r1467: Pinboard text fg colour works again (for tasklist icons too).
[rox-filer.git] / ROX-Filer / src / pinboard.c
blob4918a63449c6ade146d29ef23a302f84aa30f105
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <ctype.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkx.h>
32 #include <gtk/gtkinvisible.h>
33 #include <stdlib.h>
34 #include <libxml/parser.h>
35 #include <signal.h>
37 #include "global.h"
39 #include "pinboard.h"
40 #include "main.h"
41 #include "dnd.h"
42 #include "pixmaps.h"
43 #include "type.h"
44 #include "choices.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "options.h"
48 #include "diritem.h"
49 #include "bind.h"
50 #include "icon.h"
51 #include "run.h"
52 #include "appinfo.h"
53 #include "menu.h"
54 #include "xml.h"
55 #include "tasklist.h"
57 static gboolean tmp_icon_selected = FALSE; /* When dragging */
59 struct _Pinboard {
60 guchar *name; /* Leaf name */
61 GList *icons;
62 GtkStyle *style;
64 gchar *backdrop; /* Pathname */
65 BackdropStyle backdrop_style;
66 gint to_backdrop_app; /* pipe FD, or -1 */
67 gint from_backdrop_app; /* pipe FD, or -1 */
68 gint input_tag;
69 GString *input_buffer;
71 GtkWidget *window; /* Screen-sized window */
72 GtkWidget *fixed;
75 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
77 typedef struct _PinIconClass PinIconClass;
78 typedef struct _PinIcon PinIcon;
80 struct _PinIconClass {
81 IconClass parent;
84 struct _PinIcon {
85 Icon icon;
87 int x, y;
88 GtkWidget *win;
89 GtkWidget *widget; /* The drawing area for the icon */
90 GtkWidget *label;
93 /* The number of pixels between the bottom of the image and the top
94 * of the text.
96 #define GAP 4
98 /* The size of the border around the icon which is used when winking */
99 #define WINK_FRAME 2
101 /* Grid sizes */
102 #define GRID_STEP_FINE 2
103 #define GRID_STEP_MED 16
104 #define GRID_STEP_COARSE 32
106 static PinIcon *current_wink_icon = NULL;
107 static gint wink_timeout;
109 /* Used for the text colours (only) in the icons (and tasklist windows) */
110 GdkColor pin_text_fg_col, pin_text_bg_col;
112 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
113 static GtkStyle *pinicon_style = NULL;
115 Pinboard *current_pinboard = NULL;
116 static gint loading_pinboard = 0; /* Non-zero => loading */
118 /* The Icon that was used to start the current drag, if any */
119 Icon *pinboard_drag_in_progress = NULL;
121 typedef enum {
122 TEXT_BG_NONE = 0,
123 TEXT_BG_OUTLINE = 1,
124 TEXT_BG_SOLID = 2,
125 } TextBgType;
127 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
128 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
129 static Option o_pinboard_tasklist;
131 /* Static prototypes */
132 static GType pin_icon_get_type(void);
133 static void set_size_and_style(PinIcon *pi);
134 static gint stop_expose(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
135 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
136 static gint end_wink(gpointer data);
137 static gboolean button_release_event(GtkWidget *widget,
138 GdkEventButton *event,
139 PinIcon *pi);
140 static gboolean enter_notify(GtkWidget *widget,
141 GdkEventCrossing *event,
142 PinIcon *pi);
143 static gboolean button_press_event(GtkWidget *widget,
144 GdkEventButton *event,
145 PinIcon *pi);
146 static gint icon_motion_notify(GtkWidget *widget,
147 GdkEventMotion *event,
148 PinIcon *pi);
149 static const char *pin_from_file(gchar *line);
150 static void snap_to_grid(int *x, int *y);
151 static void offset_from_centre(PinIcon *pi, int *x, int *y);
152 static gboolean drag_motion(GtkWidget *widget,
153 GdkDragContext *context,
154 gint x,
155 gint y,
156 guint time,
157 PinIcon *pi);
158 static void drag_set_pinicon_dest(PinIcon *pi);
159 static void drag_leave(GtkWidget *widget,
160 GdkDragContext *context,
161 guint32 time,
162 PinIcon *pi);
163 static gboolean bg_drag_motion(GtkWidget *widget,
164 GdkDragContext *context,
165 gint x,
166 gint y,
167 guint time,
168 gpointer data);
169 static gboolean bg_drag_leave(GtkWidget *widget,
170 GdkDragContext *context,
171 guint32 time,
172 gpointer data);
173 static gboolean bg_expose(GtkWidget *window,
174 GdkEventExpose *event, gpointer data);
175 static void drag_end(GtkWidget *widget,
176 GdkDragContext *context,
177 PinIcon *pi);
178 static void reshape_all(void);
179 static void pinboard_check_options(void);
180 static void pinboard_load_from_xml(xmlDocPtr doc);
181 static void pinboard_clear(void);
182 static void pinboard_save(void);
183 static PinIcon *pin_icon_new(const char *pathname, const char *name);
184 static void pin_icon_destroyed(PinIcon *pi);
185 static void pin_icon_set_tip(PinIcon *pi);
186 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
187 static void create_pinboard_window(Pinboard *pinboard);
188 static void reload_backdrop(Pinboard *pinboard,
189 const gchar *backdrop,
190 BackdropStyle backdrop_style);
191 static void set_backdrop(const gchar *path, BackdropStyle style);
192 void pinboard_reshape_icon(Icon *icon);
193 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
194 static void abandon_backdrop_app(Pinboard *pinboard);
197 /****************************************************************
198 * EXTERNAL INTERFACE *
199 ****************************************************************/
201 void pinboard_init(void)
203 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
204 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
206 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
207 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
208 GRID_STEP_COARSE);
209 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
211 option_add_notify(pinboard_check_options);
213 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
214 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
217 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
218 * and make it the current pinboard.
219 * Any existing pinned items are removed. You must call this
220 * at least once before using the pinboard. NULL disables the
221 * pinboard.
223 void pinboard_activate(const gchar *name)
225 Pinboard *old_board = current_pinboard;
226 guchar *path, *slash;
228 /* Treat an empty name the same as NULL */
229 if (name && !*name)
230 name = NULL;
232 if (old_board)
233 pinboard_clear();
235 if (!name)
237 if (number_of_windows < 1 && gtk_main_level() > 0)
238 gtk_main_quit();
239 return;
242 number_of_windows++;
244 slash = strchr(name, '/');
245 if (slash)
247 if (access(name, F_OK))
248 path = NULL; /* File does not (yet) exist */
249 else
250 path = g_strdup(name);
252 else
254 guchar *leaf;
256 leaf = g_strconcat("pb_", name, NULL);
257 path = choices_find_path_load(leaf, PROJECT);
258 g_free(leaf);
261 current_pinboard = g_new(Pinboard, 1);
262 current_pinboard->name = g_strdup(name);
263 current_pinboard->icons = NULL;
264 current_pinboard->window = NULL;
265 current_pinboard->backdrop = NULL;
266 current_pinboard->backdrop_style = BACKDROP_NONE;
267 current_pinboard->to_backdrop_app = -1;
268 current_pinboard->from_backdrop_app = -1;
269 current_pinboard->input_tag = -1;
270 current_pinboard->input_buffer = NULL;
272 create_pinboard_window(current_pinboard);
274 loading_pinboard++;
275 if (path)
277 xmlDocPtr doc;
278 doc = xmlParseFile(path);
279 if (doc)
281 pinboard_load_from_xml(doc);
282 xmlFreeDoc(doc);
283 reload_backdrop(current_pinboard,
284 current_pinboard->backdrop,
285 current_pinboard->backdrop_style);
287 else
289 parse_file(path, pin_from_file);
290 info_message(_("Your old pinboard file has been "
291 "converted to the new XML format."));
292 pinboard_save();
294 g_free(path);
296 else
297 pinboard_pin(home_dir, "Home",
298 4 + ICON_WIDTH / 2,
299 4 + ICON_HEIGHT / 2);
300 loading_pinboard--;
302 if (o_pinboard_tasklist.int_value)
303 tasklist_set_active(TRUE);
306 /* Return the window of the current pinboard, or NULL.
307 * Used to make sure lowering the panels doesn't lose them...
309 GdkWindow *pinboard_get_window(void)
311 if (current_pinboard)
312 return current_pinboard->window->window;
313 return NULL;
316 const char *pinboard_get_name(void)
318 g_return_val_if_fail(current_pinboard != NULL, NULL);
320 return current_pinboard->name;
323 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
324 * being cleared.
326 void pinboard_add_widget(GtkWidget *widget, int x, int y)
328 g_return_if_fail(current_pinboard != NULL);
330 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, x, y);
333 /* Add a new icon to the background.
334 * 'path' should be an absolute pathname.
335 * 'x' and 'y' are the coordinates of the point in the middle of the text.
336 * 'name' is the name to use. If NULL then the leafname of path is used.
338 * name and path are in UTF-8 for Gtk+-2.0 only.
340 void pinboard_pin(const gchar *path, const gchar *name, int x, int y)
342 GtkWidget *align, *vbox;
343 GdkWindow *events;
344 PinIcon *pi;
345 Icon *icon;
347 g_return_if_fail(path != NULL);
348 g_return_if_fail(current_pinboard != NULL);
350 pi = pin_icon_new(path, name);
351 icon = (Icon *) pi;
352 pi->x = x;
353 pi->y = y;
355 /* This is a bit complicated...
357 * An icon needs to be a NO_WINDOW widget so that the image can
358 * blend with the background (A ParentRelative window also works, but
359 * is slow, causes the xfree86's memory consumption to grow without
360 * bound, and doesn't even get freed when the filer quits!).
362 * However, the icon also needs to have a window, so we get events
363 * delivered correctly. The solution is to float an InputOnly window
364 * over the icon. Since GtkButton works the same way, we just use
365 * that :-)
368 /* Button takes the initial ref of Icon */
369 pi->win = gtk_button_new();
370 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
371 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
372 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
374 vbox = gtk_vbox_new(FALSE, 0);
375 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
377 align = gtk_alignment_new(0.5, 0.5, 0, 0);
378 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
379 gtk_container_add(GTK_CONTAINER(align), pi->widget);
381 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
382 drag_set_pinicon_dest(pi);
383 g_signal_connect(pi->win, "drag_data_get",
384 G_CALLBACK(drag_data_get), NULL);
386 pi->label = gtk_label_new(icon->item->leafname);
387 gtk_label_set_line_wrap(GTK_LABEL(pi->label), TRUE);
388 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
390 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
392 snap_to_grid(&x, &y);
393 pi->x = x;
394 pi->y = y;
395 gtk_widget_show_all(pi->win);
396 pinboard_reshape_icon((Icon *) pi);
398 gtk_widget_realize(pi->win);
399 events = GTK_BUTTON(pi->win)->event_window;
400 gdk_window_set_events(events,
401 GDK_EXPOSURE_MASK |
402 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
403 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
404 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
405 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
406 g_signal_connect(pi->win, "enter-notify-event",
407 G_CALLBACK(enter_notify), pi);
408 g_signal_connect(pi->win, "button-press-event",
409 G_CALLBACK(button_press_event), pi);
410 g_signal_connect(pi->win, "button-release-event",
411 G_CALLBACK(button_release_event), pi);
412 g_signal_connect(pi->win, "motion-notify-event",
413 G_CALLBACK(icon_motion_notify), pi);
414 g_signal_connect(pi->win, "expose-event",
415 G_CALLBACK(stop_expose), pi);
416 g_signal_connect(pi->widget, "expose-event",
417 G_CALLBACK(draw_icon), pi);
418 g_signal_connect_swapped(pi->win, "destroy",
419 G_CALLBACK(pin_icon_destroyed), pi);
421 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
422 pin_icon_set_tip(pi);
424 if (!loading_pinboard)
425 pinboard_save();
428 /* Remove an icon from the pinboard */
429 /* XXX: use destroy */
430 void pinboard_unpin(PinIcon *pi)
432 g_return_if_fail(pi != NULL);
434 gtk_widget_destroy(pi->win);
435 pinboard_save();
438 /* Put a border around the icon, briefly.
439 * If icon is NULL then cancel any existing wink.
440 * The icon will automatically unhighlight unless timeout is FALSE,
441 * in which case you must call this function again (with NULL or another
442 * icon) to remove the highlight.
444 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
446 PinIcon *old = current_wink_icon;
448 if (old == pi)
449 return;
451 current_wink_icon = pi;
453 if (old)
455 gtk_widget_queue_draw(old->win);
456 gdk_window_process_updates(old->widget->window, TRUE);
458 if (wink_timeout != -1)
459 gtk_timeout_remove(wink_timeout);
462 if (pi)
464 gtk_widget_queue_draw(pi->win);
465 gdk_window_process_updates(pi->widget->window, TRUE);
467 if (timeout)
468 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
469 else
470 wink_timeout = -1;
474 /* Icon's size, shape or appearance has changed - update the display */
475 void pinboard_reshape_icon(Icon *icon)
477 PinIcon *pi = (PinIcon *) icon;
478 int x = pi->x, y = pi->y;
480 set_size_and_style(pi);
481 offset_from_centre(pi, &x, &y);
483 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
485 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
486 pi->win, x, y);
490 /* 'app' is saved as the new application to set the backdrop. It will then be
491 * run, and should communicate with the filer as described in the manual.
493 void pinboard_set_backdrop_app(const gchar *app)
495 XMLwrapper *ai;
496 DirItem *item;
497 gboolean can_set;
499 item = diritem_new("");
500 diritem_restat(app, item, NULL);
501 ai = appinfo_get(app, item);
502 diritem_free(item);
504 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
505 if (ai)
506 g_object_unref(ai);
508 if (can_set)
509 set_backdrop(app, BACKDROP_PROGRAM);
510 else
511 delayed_error(_("This program does not know how to "
512 "manage ROX-Filer's backdrop image."));
515 /* Use this icon / program as the backdrop for the current pinboard.
516 * NULL removes the backdrop. If type is BACKDROP_NONE then will try
517 * to work it out ourself.
518 * If no pinboard is in use, activates 'Default'.
520 void pinboard_set_backdrop(DirItem *item, const gchar *path)
522 if (path == NULL || item == NULL)
524 /* Remove backdrop */
526 if (current_pinboard && current_pinboard->backdrop)
527 set_backdrop(NULL, BACKDROP_NONE);
528 else
529 delayed_error(_("No backdrop image is currently "
530 "set. Use the 'Use for Backdrop' menu "
531 "item to set an image (or program) for "
532 "the backdrop."));
533 return;
536 if (item->flags & ITEM_FLAG_APPDIR)
538 /* Use this program to set the backdrop */
539 pinboard_set_backdrop_app(path);
541 else if (item->base_type == TYPE_FILE)
543 int i;
545 /* Use this file as the backdrop */
547 i = get_choice(_("Set backdrop"),
548 _("How should this image be displayed?"),
549 4, GTK_STOCK_CANCEL,
550 _("Centred"), _("_Scaled"), _("Tiled"));
552 if (i >= 0)
553 set_backdrop(path,
554 i == 1 ? BACKDROP_CENTRE :
555 i == 2 ? BACKDROP_SCALE :
556 BACKDROP_TILE);
558 else
559 delayed_error(_("Only files and certain applications can be "
560 "used to set the background image."));
563 /****************************************************************
564 * INTERNAL FUNCTIONS *
565 ****************************************************************/
567 static void pinboard_check_options(void)
569 GdkColor n_fg, n_bg;
571 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
572 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
574 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
576 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
577 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0)
579 memcpy(&pin_text_fg_col, &n_fg, sizeof(GdkColor));
580 memcpy(&pin_text_bg_col, &n_bg, sizeof(GdkColor));
582 if (pinicon_style)
584 g_object_unref(G_OBJECT(pinicon_style));
585 pinicon_style = NULL;
588 if (current_pinboard)
589 reshape_all();
591 tasklist_style_changed();
595 static gint end_wink(gpointer data)
597 pinboard_wink_item(NULL, FALSE);
598 return FALSE;
601 /* Updates the width, height, name_width and label fields, and resizes the
602 * window. Also sets the style to pinicon_style, generating it if needed.
604 static void set_size_and_style(PinIcon *pi)
606 Icon *icon = (Icon *) pi;
607 MaskedPixmap *image = icon->item->image;
608 int iwidth = image->width;
609 int iheight = image->height;
611 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
612 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
613 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
614 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
616 gtk_label_set_text(GTK_LABEL(pi->label), icon->item->leafname);
618 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
621 /* Don't draw the normal button effect */
622 static gint stop_expose(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
624 static GtkWidgetClass *parent_class = NULL;
626 if (!parent_class)
628 gpointer c = ((GTypeInstance *) widget)->g_class;
629 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
632 (parent_class->expose_event)(widget, event);
633 return TRUE;
636 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
638 Icon *icon = (Icon *) pi;
639 DirItem *item = icon->item;
640 MaskedPixmap *image = item->image;
641 int iwidth = image->width;
642 int iheight = image->height;
643 int x, y;
644 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
645 : GTK_STATE_NORMAL;
648 x = widget->allocation.x;
649 y = widget->allocation.y;
651 gdk_pixbuf_render_to_drawable_alpha(
652 icon->selected ? image->pixbuf_lit : image->pixbuf,
653 widget->window,
654 0, 0, /* src */
655 x, y, /* dest */
656 iwidth, iheight,
657 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
658 GDK_RGB_DITHER_NORMAL, 0, 0);
660 if (item->flags & ITEM_FLAG_SYMLINK)
662 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
663 widget->window,
664 0, 0, /* src */
665 x, y,
666 -1, -1,
667 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
668 GDK_RGB_DITHER_NORMAL, 0, 0);
670 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
672 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
673 ? im_mounted
674 : im_unmounted;
676 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
677 widget->window,
678 0, 0, /* src */
679 x, y,
680 -1, -1,
681 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
682 GDK_RGB_DITHER_NORMAL, 0, 0);
685 if (icon->selected)
687 gtk_paint_flat_box(pi->label->style, pi->label->window,
688 state,
689 GTK_SHADOW_NONE,
690 NULL, pi->label, "text",
691 pi->label->allocation.x,
692 pi->label->allocation.y,
693 pi->label->allocation.width,
694 pi->label->allocation.height);
697 return FALSE;
700 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
702 gint x, y, width, height;
704 if (current_wink_icon != pi)
705 return FALSE;
707 x = widget->allocation.x;
708 y = widget->allocation.y;
709 width = widget->allocation.width;
710 height = widget->allocation.height;
712 gdk_draw_rectangle(widget->window,
713 pi->widget->style->white_gc,
714 FALSE,
715 x, y, width - 1, height - 1);
716 gdk_draw_rectangle(widget->window,
717 pi->widget->style->black_gc,
718 FALSE,
719 x + 1, y + 1, width - 3, height - 3);
721 return FALSE;
724 static gboolean enter_notify(GtkWidget *widget,
725 GdkEventCrossing *event,
726 PinIcon *pi)
728 icon_may_update((Icon *) pi);
730 return FALSE;
733 static void perform_action(PinIcon *pi, GdkEventButton *event)
735 BindAction action;
736 Icon *icon = (Icon *) pi;
738 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
739 event);
741 /* Actions that can happen with or without an icon */
742 switch (action)
744 case ACT_CLEAR_SELECTION:
745 icon_select_only(NULL);
746 return;
747 case ACT_POPUP_MENU:
748 dnd_motion_ungrab();
749 pinboard_show_menu(event, pi);
750 return;
751 case ACT_IGNORE:
752 return;
753 default:
754 break;
757 g_return_if_fail(pi != NULL);
759 switch (action)
761 case ACT_OPEN_ITEM:
762 dnd_motion_ungrab();
763 pinboard_wink_item(pi, TRUE);
764 if (event->type == GDK_2BUTTON_PRESS)
765 icon_set_selected(icon, FALSE);
766 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
767 break;
768 case ACT_EDIT_ITEM:
769 dnd_motion_ungrab();
770 pinboard_wink_item(pi, TRUE);
771 if (event->type == GDK_2BUTTON_PRESS)
772 icon_set_selected(icon, FALSE);
773 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
774 break;
775 case ACT_PRIME_AND_SELECT:
776 if (!icon->selected)
777 icon_select_only(icon);
778 dnd_motion_start(MOTION_READY_FOR_DND);
779 break;
780 case ACT_PRIME_AND_TOGGLE:
781 icon_set_selected(icon, !icon->selected);
782 dnd_motion_start(MOTION_READY_FOR_DND);
783 break;
784 case ACT_PRIME_FOR_DND:
785 dnd_motion_start(MOTION_READY_FOR_DND);
786 break;
787 case ACT_TOGGLE_SELECTED:
788 icon_set_selected(icon, !icon->selected);
789 break;
790 case ACT_SELECT_EXCL:
791 icon_select_only(icon);
792 break;
793 default:
794 g_warning("Unsupported action : %d\n", action);
795 break;
799 static void forward_to_root(GdkEventButton *event)
801 XButtonEvent xev;
803 if (event->type == GDK_BUTTON_PRESS)
805 xev.type = ButtonPress;
806 XUngrabPointer(gdk_display, event->time);
808 else
809 xev.type = ButtonRelease;
811 xev.window = gdk_x11_get_default_root_xwindow();
812 xev.root = xev.window;
813 xev.subwindow = None;
814 xev.time = event->time;
815 xev.x = event->x;
816 xev.y = event->y;
817 xev.x_root = event->x_root;
818 xev.y_root = event->y_root;
819 xev.state = event->state;
820 xev.button = event->button;
821 xev.same_screen = True;
823 XSendEvent(gdk_display, xev.window, False,
824 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
827 /* pi is NULL if this is a root event */
828 static gboolean button_release_event(GtkWidget *widget,
829 GdkEventButton *event,
830 PinIcon *pi)
832 if (event->button == 2)
833 forward_to_root(event);
834 else if (dnd_motion_release(event))
835 return TRUE;
837 perform_action(pi, event);
839 return TRUE;
842 /* pi is NULL if this is a root event */
843 static gboolean button_press_event(GtkWidget *widget,
844 GdkEventButton *event,
845 PinIcon *pi)
847 /* Just in case we've jumped in front of everything... */
848 gdk_window_lower(current_pinboard->window->window);
850 if (event->button == 2)
851 forward_to_root(event);
852 else if (dnd_motion_press(widget, event))
853 perform_action(pi, event);
855 return TRUE;
858 static void start_drag(PinIcon *pi, GdkEventMotion *event)
860 GtkWidget *widget = pi->win;
861 Icon *icon = (Icon *) pi;
863 if (!icon->selected)
865 tmp_icon_selected = TRUE;
866 icon_select_only(icon);
869 g_return_if_fail(icon_selection != NULL);
871 pinboard_drag_in_progress = icon;
873 if (icon_selection->next == NULL)
874 drag_one_item(widget, event, icon->path, icon->item, NULL);
875 else
877 guchar *uri_list;
879 uri_list = icon_create_uri_list();
880 drag_selection(widget, event, uri_list);
881 g_free(uri_list);
885 /* An icon is being dragged around... */
886 static gint icon_motion_notify(GtkWidget *widget,
887 GdkEventMotion *event,
888 PinIcon *pi)
890 if (motion_state == MOTION_READY_FOR_DND)
892 if (dnd_motion_moved(event))
893 start_drag(pi, event);
894 return TRUE;
897 return FALSE;
900 static void backdrop_from_xml(xmlNode *node)
902 gchar *style;
904 g_free(current_pinboard->backdrop);
905 current_pinboard->backdrop = xmlNodeGetContent(node);
907 style = xmlGetProp(node, "style");
909 if (style)
911 current_pinboard->backdrop_style =
912 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
913 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
914 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
915 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
916 BACKDROP_NONE;
917 g_free(style);
919 else
920 current_pinboard->backdrop_style = BACKDROP_TILE;
923 /* Create one pinboard icon for each icon in the doc */
924 static void pinboard_load_from_xml(xmlDocPtr doc)
926 xmlNodePtr node, root;
927 char *tmp, *label, *path;
928 int x, y;
930 root = xmlDocGetRootElement(doc);
932 for (node = root->xmlChildrenNode; node; node = node->next)
934 if (node->type != XML_ELEMENT_NODE)
935 continue;
936 if (strcmp(node->name, "backdrop") == 0)
938 backdrop_from_xml(node);
939 continue;
941 if (strcmp(node->name, "icon") != 0)
942 continue;
944 tmp = xmlGetProp(node, "x");
945 if (!tmp)
946 continue;
947 x = atoi(tmp);
948 g_free(tmp);
950 tmp = xmlGetProp(node, "y");
951 if (!tmp)
952 continue;
953 y = atoi(tmp);
954 g_free(tmp);
956 label = xmlGetProp(node, "label");
957 if (!label)
958 label = g_strdup("<missing label>");
959 path = xmlNodeGetContent(node);
960 if (!path)
961 path = g_strdup("<missing path>");
963 pinboard_pin(path, label, x, y);
965 g_free(path);
966 g_free(label);
970 /* Called for each line in the pinboard file while loading a new board.
971 * Only used for old-format files when converting to XML.
973 static const char *pin_from_file(gchar *line)
975 gchar *leaf = NULL;
976 int x, y, n;
978 if (*line == '<')
980 gchar *end;
982 end = strchr(line + 1, '>');
983 if (!end)
984 return _("Missing '>' in icon label");
986 leaf = g_strndup(line + 1, end - line - 1);
988 line = end + 1;
990 while (isspace(*line))
991 line++;
992 if (*line != ',')
993 return _("Missing ',' after icon label");
994 line++;
997 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
998 return NULL; /* Ignore format errors */
1000 pinboard_pin(line + n, leaf, x, y);
1002 g_free(leaf);
1004 return NULL;
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;
1012 GList *next;
1013 xmlDocPtr doc = NULL;
1014 xmlNodePtr root;
1016 g_return_if_fail(current_pinboard != NULL);
1018 if (strchr(current_pinboard->name, '/'))
1019 save = g_strdup(current_pinboard->name);
1020 else
1022 guchar *leaf;
1024 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1025 save = choices_find_path_save(leaf, PROJECT, TRUE);
1026 g_free(leaf);
1029 if (!save)
1030 return;
1032 doc = xmlNewDoc("1.0");
1033 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1035 root = xmlDocGetRootElement(doc);
1037 if (current_pinboard->backdrop)
1039 BackdropStyle style = current_pinboard->backdrop_style;
1040 xmlNodePtr tree;
1042 tree = xmlNewTextChild(root, NULL, "backdrop",
1043 current_pinboard->backdrop);
1044 xmlSetProp(tree, "style",
1045 style == BACKDROP_TILE ? "Tiled" :
1046 style == BACKDROP_CENTRE ? "Centred" :
1047 style == BACKDROP_SCALE ? "Scaled" :
1048 "Program");
1051 for (next = current_pinboard->icons; next; next = next->next)
1053 xmlNodePtr tree;
1054 PinIcon *pi = (PinIcon *) next->data;
1055 Icon *icon = (Icon *) pi;
1056 char *tmp;
1058 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1060 tmp = g_strdup_printf("%d", pi->x);
1061 xmlSetProp(tree, "x", tmp);
1062 g_free(tmp);
1064 tmp = g_strdup_printf("%d", pi->y);
1065 xmlSetProp(tree, "y", tmp);
1066 g_free(tmp);
1068 xmlSetProp(tree, "label", icon->item->leafname);
1071 save_new = g_strconcat(save, ".new", NULL);
1072 if (save_xml_file(doc, save_new) || rename(save_new, save))
1073 delayed_error(_("Error saving pinboard %s: %s"),
1074 save, g_strerror(errno));
1075 g_free(save_new);
1077 g_free(save);
1078 if (doc)
1079 xmlFreeDoc(doc);
1082 static void snap_to_grid(int *x, int *y)
1084 int step = o_pinboard_grid_step.int_value;
1086 *x = ((*x + step / 2) / step) * step;
1087 *y = ((*y + step / 2) / step) * step;
1090 /* Convert (x,y) from a centre point to a window position */
1091 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1093 gboolean clamp = o_pinboard_clamp_icons.int_value;
1094 GtkRequisition req;
1096 gtk_widget_size_request(pi->win, &req);
1098 *x -= req.width >> 1;
1099 *y -= req.height >> 1;
1100 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1101 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1104 /* Same as drag_set_dest(), but for pinboard icons */
1105 static void drag_set_pinicon_dest(PinIcon *pi)
1107 GtkObject *obj = GTK_OBJECT(pi->win);
1109 make_drop_target(pi->win, 0);
1111 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1112 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1113 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1116 /* Called during the drag when the mouse is in a widget registered
1117 * as a drop target. Returns TRUE if we can accept the drop.
1119 static gboolean drag_motion(GtkWidget *widget,
1120 GdkDragContext *context,
1121 gint x,
1122 gint y,
1123 guint time,
1124 PinIcon *pi)
1126 GdkDragAction action = context->suggested_action;
1127 char *type = NULL;
1128 Icon *icon = (Icon *) pi;
1129 DirItem *item = icon->item;
1131 if (gtk_drag_get_source_widget(context) == widget)
1132 goto out; /* Can't drag something to itself! */
1134 if (icon->selected)
1135 goto out; /* Can't drag a selection to itself */
1137 type = dnd_motion_item(context, &item);
1139 if (!item)
1140 type = NULL;
1141 out:
1142 /* We actually must pretend to accept the drop, even if the
1143 * directory isn't writeable, so that the spring-opening
1144 * thing works.
1147 /* Don't allow drops to non-writeable directories */
1148 if (o_dnd_spring_open.int_value == FALSE &&
1149 type == drop_dest_dir &&
1150 access(icon->path, W_OK) != 0)
1152 type = NULL;
1155 g_dataset_set_data(context, "drop_dest_type", type);
1156 if (type)
1158 gdk_drag_status(context, action, time);
1159 g_dataset_set_data_full(context, "drop_dest_path",
1160 g_strdup(icon->path), g_free);
1161 if (type == drop_dest_dir)
1162 dnd_spring_load(context, NULL);
1164 pinboard_wink_item(pi, FALSE);
1166 else
1167 gdk_drag_status(context, 0, time);
1169 /* Always return TRUE to stop the pinboard getting the events */
1170 return TRUE;
1173 static gboolean pinboard_shadow = FALSE;
1174 static gint shadow_x, shadow_y;
1175 #define SHADOW_SIZE (ICON_WIDTH)
1177 static gboolean bg_expose(GtkWidget *widget,
1178 GdkEventExpose *event, gpointer data)
1180 if (!pinboard_shadow)
1181 return FALSE;
1183 gdk_draw_rectangle(widget->window,
1184 widget->style->white_gc, FALSE,
1185 shadow_x, shadow_y,
1186 SHADOW_SIZE, SHADOW_SIZE);
1187 gdk_draw_rectangle(widget->window,
1188 widget->style->black_gc, FALSE,
1189 shadow_x + 1, shadow_y + 1,
1190 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1192 return FALSE;
1195 /* Draw a 'shadow' under an icon being dragged, showing where
1196 * it will land.
1198 static void pinboard_set_shadow(gboolean on)
1200 GdkRectangle area;
1202 if (pinboard_shadow)
1204 area.x = shadow_x;
1205 area.y = shadow_y;
1206 area.width = SHADOW_SIZE + 1;
1207 area.height = SHADOW_SIZE + 1;
1209 gdk_window_invalidate_rect(current_pinboard->window->window,
1210 &area, TRUE);
1213 if (on)
1215 int old_x = shadow_x, old_y = shadow_y;
1217 gdk_window_get_pointer(current_pinboard->fixed->window,
1218 &shadow_x, &shadow_y, NULL);
1219 snap_to_grid(&shadow_x, &shadow_y);
1220 shadow_x -= SHADOW_SIZE / 2;
1221 shadow_y -= SHADOW_SIZE / 2;
1224 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1225 return;
1227 area.x = shadow_x;
1228 area.y = shadow_y;
1229 area.width = SHADOW_SIZE + 1;
1230 area.height = SHADOW_SIZE + 1;
1232 gdk_window_invalidate_rect(current_pinboard->window->window,
1233 &area, TRUE);
1236 pinboard_shadow = on;
1239 /* Called when dragging some pinboard icons finishes */
1240 void pinboard_move_icons(void)
1242 int x = shadow_x, y = shadow_y;
1243 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1244 int width, height;
1246 g_return_if_fail(pi != NULL);
1248 x += SHADOW_SIZE / 2;
1249 y += SHADOW_SIZE / 2;
1250 snap_to_grid(&x, &y);
1252 if (pi->x == x && pi->y == y)
1253 return;
1255 pi->x = x;
1256 pi->y = y;
1257 gdk_drawable_get_size(pi->win->window, &width, &height);
1258 offset_from_centre(pi, &x, &y);
1260 fixed_move_fast(GTK_FIXED(current_pinboard->fixed), pi->win, x, y);
1262 pinboard_save();
1265 static void drag_leave(GtkWidget *widget,
1266 GdkDragContext *context,
1267 guint32 time,
1268 PinIcon *pi)
1270 pinboard_wink_item(NULL, FALSE);
1271 dnd_spring_abort();
1274 static gboolean bg_drag_leave(GtkWidget *widget,
1275 GdkDragContext *context,
1276 guint32 time,
1277 gpointer data)
1279 pinboard_set_shadow(FALSE);
1280 return TRUE;
1283 static gboolean bg_drag_motion(GtkWidget *widget,
1284 GdkDragContext *context,
1285 gint x,
1286 gint y,
1287 guint time,
1288 gpointer data)
1290 /* Dragging from the pinboard to the pinboard is not allowed */
1292 if (!provides(context, text_uri_list))
1293 return FALSE;
1295 pinboard_set_shadow(TRUE);
1297 gdk_drag_status(context,
1298 context->suggested_action == GDK_ACTION_ASK
1299 ? GDK_ACTION_LINK : context->suggested_action,
1300 time);
1301 return TRUE;
1304 static void drag_end(GtkWidget *widget,
1305 GdkDragContext *context,
1306 PinIcon *pi)
1308 pinboard_drag_in_progress = NULL;
1309 if (tmp_icon_selected)
1311 icon_select_only(NULL);
1312 tmp_icon_selected = FALSE;
1316 /* Something which affects all the icons has changed - reshape
1317 * and redraw all of them.
1319 static void reshape_all(void)
1321 GList *next;
1323 g_return_if_fail(current_pinboard != NULL);
1325 for (next = current_pinboard->icons; next; next = next->next)
1327 Icon *icon = (Icon *) next->data;
1328 pinboard_reshape_icon(icon);
1332 /* Turns off the pinboard. Does not call gtk_main_quit. */
1333 static void pinboard_clear(void)
1335 GList *next;
1337 g_return_if_fail(current_pinboard != NULL);
1339 tasklist_set_active(FALSE);
1341 next = current_pinboard->icons;
1342 while (next)
1344 PinIcon *pi = (PinIcon *) next->data;
1346 next = next->next;
1348 gtk_widget_destroy(pi->win);
1351 gtk_widget_destroy(current_pinboard->window);
1353 abandon_backdrop_app(current_pinboard);
1355 g_free(current_pinboard->name);
1356 g_free(current_pinboard);
1357 current_pinboard = NULL;
1359 number_of_windows--;
1362 static gpointer parent_class;
1364 static void pin_icon_destroy(Icon *icon)
1366 PinIcon *pi = (PinIcon *) icon;
1368 g_return_if_fail(pi->win != NULL);
1370 gtk_widget_destroy(pi->win);
1373 static void pinboard_remove_items(void)
1375 g_return_if_fail(icon_selection != NULL);
1377 while (icon_selection)
1378 icon_destroy((Icon *) icon_selection->data);
1380 pinboard_save();
1383 static void pin_icon_update(Icon *icon)
1385 pinboard_reshape_icon(icon);
1386 pinboard_save();
1389 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1391 return IS_PIN_ICON(other);
1394 static void pin_icon_class_init(gpointer gclass, gpointer data)
1396 IconClass *icon = (IconClass *) gclass;
1398 parent_class = g_type_class_peek_parent(gclass);
1400 icon->destroy = pin_icon_destroy;
1401 icon->redraw = pinboard_reshape_icon;
1402 icon->update = pin_icon_update;
1403 icon->remove_items = pinboard_remove_items;
1404 icon->same_group = pin_icon_same_group;
1407 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1411 static GType pin_icon_get_type(void)
1413 static GType type = 0;
1415 if (!type)
1417 static const GTypeInfo info =
1419 sizeof (PinIconClass),
1420 NULL, /* base_init */
1421 NULL, /* base_finalise */
1422 pin_icon_class_init,
1423 NULL, /* class_finalise */
1424 NULL, /* class_data */
1425 sizeof(PinIcon),
1426 0, /* n_preallocs */
1427 pin_icon_init
1430 type = g_type_register_static(icon_get_type(),
1431 "PinIcon", &info, 0);
1434 return type;
1437 static PinIcon *pin_icon_new(const char *pathname, const char *name)
1439 PinIcon *pi;
1440 Icon *icon;
1442 pi = g_object_new(pin_icon_get_type(), NULL);
1443 icon = (Icon *) pi;
1445 icon_set_path(icon, pathname, name);
1447 return pi;
1450 /* Called when the window widget is somehow destroyed */
1451 static void pin_icon_destroyed(PinIcon *pi)
1453 g_return_if_fail(pi->win != NULL);
1455 pi->win = NULL;
1457 pinboard_wink_item(NULL, FALSE);
1459 if (pinboard_drag_in_progress == (Icon *) pi)
1460 pinboard_drag_in_progress = NULL;
1462 if (current_pinboard)
1463 current_pinboard->icons =
1464 g_list_remove(current_pinboard->icons, pi);
1466 g_object_unref(pi);
1469 /* Set the tooltip */
1470 static void pin_icon_set_tip(PinIcon *pi)
1472 XMLwrapper *ai;
1473 xmlNode *node;
1474 Icon *icon = (Icon *) pi;
1476 g_return_if_fail(pi != NULL);
1478 ai = appinfo_get(icon->path, icon->item);
1480 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
1482 guchar *str;
1483 str = xmlNodeListGetString(node->doc,
1484 node->xmlChildrenNode, 1);
1485 if (str)
1487 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
1488 g_free(str);
1491 else
1492 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
1494 if (ai)
1495 g_object_unref(ai);
1498 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
1500 int pos[3];
1502 pos[0] = event->x_root;
1503 pos[1] = event->y_root;
1504 pos[2] = 1;
1506 icon_prepare_menu((Icon *) pi);
1507 gtk_widget_show(icon_menu_remove_backdrop);
1508 gtk_widget_set_sensitive(GTK_BIN(icon_menu_remove_backdrop)->child,
1509 current_pinboard->backdrop != NULL);
1511 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
1512 position_menu,
1513 (gpointer) pos, event->button, event->time);
1516 static void create_pinboard_window(Pinboard *pinboard)
1518 GtkWidget *win;
1520 g_return_if_fail(pinboard->window == NULL);
1522 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1523 gtk_widget_set_app_paintable(win, TRUE);
1524 gtk_widget_set_name(win, "rox-pinboard");
1525 pinboard->window = win;
1526 pinboard->fixed = gtk_fixed_new();
1527 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
1529 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
1531 gtk_widget_set_size_request(win, screen_width, screen_height);
1532 gtk_widget_realize(win);
1533 gtk_window_move(GTK_WINDOW(win), 0, 0);
1534 make_panel_window(win);
1536 /* TODO: Use gdk function when it supports this type */
1538 GdkAtom desktop_type;
1540 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1541 FALSE);
1542 gdk_property_change(win->window,
1543 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
1544 gdk_atom_intern("ATOM", FALSE), 32,
1545 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
1548 gtk_widget_add_events(win,
1549 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1550 GDK_EXPOSURE_MASK);
1551 g_signal_connect(win, "button-press-event",
1552 G_CALLBACK(button_press_event), NULL);
1553 g_signal_connect(win, "button-release-event",
1554 G_CALLBACK(button_release_event), NULL);
1555 g_signal_connect(pinboard->fixed, "expose_event",
1556 G_CALLBACK(bg_expose), NULL);
1558 /* Drag and drop handlers */
1559 drag_set_pinboard_dest(win);
1560 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
1561 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
1563 gtk_widget_show_all(win);
1564 gdk_window_lower(win->window);
1567 /* Load image 'path' and scale according to 'style' */
1568 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
1570 GdkPixmap *pixmap;
1571 GdkPixbuf *pixbuf;
1572 GError *error = NULL;
1574 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1575 if (error)
1577 delayed_error(_("Error loading backdrop image:\n%s\n"
1578 "Backdrop removed."),
1579 error->message);
1580 g_error_free(error);
1581 set_backdrop(NULL, BACKDROP_NONE);
1582 return NULL;
1585 if (style == BACKDROP_SCALE)
1587 GdkPixbuf *old = pixbuf;
1589 pixbuf = gdk_pixbuf_scale_simple(old,
1590 screen_width, screen_height,
1591 GDK_INTERP_HYPER);
1593 g_object_unref(old);
1595 else if (style == BACKDROP_CENTRE)
1597 GdkPixbuf *old = pixbuf;
1598 int x, y, width, height;
1600 width = gdk_pixbuf_get_width(pixbuf);
1601 height = gdk_pixbuf_get_height(pixbuf);
1603 pixbuf = gdk_pixbuf_new(
1604 gdk_pixbuf_get_colorspace(pixbuf), 0,
1605 8, screen_width, screen_height);
1606 gdk_pixbuf_fill(pixbuf, 0);
1608 x = (screen_width - width) / 2;
1609 y = (screen_height - height) / 2;
1610 x = MAX(x, 0);
1611 y = MAX(y, 0);
1613 gdk_pixbuf_composite(old, pixbuf,
1614 x, y,
1615 MIN(screen_width, width),
1616 MIN(screen_height, height),
1617 x, y, 1, 1,
1618 GDK_INTERP_NEAREST, 255);
1619 g_object_unref(old);
1622 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
1623 &pixmap, NULL, 0);
1624 g_object_unref(pixbuf);
1626 return pixmap;
1629 static void abandon_backdrop_app(Pinboard *pinboard)
1631 g_return_if_fail(pinboard != NULL);
1633 if (pinboard->to_backdrop_app != -1)
1635 close(pinboard->to_backdrop_app);
1636 close(pinboard->from_backdrop_app);
1637 gtk_input_remove(pinboard->input_tag);
1638 g_string_free(pinboard->input_buffer, TRUE);
1639 pinboard->to_backdrop_app = -1;
1640 pinboard->from_backdrop_app = -1;
1641 pinboard->input_tag = -1;
1642 pinboard->input_buffer = NULL;
1645 g_return_if_fail(pinboard->to_backdrop_app == -1);
1646 g_return_if_fail(pinboard->from_backdrop_app == -1);
1647 g_return_if_fail(pinboard->input_tag == -1);
1648 g_return_if_fail(pinboard->input_buffer == NULL);
1651 /* A single line has been read from the child.
1652 * Processes the command, and replies 'ok' (or abandons the child on error).
1654 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
1656 BackdropStyle style;
1657 const char *ok = "ok\n";
1659 if (strncmp(command, "tile ", 5) == 0)
1661 style = BACKDROP_TILE;
1662 command += 5;
1664 else if (strncmp(command, "scale ", 6) == 0)
1666 style = BACKDROP_SCALE;
1667 command += 6;
1669 else if (strncmp(command, "centre ", 7) == 0)
1671 style = BACKDROP_CENTRE;
1672 command += 7;
1674 else
1676 g_warning("Invalid command '%s' from backdrop app\n",
1677 command);
1678 abandon_backdrop_app(pinboard);
1679 return;
1682 reload_backdrop(pinboard, command, style);
1684 while (*ok)
1686 int sent;
1688 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
1689 if (sent <= 0)
1691 g_warning("command_from_backdrop_app: %s\n",
1692 g_strerror(errno));
1693 abandon_backdrop_app(pinboard);
1694 return;
1696 ok += sent;
1700 static void backdrop_from_child(Pinboard *pinboard,
1701 int src, GdkInputCondition cond)
1703 char buf[256];
1704 int got;
1706 got = read(src, buf, sizeof(buf));
1708 if (got <= 0)
1710 if (got < 0)
1711 g_warning("backdrop_from_child: %s\n",
1712 g_strerror(errno));
1713 abandon_backdrop_app(pinboard);
1714 return;
1717 g_string_append_len(pinboard->input_buffer, buf, got);
1719 while (pinboard->from_backdrop_app != -1)
1721 int len;
1722 char *nl, *command;
1724 nl = strchr(pinboard->input_buffer->str, '\n');
1725 if (!nl)
1726 return; /* Haven't got a whole line yet */
1728 len = nl - pinboard->input_buffer->str;
1729 command = g_strndup(pinboard->input_buffer->str, len);
1730 g_string_erase(pinboard->input_buffer, 0, len + 1);
1732 command_from_backdrop_app(pinboard, command);
1734 g_free(command);
1738 static void reload_backdrop(Pinboard *pinboard,
1739 const gchar *backdrop,
1740 BackdropStyle backdrop_style)
1742 GtkStyle *style;
1744 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
1746 const char *argv[] = {NULL, "--backdrop", NULL};
1747 GError *error = NULL;
1749 g_return_if_fail(pinboard->to_backdrop_app == -1);
1750 g_return_if_fail(pinboard->from_backdrop_app == -1);
1751 g_return_if_fail(pinboard->input_tag == -1);
1752 g_return_if_fail(pinboard->input_buffer == NULL);
1754 argv[0] = make_path(backdrop, "AppRun")->str;
1756 /* Run the program. It'll send us a SOAP message and we'll
1757 * get back here with a different style and image.
1760 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
1761 G_SPAWN_DO_NOT_REAP_CHILD |
1762 G_SPAWN_SEARCH_PATH,
1763 NULL, NULL, /* Child setup fn */
1764 NULL, /* Child PID */
1765 &pinboard->to_backdrop_app,
1766 &pinboard->from_backdrop_app,
1767 NULL, /* Standard error */
1768 &error))
1770 pinboard->input_buffer = g_string_new(NULL);
1771 pinboard->input_tag = gtk_input_add_full(
1772 pinboard->from_backdrop_app,
1773 GDK_INPUT_READ,
1774 (GdkInputFunction) backdrop_from_child,
1775 NULL, pinboard, NULL);
1777 else
1779 delayed_error("%s", error ? error->message : "(null)");
1780 g_error_free(error);
1782 return;
1785 /* Note: Copying a style does not ref the pixmaps! */
1787 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
1788 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
1790 if (backdrop)
1791 style->bg_pixmap[GTK_STATE_NORMAL] =
1792 load_backdrop(backdrop, backdrop_style);
1794 gtk_widget_set_style(pinboard->window, style);
1796 g_object_unref(style);
1798 gtk_widget_queue_draw(pinboard->window);
1801 /* Set and save (path, style) as the new backdrop.
1802 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
1803 * Otherwise, the image is displayed now.
1805 static void set_backdrop(const gchar *path, BackdropStyle style)
1807 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
1808 (path != NULL && style != BACKDROP_NONE));
1810 if (!current_pinboard)
1812 if (!path)
1813 return;
1814 pinboard_activate("Default");
1815 delayed_error(_("No pinboard was in use... "
1816 "the 'Default' pinboard has been selected. "
1817 "Use 'rox -p=Default' to turn it on in "
1818 "future."));
1819 g_return_if_fail(current_pinboard != NULL);
1822 abandon_backdrop_app(current_pinboard);
1824 g_free(current_pinboard->backdrop);
1825 current_pinboard->backdrop = g_strdup(path);
1826 current_pinboard->backdrop_style = style;
1827 reload_backdrop(current_pinboard,
1828 current_pinboard->backdrop,
1829 current_pinboard->backdrop_style);
1831 pinboard_save();