r1482: Pinboard background colour option works again.
[rox-filer.git] / ROX-Filer / src / pinboard.c
blob28c5ea6e81196cac1d0342d1a913803e6ba5056c
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, o_forward_button_3;
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);
195 static void drag_backdrop_dropped(GtkWidget *frame,
196 GdkDragContext *context,
197 gint x,
198 gint y,
199 GtkSelectionData *selection_data,
200 guint drag_info,
201 guint32 time,
202 GtkWidget *dialog);
203 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
206 /****************************************************************
207 * EXTERNAL INTERFACE *
208 ****************************************************************/
210 void pinboard_init(void)
212 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
213 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
215 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
216 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
217 GRID_STEP_COARSE);
218 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
219 option_add_int(&o_forward_button_3, "pinboard_forward_button_3", FALSE);
221 option_add_notify(pinboard_check_options);
223 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
224 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
227 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
228 * and make it the current pinboard.
229 * Any existing pinned items are removed. You must call this
230 * at least once before using the pinboard. NULL disables the
231 * pinboard.
233 void pinboard_activate(const gchar *name)
235 Pinboard *old_board = current_pinboard;
236 guchar *path, *slash;
238 /* Treat an empty name the same as NULL */
239 if (name && !*name)
240 name = NULL;
242 if (old_board)
243 pinboard_clear();
245 if (!name)
247 if (number_of_windows < 1 && gtk_main_level() > 0)
248 gtk_main_quit();
249 return;
252 number_of_windows++;
254 slash = strchr(name, '/');
255 if (slash)
257 if (access(name, F_OK))
258 path = NULL; /* File does not (yet) exist */
259 else
260 path = g_strdup(name);
262 else
264 guchar *leaf;
266 leaf = g_strconcat("pb_", name, NULL);
267 path = choices_find_path_load(leaf, PROJECT);
268 g_free(leaf);
271 current_pinboard = g_new(Pinboard, 1);
272 current_pinboard->name = g_strdup(name);
273 current_pinboard->icons = NULL;
274 current_pinboard->window = NULL;
275 current_pinboard->backdrop = NULL;
276 current_pinboard->backdrop_style = BACKDROP_NONE;
277 current_pinboard->to_backdrop_app = -1;
278 current_pinboard->from_backdrop_app = -1;
279 current_pinboard->input_tag = -1;
280 current_pinboard->input_buffer = NULL;
282 create_pinboard_window(current_pinboard);
284 loading_pinboard++;
285 if (path)
287 xmlDocPtr doc;
288 doc = xmlParseFile(path);
289 if (doc)
291 pinboard_load_from_xml(doc);
292 xmlFreeDoc(doc);
293 reload_backdrop(current_pinboard,
294 current_pinboard->backdrop,
295 current_pinboard->backdrop_style);
297 else
299 parse_file(path, pin_from_file);
300 info_message(_("Your old pinboard file has been "
301 "converted to the new XML format."));
302 pinboard_save();
304 g_free(path);
306 else
307 pinboard_pin(home_dir, "Home",
308 4 + ICON_WIDTH / 2,
309 4 + ICON_HEIGHT / 2);
310 loading_pinboard--;
312 if (o_pinboard_tasklist.int_value)
313 tasklist_set_active(TRUE);
316 /* Return the window of the current pinboard, or NULL.
317 * Used to make sure lowering the panels doesn't lose them...
319 GdkWindow *pinboard_get_window(void)
321 if (current_pinboard)
322 return current_pinboard->window->window;
323 return NULL;
326 const char *pinboard_get_name(void)
328 g_return_val_if_fail(current_pinboard != NULL, NULL);
330 return current_pinboard->name;
333 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
334 * being cleared.
336 void pinboard_add_widget(GtkWidget *widget, int x, int y)
338 g_return_if_fail(current_pinboard != NULL);
340 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, x, y);
343 /* Add a new icon to the background.
344 * 'path' should be an absolute pathname.
345 * 'x' and 'y' are the coordinates of the point in the middle of the text.
346 * 'name' is the name to use. If NULL then the leafname of path is used.
348 * name and path are in UTF-8 for Gtk+-2.0 only.
350 void pinboard_pin(const gchar *path, const gchar *name, int x, int y)
352 GtkWidget *align, *vbox;
353 GdkWindow *events;
354 PinIcon *pi;
355 Icon *icon;
357 g_return_if_fail(path != NULL);
358 g_return_if_fail(current_pinboard != NULL);
360 pi = pin_icon_new(path, name);
361 icon = (Icon *) pi;
362 pi->x = x;
363 pi->y = y;
365 /* This is a bit complicated...
367 * An icon needs to be a NO_WINDOW widget so that the image can
368 * blend with the background (A ParentRelative window also works, but
369 * is slow, causes the xfree86's memory consumption to grow without
370 * bound, and doesn't even get freed when the filer quits!).
372 * However, the icon also needs to have a window, so we get events
373 * delivered correctly. The solution is to float an InputOnly window
374 * over the icon. Since GtkButton works the same way, we just use
375 * that :-)
378 /* Button takes the initial ref of Icon */
379 pi->win = gtk_button_new();
380 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
381 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
382 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
384 vbox = gtk_vbox_new(FALSE, 0);
385 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
387 align = gtk_alignment_new(0.5, 0.5, 0, 0);
388 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
389 gtk_container_add(GTK_CONTAINER(align), pi->widget);
391 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
392 drag_set_pinicon_dest(pi);
393 g_signal_connect(pi->win, "drag_data_get",
394 G_CALLBACK(drag_data_get), NULL);
396 pi->label = gtk_label_new(icon->item->leafname);
397 gtk_label_set_line_wrap(GTK_LABEL(pi->label), TRUE);
398 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
400 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
402 snap_to_grid(&x, &y);
403 pi->x = x;
404 pi->y = y;
405 gtk_widget_show_all(pi->win);
406 pinboard_reshape_icon((Icon *) pi);
408 gtk_widget_realize(pi->win);
409 events = GTK_BUTTON(pi->win)->event_window;
410 gdk_window_set_events(events,
411 GDK_EXPOSURE_MASK |
412 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
413 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
414 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
415 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
416 g_signal_connect(pi->win, "enter-notify-event",
417 G_CALLBACK(enter_notify), pi);
418 g_signal_connect(pi->win, "button-press-event",
419 G_CALLBACK(button_press_event), pi);
420 g_signal_connect(pi->win, "button-release-event",
421 G_CALLBACK(button_release_event), pi);
422 g_signal_connect(pi->win, "motion-notify-event",
423 G_CALLBACK(icon_motion_notify), pi);
424 g_signal_connect(pi->win, "expose-event",
425 G_CALLBACK(stop_expose), pi);
426 g_signal_connect(pi->widget, "expose-event",
427 G_CALLBACK(draw_icon), pi);
428 g_signal_connect_swapped(pi->win, "destroy",
429 G_CALLBACK(pin_icon_destroyed), pi);
431 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
432 pin_icon_set_tip(pi);
434 if (!loading_pinboard)
435 pinboard_save();
438 /* Remove an icon from the pinboard */
439 /* XXX: use destroy */
440 void pinboard_unpin(PinIcon *pi)
442 g_return_if_fail(pi != NULL);
444 gtk_widget_destroy(pi->win);
445 pinboard_save();
448 /* Put a border around the icon, briefly.
449 * If icon is NULL then cancel any existing wink.
450 * The icon will automatically unhighlight unless timeout is FALSE,
451 * in which case you must call this function again (with NULL or another
452 * icon) to remove the highlight.
454 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
456 PinIcon *old = current_wink_icon;
458 if (old == pi)
459 return;
461 current_wink_icon = pi;
463 if (old)
465 gtk_widget_queue_draw(old->win);
466 gdk_window_process_updates(old->widget->window, TRUE);
468 if (wink_timeout != -1)
469 gtk_timeout_remove(wink_timeout);
472 if (pi)
474 gtk_widget_queue_draw(pi->win);
475 gdk_window_process_updates(pi->widget->window, TRUE);
477 if (timeout)
478 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
479 else
480 wink_timeout = -1;
484 /* Icon's size, shape or appearance has changed - update the display */
485 void pinboard_reshape_icon(Icon *icon)
487 PinIcon *pi = (PinIcon *) icon;
488 int x = pi->x, y = pi->y;
490 set_size_and_style(pi);
491 offset_from_centre(pi, &x, &y);
493 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
495 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
496 pi->win, x, y);
500 /* 'app' is saved as the new application to set the backdrop. It will then be
501 * run, and should communicate with the filer as described in the manual.
503 void pinboard_set_backdrop_app(const gchar *app)
505 XMLwrapper *ai;
506 DirItem *item;
507 gboolean can_set;
509 item = diritem_new("");
510 diritem_restat(app, item, NULL);
511 ai = appinfo_get(app, item);
512 diritem_free(item);
514 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
515 if (ai)
516 g_object_unref(ai);
518 if (can_set)
519 set_backdrop(app, BACKDROP_PROGRAM);
520 else
521 delayed_error(_("You can only set the backdrop to an image "
522 "or to a program which knows how to "
523 "manage ROX-Filer's backdrop."));
526 /* Open a dialog box allowing the user to set the backdrop */
527 void pinboard_set_backdrop(void)
529 GtkWidget *dialog, *frame, *label, *radio, *hbox;
530 GtkBox *vbox;
531 GtkTargetEntry targets[] = {
532 {"text/uri-list", 0, TARGET_URI_LIST},
535 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
536 GTK_DIALOG_NO_SEPARATOR,
537 GTK_STOCK_CLEAR, GTK_RESPONSE_NO,
538 GTK_STOCK_OK, GTK_RESPONSE_OK,
539 NULL);
540 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
542 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
544 label = gtk_label_new("Display backdrop image:");
545 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
546 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
547 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
549 /* The Centred, Scaled, Tiled radios... */
550 hbox = gtk_hbox_new(TRUE, 2);
551 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
553 radio = gtk_radio_button_new_with_label(NULL, _("Centred"));
554 g_object_set_data(G_OBJECT(dialog), "radio_centred", radio);
555 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
557 radio = gtk_radio_button_new_with_label(
558 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
559 _("Scaled"));
560 g_object_set_data(G_OBJECT(dialog), "radio_scaled", radio);
561 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
563 radio = gtk_radio_button_new_with_label(
564 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
565 _("Tiled"));
566 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
568 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
570 /* The drop area... */
571 frame = gtk_frame_new(NULL);
572 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
573 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
574 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
576 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
577 targets, sizeof(targets) / sizeof(*targets),
578 GDK_ACTION_COPY);
579 g_signal_connect(frame, "drag_data_received",
580 G_CALLBACK(drag_backdrop_dropped), dialog);
582 label = gtk_label_new(_("Drop an image here"));
583 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
584 gtk_container_add(GTK_CONTAINER(frame), label);
586 g_signal_connect(dialog, "response",
587 G_CALLBACK(backdrop_response), NULL);
588 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
590 gtk_widget_show_all(dialog);
593 /****************************************************************
594 * INTERNAL FUNCTIONS *
595 ****************************************************************/
597 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
599 if (response == GTK_RESPONSE_NO)
600 set_backdrop(NULL, BACKDROP_NONE);
602 gtk_widget_destroy(dialog);
605 static void drag_backdrop_dropped(GtkWidget *frame,
606 GdkDragContext *context,
607 gint x,
608 gint y,
609 GtkSelectionData *selection_data,
610 guint drag_info,
611 guint32 time,
612 GtkWidget *dialog)
614 struct stat info;
615 const gchar *path = NULL;
616 GList *uris;
618 if (!selection_data->data)
619 return; /* Timeout? */
621 uris = uri_list_to_glist(selection_data->data);
623 if (g_list_length(uris) == 1)
624 path = get_local_path((guchar *) uris->data);
625 g_list_free(uris);
627 if (!path)
629 delayed_error(
630 _("You should drop a single (local) image file "
631 "onto the drop box - that image will be "
632 "used for the desktop background. You can also "
633 "drag certain applications onto this box."));
634 return;
637 if (mc_stat(path, &info))
639 delayed_error(
640 _("Can't access '%s':\n%s"), path,
641 g_strerror(errno));
642 return;
645 if (S_ISDIR(info.st_mode))
647 /* Use this program to set the backdrop */
648 pinboard_set_backdrop_app(path);
650 else if (S_ISREG(info.st_mode))
652 GtkWidget *radio;
654 radio = g_object_get_data(G_OBJECT(dialog), "radio_scaled");
655 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
657 set_backdrop(path, BACKDROP_SCALE);
658 return;
661 radio = g_object_get_data(G_OBJECT(dialog), "radio_centred");
662 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
664 set_backdrop(path, BACKDROP_CENTRE);
665 return;
668 set_backdrop(path, BACKDROP_TILE);
670 else
671 delayed_error(_("Only files (and certain applications) can be "
672 "used to set the background image."));
675 static void pinboard_check_options(void)
677 GdkColor n_fg, n_bg;
679 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
680 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
682 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
684 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
685 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0)
687 pin_text_fg_col = n_fg;
688 pin_text_bg_col = n_bg;
690 if (pinicon_style)
692 g_object_unref(G_OBJECT(pinicon_style));
693 pinicon_style = NULL;
696 if (current_pinboard)
698 GtkWidget *w = current_pinboard->window;
699 GdkColormap *cm;
701 cm = gtk_widget_get_colormap(w);
703 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
704 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
705 gdk_window_set_background(w->window, &n_bg);
707 gtk_widget_queue_draw(w);
708 reshape_all();
711 tasklist_style_changed();
715 static gint end_wink(gpointer data)
717 pinboard_wink_item(NULL, FALSE);
718 return FALSE;
721 /* Updates the width, height, name_width and label fields, and resizes the
722 * window. Also sets the style to pinicon_style, generating it if needed.
724 static void set_size_and_style(PinIcon *pi)
726 Icon *icon = (Icon *) pi;
727 MaskedPixmap *image = icon->item->image;
728 int iwidth = image->width;
729 int iheight = image->height;
731 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
732 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
733 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
734 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
736 gtk_label_set_text(GTK_LABEL(pi->label), icon->item->leafname);
738 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
741 /* Don't draw the normal button effect */
742 static gint stop_expose(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
744 static GtkWidgetClass *parent_class = NULL;
746 if (!parent_class)
748 gpointer c = ((GTypeInstance *) widget)->g_class;
749 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
752 (parent_class->expose_event)(widget, event);
753 return TRUE;
756 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
758 Icon *icon = (Icon *) pi;
759 DirItem *item = icon->item;
760 MaskedPixmap *image = item->image;
761 int iwidth = image->width;
762 int iheight = image->height;
763 int x, y;
764 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
765 : GTK_STATE_NORMAL;
768 x = widget->allocation.x;
769 y = widget->allocation.y;
771 gdk_pixbuf_render_to_drawable_alpha(
772 icon->selected ? image->pixbuf_lit : image->pixbuf,
773 widget->window,
774 0, 0, /* src */
775 x, y, /* dest */
776 iwidth, iheight,
777 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
778 GDK_RGB_DITHER_NORMAL, 0, 0);
780 if (item->flags & ITEM_FLAG_SYMLINK)
782 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
783 widget->window,
784 0, 0, /* src */
785 x, y,
786 -1, -1,
787 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
788 GDK_RGB_DITHER_NORMAL, 0, 0);
790 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
792 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
793 ? im_mounted
794 : im_unmounted;
796 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
797 widget->window,
798 0, 0, /* src */
799 x, y,
800 -1, -1,
801 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
802 GDK_RGB_DITHER_NORMAL, 0, 0);
805 if (icon->selected)
807 gtk_paint_flat_box(pi->label->style, pi->label->window,
808 state,
809 GTK_SHADOW_NONE,
810 NULL, pi->label, "text",
811 pi->label->allocation.x,
812 pi->label->allocation.y,
813 pi->label->allocation.width,
814 pi->label->allocation.height);
817 return FALSE;
820 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
822 gint x, y, width, height;
824 if (current_wink_icon != pi)
825 return FALSE;
827 x = widget->allocation.x;
828 y = widget->allocation.y;
829 width = widget->allocation.width;
830 height = widget->allocation.height;
832 gdk_draw_rectangle(widget->window,
833 pi->widget->style->white_gc,
834 FALSE,
835 x, y, width - 1, height - 1);
836 gdk_draw_rectangle(widget->window,
837 pi->widget->style->black_gc,
838 FALSE,
839 x + 1, y + 1, width - 3, height - 3);
841 return FALSE;
844 static gboolean enter_notify(GtkWidget *widget,
845 GdkEventCrossing *event,
846 PinIcon *pi)
848 icon_may_update((Icon *) pi);
850 return FALSE;
853 static void perform_action(PinIcon *pi, GdkEventButton *event)
855 BindAction action;
856 Icon *icon = (Icon *) pi;
858 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
859 event);
861 /* Actions that can happen with or without an icon */
862 switch (action)
864 case ACT_CLEAR_SELECTION:
865 icon_select_only(NULL);
866 return;
867 case ACT_POPUP_MENU:
868 dnd_motion_ungrab();
869 pinboard_show_menu(event, pi);
870 return;
871 case ACT_IGNORE:
872 return;
873 default:
874 break;
877 g_return_if_fail(pi != NULL);
879 switch (action)
881 case ACT_OPEN_ITEM:
882 dnd_motion_ungrab();
883 pinboard_wink_item(pi, TRUE);
884 if (event->type == GDK_2BUTTON_PRESS)
885 icon_set_selected(icon, FALSE);
886 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
887 break;
888 case ACT_EDIT_ITEM:
889 dnd_motion_ungrab();
890 pinboard_wink_item(pi, TRUE);
891 if (event->type == GDK_2BUTTON_PRESS)
892 icon_set_selected(icon, FALSE);
893 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
894 break;
895 case ACT_PRIME_AND_SELECT:
896 if (!icon->selected)
897 icon_select_only(icon);
898 dnd_motion_start(MOTION_READY_FOR_DND);
899 break;
900 case ACT_PRIME_AND_TOGGLE:
901 icon_set_selected(icon, !icon->selected);
902 dnd_motion_start(MOTION_READY_FOR_DND);
903 break;
904 case ACT_PRIME_FOR_DND:
905 dnd_motion_start(MOTION_READY_FOR_DND);
906 break;
907 case ACT_TOGGLE_SELECTED:
908 icon_set_selected(icon, !icon->selected);
909 break;
910 case ACT_SELECT_EXCL:
911 icon_select_only(icon);
912 break;
913 default:
914 g_warning("Unsupported action : %d\n", action);
915 break;
919 static void forward_to_root(GdkEventButton *event)
921 XButtonEvent xev;
923 if (event->type == GDK_BUTTON_PRESS)
925 xev.type = ButtonPress;
926 XUngrabPointer(gdk_display, event->time);
928 else
929 xev.type = ButtonRelease;
931 xev.window = gdk_x11_get_default_root_xwindow();
932 xev.root = xev.window;
933 xev.subwindow = None;
934 xev.time = event->time;
935 xev.x = event->x;
936 xev.y = event->y;
937 xev.x_root = event->x_root;
938 xev.y_root = event->y_root;
939 xev.state = event->state;
940 xev.button = event->button;
941 xev.same_screen = True;
943 XSendEvent(gdk_display, xev.window, False,
944 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
947 #define FORWARDED_BUTTON(pi, button) ((button) == 2 || \
948 ((button) == 3 && o_forward_button_3.int_value && !pi))
950 /* pi is NULL if this is a root event */
951 static gboolean button_release_event(GtkWidget *widget,
952 GdkEventButton *event,
953 PinIcon *pi)
955 if (FORWARDED_BUTTON(pi, event->button))
956 forward_to_root(event);
957 else if (dnd_motion_release(event))
958 return TRUE;
960 perform_action(pi, event);
962 return TRUE;
965 /* pi is NULL if this is a root event */
966 static gboolean button_press_event(GtkWidget *widget,
967 GdkEventButton *event,
968 PinIcon *pi)
970 /* Just in case we've jumped in front of everything... */
971 gdk_window_lower(current_pinboard->window->window);
973 if (FORWARDED_BUTTON(pi, event->button))
974 forward_to_root(event);
975 else if (dnd_motion_press(widget, event))
976 perform_action(pi, event);
978 return TRUE;
981 static void start_drag(PinIcon *pi, GdkEventMotion *event)
983 GtkWidget *widget = pi->win;
984 Icon *icon = (Icon *) pi;
986 if (!icon->selected)
988 tmp_icon_selected = TRUE;
989 icon_select_only(icon);
992 g_return_if_fail(icon_selection != NULL);
994 pinboard_drag_in_progress = icon;
996 if (icon_selection->next == NULL)
997 drag_one_item(widget, event, icon->path, icon->item, NULL);
998 else
1000 guchar *uri_list;
1002 uri_list = icon_create_uri_list();
1003 drag_selection(widget, event, uri_list);
1004 g_free(uri_list);
1008 /* An icon is being dragged around... */
1009 static gint icon_motion_notify(GtkWidget *widget,
1010 GdkEventMotion *event,
1011 PinIcon *pi)
1013 if (motion_state == MOTION_READY_FOR_DND)
1015 if (dnd_motion_moved(event))
1016 start_drag(pi, event);
1017 return TRUE;
1020 return FALSE;
1023 static void backdrop_from_xml(xmlNode *node)
1025 gchar *style;
1027 g_free(current_pinboard->backdrop);
1028 current_pinboard->backdrop = xmlNodeGetContent(node);
1030 style = xmlGetProp(node, "style");
1032 if (style)
1034 current_pinboard->backdrop_style =
1035 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1036 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1037 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1038 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1039 BACKDROP_NONE;
1040 g_free(style);
1042 else
1043 current_pinboard->backdrop_style = BACKDROP_TILE;
1046 /* Create one pinboard icon for each icon in the doc */
1047 static void pinboard_load_from_xml(xmlDocPtr doc)
1049 xmlNodePtr node, root;
1050 char *tmp, *label, *path;
1051 int x, y;
1053 root = xmlDocGetRootElement(doc);
1055 for (node = root->xmlChildrenNode; node; node = node->next)
1057 if (node->type != XML_ELEMENT_NODE)
1058 continue;
1059 if (strcmp(node->name, "backdrop") == 0)
1061 backdrop_from_xml(node);
1062 continue;
1064 if (strcmp(node->name, "icon") != 0)
1065 continue;
1067 tmp = xmlGetProp(node, "x");
1068 if (!tmp)
1069 continue;
1070 x = atoi(tmp);
1071 g_free(tmp);
1073 tmp = xmlGetProp(node, "y");
1074 if (!tmp)
1075 continue;
1076 y = atoi(tmp);
1077 g_free(tmp);
1079 label = xmlGetProp(node, "label");
1080 if (!label)
1081 label = g_strdup("<missing label>");
1082 path = xmlNodeGetContent(node);
1083 if (!path)
1084 path = g_strdup("<missing path>");
1086 pinboard_pin(path, label, x, y);
1088 g_free(path);
1089 g_free(label);
1093 /* Called for each line in the pinboard file while loading a new board.
1094 * Only used for old-format files when converting to XML.
1096 static const char *pin_from_file(gchar *line)
1098 gchar *leaf = NULL;
1099 int x, y, n;
1101 if (*line == '<')
1103 gchar *end;
1105 end = strchr(line + 1, '>');
1106 if (!end)
1107 return _("Missing '>' in icon label");
1109 leaf = g_strndup(line + 1, end - line - 1);
1111 line = end + 1;
1113 while (isspace(*line))
1114 line++;
1115 if (*line != ',')
1116 return _("Missing ',' after icon label");
1117 line++;
1120 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1121 return NULL; /* Ignore format errors */
1123 pinboard_pin(line + n, leaf, x, y);
1125 g_free(leaf);
1127 return NULL;
1130 /* Write the current state of the pinboard to the current pinboard file */
1131 static void pinboard_save(void)
1133 guchar *save = NULL;
1134 guchar *save_new = NULL;
1135 GList *next;
1136 xmlDocPtr doc = NULL;
1137 xmlNodePtr root;
1139 g_return_if_fail(current_pinboard != NULL);
1141 if (strchr(current_pinboard->name, '/'))
1142 save = g_strdup(current_pinboard->name);
1143 else
1145 guchar *leaf;
1147 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1148 save = choices_find_path_save(leaf, PROJECT, TRUE);
1149 g_free(leaf);
1152 if (!save)
1153 return;
1155 doc = xmlNewDoc("1.0");
1156 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1158 root = xmlDocGetRootElement(doc);
1160 if (current_pinboard->backdrop)
1162 BackdropStyle style = current_pinboard->backdrop_style;
1163 xmlNodePtr tree;
1165 tree = xmlNewTextChild(root, NULL, "backdrop",
1166 current_pinboard->backdrop);
1167 xmlSetProp(tree, "style",
1168 style == BACKDROP_TILE ? "Tiled" :
1169 style == BACKDROP_CENTRE ? "Centred" :
1170 style == BACKDROP_SCALE ? "Scaled" :
1171 "Program");
1174 for (next = current_pinboard->icons; next; next = next->next)
1176 xmlNodePtr tree;
1177 PinIcon *pi = (PinIcon *) next->data;
1178 Icon *icon = (Icon *) pi;
1179 char *tmp;
1181 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1183 tmp = g_strdup_printf("%d", pi->x);
1184 xmlSetProp(tree, "x", tmp);
1185 g_free(tmp);
1187 tmp = g_strdup_printf("%d", pi->y);
1188 xmlSetProp(tree, "y", tmp);
1189 g_free(tmp);
1191 xmlSetProp(tree, "label", icon->item->leafname);
1194 save_new = g_strconcat(save, ".new", NULL);
1195 if (save_xml_file(doc, save_new) || rename(save_new, save))
1196 delayed_error(_("Error saving pinboard %s: %s"),
1197 save, g_strerror(errno));
1198 g_free(save_new);
1200 g_free(save);
1201 if (doc)
1202 xmlFreeDoc(doc);
1205 static void snap_to_grid(int *x, int *y)
1207 int step = o_pinboard_grid_step.int_value;
1209 *x = ((*x + step / 2) / step) * step;
1210 *y = ((*y + step / 2) / step) * step;
1213 /* Convert (x,y) from a centre point to a window position */
1214 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1216 gboolean clamp = o_pinboard_clamp_icons.int_value;
1217 GtkRequisition req;
1219 gtk_widget_size_request(pi->win, &req);
1221 *x -= req.width >> 1;
1222 *y -= req.height >> 1;
1223 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1224 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1227 /* Same as drag_set_dest(), but for pinboard icons */
1228 static void drag_set_pinicon_dest(PinIcon *pi)
1230 GtkObject *obj = GTK_OBJECT(pi->win);
1232 make_drop_target(pi->win, 0);
1234 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1235 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1236 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1239 /* Called during the drag when the mouse is in a widget registered
1240 * as a drop target. Returns TRUE if we can accept the drop.
1242 static gboolean drag_motion(GtkWidget *widget,
1243 GdkDragContext *context,
1244 gint x,
1245 gint y,
1246 guint time,
1247 PinIcon *pi)
1249 GdkDragAction action = context->suggested_action;
1250 char *type = NULL;
1251 Icon *icon = (Icon *) pi;
1252 DirItem *item = icon->item;
1254 if (gtk_drag_get_source_widget(context) == widget)
1255 goto out; /* Can't drag something to itself! */
1257 if (icon->selected)
1258 goto out; /* Can't drag a selection to itself */
1260 type = dnd_motion_item(context, &item);
1262 if (!item)
1263 type = NULL;
1264 out:
1265 /* We actually must pretend to accept the drop, even if the
1266 * directory isn't writeable, so that the spring-opening
1267 * thing works.
1270 /* Don't allow drops to non-writeable directories */
1271 if (o_dnd_spring_open.int_value == FALSE &&
1272 type == drop_dest_dir &&
1273 access(icon->path, W_OK) != 0)
1275 type = NULL;
1278 g_dataset_set_data(context, "drop_dest_type", type);
1279 if (type)
1281 gdk_drag_status(context, action, time);
1282 g_dataset_set_data_full(context, "drop_dest_path",
1283 g_strdup(icon->path), g_free);
1284 if (type == drop_dest_dir)
1285 dnd_spring_load(context, NULL);
1287 pinboard_wink_item(pi, FALSE);
1289 else
1290 gdk_drag_status(context, 0, time);
1292 /* Always return TRUE to stop the pinboard getting the events */
1293 return TRUE;
1296 static gboolean pinboard_shadow = FALSE;
1297 static gint shadow_x, shadow_y;
1298 #define SHADOW_SIZE (ICON_WIDTH)
1300 static gboolean bg_expose(GtkWidget *widget,
1301 GdkEventExpose *event, gpointer data)
1303 if (!pinboard_shadow)
1304 return FALSE;
1306 gdk_draw_rectangle(widget->window,
1307 widget->style->white_gc, FALSE,
1308 shadow_x, shadow_y,
1309 SHADOW_SIZE, SHADOW_SIZE);
1310 gdk_draw_rectangle(widget->window,
1311 widget->style->black_gc, FALSE,
1312 shadow_x + 1, shadow_y + 1,
1313 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1315 return FALSE;
1318 /* Draw a 'shadow' under an icon being dragged, showing where
1319 * it will land.
1321 static void pinboard_set_shadow(gboolean on)
1323 GdkRectangle area;
1325 if (pinboard_shadow)
1327 area.x = shadow_x;
1328 area.y = shadow_y;
1329 area.width = SHADOW_SIZE + 1;
1330 area.height = SHADOW_SIZE + 1;
1332 gdk_window_invalidate_rect(current_pinboard->window->window,
1333 &area, TRUE);
1336 if (on)
1338 int old_x = shadow_x, old_y = shadow_y;
1340 gdk_window_get_pointer(current_pinboard->fixed->window,
1341 &shadow_x, &shadow_y, NULL);
1342 snap_to_grid(&shadow_x, &shadow_y);
1343 shadow_x -= SHADOW_SIZE / 2;
1344 shadow_y -= SHADOW_SIZE / 2;
1347 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1348 return;
1350 area.x = shadow_x;
1351 area.y = shadow_y;
1352 area.width = SHADOW_SIZE + 1;
1353 area.height = SHADOW_SIZE + 1;
1355 gdk_window_invalidate_rect(current_pinboard->window->window,
1356 &area, TRUE);
1359 pinboard_shadow = on;
1362 /* Called when dragging some pinboard icons finishes */
1363 void pinboard_move_icons(void)
1365 int x = shadow_x, y = shadow_y;
1366 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1367 int width, height;
1369 g_return_if_fail(pi != NULL);
1371 x += SHADOW_SIZE / 2;
1372 y += SHADOW_SIZE / 2;
1373 snap_to_grid(&x, &y);
1375 if (pi->x == x && pi->y == y)
1376 return;
1378 pi->x = x;
1379 pi->y = y;
1380 gdk_drawable_get_size(pi->win->window, &width, &height);
1381 offset_from_centre(pi, &x, &y);
1383 fixed_move_fast(GTK_FIXED(current_pinboard->fixed), pi->win, x, y);
1385 pinboard_save();
1388 static void drag_leave(GtkWidget *widget,
1389 GdkDragContext *context,
1390 guint32 time,
1391 PinIcon *pi)
1393 pinboard_wink_item(NULL, FALSE);
1394 dnd_spring_abort();
1397 static gboolean bg_drag_leave(GtkWidget *widget,
1398 GdkDragContext *context,
1399 guint32 time,
1400 gpointer data)
1402 pinboard_set_shadow(FALSE);
1403 return TRUE;
1406 static gboolean bg_drag_motion(GtkWidget *widget,
1407 GdkDragContext *context,
1408 gint x,
1409 gint y,
1410 guint time,
1411 gpointer data)
1413 /* Dragging from the pinboard to the pinboard is not allowed */
1415 if (!provides(context, text_uri_list))
1416 return FALSE;
1418 pinboard_set_shadow(TRUE);
1420 gdk_drag_status(context,
1421 context->suggested_action == GDK_ACTION_ASK
1422 ? GDK_ACTION_LINK : context->suggested_action,
1423 time);
1424 return TRUE;
1427 static void drag_end(GtkWidget *widget,
1428 GdkDragContext *context,
1429 PinIcon *pi)
1431 pinboard_drag_in_progress = NULL;
1432 if (tmp_icon_selected)
1434 icon_select_only(NULL);
1435 tmp_icon_selected = FALSE;
1439 /* Something which affects all the icons has changed - reshape
1440 * and redraw all of them.
1442 static void reshape_all(void)
1444 GList *next;
1446 g_return_if_fail(current_pinboard != NULL);
1448 for (next = current_pinboard->icons; next; next = next->next)
1450 Icon *icon = (Icon *) next->data;
1451 pinboard_reshape_icon(icon);
1455 /* Turns off the pinboard. Does not call gtk_main_quit. */
1456 static void pinboard_clear(void)
1458 GList *next;
1460 g_return_if_fail(current_pinboard != NULL);
1462 tasklist_set_active(FALSE);
1464 next = current_pinboard->icons;
1465 while (next)
1467 PinIcon *pi = (PinIcon *) next->data;
1469 next = next->next;
1471 gtk_widget_destroy(pi->win);
1474 gtk_widget_destroy(current_pinboard->window);
1476 abandon_backdrop_app(current_pinboard);
1478 g_free(current_pinboard->name);
1479 g_free(current_pinboard);
1480 current_pinboard = NULL;
1482 number_of_windows--;
1485 static gpointer parent_class;
1487 static void pin_icon_destroy(Icon *icon)
1489 PinIcon *pi = (PinIcon *) icon;
1491 g_return_if_fail(pi->win != NULL);
1493 gtk_widget_destroy(pi->win);
1496 static void pinboard_remove_items(void)
1498 g_return_if_fail(icon_selection != NULL);
1500 while (icon_selection)
1501 icon_destroy((Icon *) icon_selection->data);
1503 pinboard_save();
1506 static void pin_icon_update(Icon *icon)
1508 pinboard_reshape_icon(icon);
1509 pinboard_save();
1512 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1514 return IS_PIN_ICON(other);
1517 static void pin_icon_class_init(gpointer gclass, gpointer data)
1519 IconClass *icon = (IconClass *) gclass;
1521 parent_class = g_type_class_peek_parent(gclass);
1523 icon->destroy = pin_icon_destroy;
1524 icon->redraw = pinboard_reshape_icon;
1525 icon->update = pin_icon_update;
1526 icon->remove_items = pinboard_remove_items;
1527 icon->same_group = pin_icon_same_group;
1530 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1534 static GType pin_icon_get_type(void)
1536 static GType type = 0;
1538 if (!type)
1540 static const GTypeInfo info =
1542 sizeof (PinIconClass),
1543 NULL, /* base_init */
1544 NULL, /* base_finalise */
1545 pin_icon_class_init,
1546 NULL, /* class_finalise */
1547 NULL, /* class_data */
1548 sizeof(PinIcon),
1549 0, /* n_preallocs */
1550 pin_icon_init
1553 type = g_type_register_static(icon_get_type(),
1554 "PinIcon", &info, 0);
1557 return type;
1560 static PinIcon *pin_icon_new(const char *pathname, const char *name)
1562 PinIcon *pi;
1563 Icon *icon;
1565 pi = g_object_new(pin_icon_get_type(), NULL);
1566 icon = (Icon *) pi;
1568 icon_set_path(icon, pathname, name);
1570 return pi;
1573 /* Called when the window widget is somehow destroyed */
1574 static void pin_icon_destroyed(PinIcon *pi)
1576 g_return_if_fail(pi->win != NULL);
1578 pi->win = NULL;
1580 pinboard_wink_item(NULL, FALSE);
1582 if (pinboard_drag_in_progress == (Icon *) pi)
1583 pinboard_drag_in_progress = NULL;
1585 if (current_pinboard)
1586 current_pinboard->icons =
1587 g_list_remove(current_pinboard->icons, pi);
1589 g_object_unref(pi);
1592 /* Set the tooltip */
1593 static void pin_icon_set_tip(PinIcon *pi)
1595 XMLwrapper *ai;
1596 xmlNode *node;
1597 Icon *icon = (Icon *) pi;
1599 g_return_if_fail(pi != NULL);
1601 ai = appinfo_get(icon->path, icon->item);
1603 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
1605 guchar *str;
1606 str = xmlNodeListGetString(node->doc,
1607 node->xmlChildrenNode, 1);
1608 if (str)
1610 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
1611 g_free(str);
1614 else
1615 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
1617 if (ai)
1618 g_object_unref(ai);
1621 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
1623 int pos[3];
1625 pos[0] = event->x_root;
1626 pos[1] = event->y_root;
1627 pos[2] = 1;
1629 icon_prepare_menu((Icon *) pi, TRUE);
1631 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
1632 position_menu,
1633 (gpointer) pos, event->button, event->time);
1636 static void create_pinboard_window(Pinboard *pinboard)
1638 GtkWidget *win;
1640 g_return_if_fail(pinboard->window == NULL);
1642 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1644 gtk_widget_modify_bg(win, GTK_STATE_NORMAL, &pin_text_bg_col);
1646 gtk_widget_set_app_paintable(win, TRUE);
1647 gtk_widget_set_name(win, "rox-pinboard");
1648 pinboard->window = win;
1649 pinboard->fixed = gtk_fixed_new();
1650 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
1652 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
1654 gtk_widget_set_size_request(win, screen_width, screen_height);
1655 gtk_widget_realize(win);
1656 gtk_window_move(GTK_WINDOW(win), 0, 0);
1657 make_panel_window(win);
1659 /* TODO: Use gdk function when it supports this type */
1661 GdkAtom desktop_type;
1663 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1664 FALSE);
1665 gdk_property_change(win->window,
1666 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
1667 gdk_atom_intern("ATOM", FALSE), 32,
1668 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
1671 gtk_widget_add_events(win,
1672 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1673 GDK_EXPOSURE_MASK);
1674 g_signal_connect(win, "button-press-event",
1675 G_CALLBACK(button_press_event), NULL);
1676 g_signal_connect(win, "button-release-event",
1677 G_CALLBACK(button_release_event), NULL);
1678 g_signal_connect(pinboard->fixed, "expose_event",
1679 G_CALLBACK(bg_expose), NULL);
1681 /* Drag and drop handlers */
1682 drag_set_pinboard_dest(win);
1683 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
1684 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
1686 gtk_widget_show_all(win);
1687 gdk_window_lower(win->window);
1690 /* Load image 'path' and scale according to 'style' */
1691 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
1693 GdkPixmap *pixmap;
1694 GdkPixbuf *pixbuf;
1695 GError *error = NULL;
1697 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1698 if (error)
1700 delayed_error(_("Error loading backdrop image:\n%s\n"
1701 "Backdrop removed."),
1702 error->message);
1703 g_error_free(error);
1704 set_backdrop(NULL, BACKDROP_NONE);
1705 return NULL;
1708 if (style == BACKDROP_SCALE)
1710 GdkPixbuf *old = pixbuf;
1712 pixbuf = gdk_pixbuf_scale_simple(old,
1713 screen_width, screen_height,
1714 GDK_INTERP_HYPER);
1716 g_object_unref(old);
1718 else if (style == BACKDROP_CENTRE)
1720 GdkPixbuf *old = pixbuf;
1721 int x, y, width, height;
1723 width = gdk_pixbuf_get_width(pixbuf);
1724 height = gdk_pixbuf_get_height(pixbuf);
1726 pixbuf = gdk_pixbuf_new(
1727 gdk_pixbuf_get_colorspace(pixbuf), 0,
1728 8, screen_width, screen_height);
1729 gdk_pixbuf_fill(pixbuf, 0);
1731 x = (screen_width - width) / 2;
1732 y = (screen_height - height) / 2;
1733 x = MAX(x, 0);
1734 y = MAX(y, 0);
1736 gdk_pixbuf_composite(old, pixbuf,
1737 x, y,
1738 MIN(screen_width, width),
1739 MIN(screen_height, height),
1740 x, y, 1, 1,
1741 GDK_INTERP_NEAREST, 255);
1742 g_object_unref(old);
1745 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
1746 &pixmap, NULL, 0);
1747 g_object_unref(pixbuf);
1749 return pixmap;
1752 static void abandon_backdrop_app(Pinboard *pinboard)
1754 g_return_if_fail(pinboard != NULL);
1756 if (pinboard->to_backdrop_app != -1)
1758 close(pinboard->to_backdrop_app);
1759 close(pinboard->from_backdrop_app);
1760 gtk_input_remove(pinboard->input_tag);
1761 g_string_free(pinboard->input_buffer, TRUE);
1762 pinboard->to_backdrop_app = -1;
1763 pinboard->from_backdrop_app = -1;
1764 pinboard->input_tag = -1;
1765 pinboard->input_buffer = NULL;
1768 g_return_if_fail(pinboard->to_backdrop_app == -1);
1769 g_return_if_fail(pinboard->from_backdrop_app == -1);
1770 g_return_if_fail(pinboard->input_tag == -1);
1771 g_return_if_fail(pinboard->input_buffer == NULL);
1774 /* A single line has been read from the child.
1775 * Processes the command, and replies 'ok' (or abandons the child on error).
1777 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
1779 BackdropStyle style;
1780 const char *ok = "ok\n";
1782 if (strncmp(command, "tile ", 5) == 0)
1784 style = BACKDROP_TILE;
1785 command += 5;
1787 else if (strncmp(command, "scale ", 6) == 0)
1789 style = BACKDROP_SCALE;
1790 command += 6;
1792 else if (strncmp(command, "centre ", 7) == 0)
1794 style = BACKDROP_CENTRE;
1795 command += 7;
1797 else
1799 g_warning("Invalid command '%s' from backdrop app\n",
1800 command);
1801 abandon_backdrop_app(pinboard);
1802 return;
1805 reload_backdrop(pinboard, command, style);
1807 while (*ok)
1809 int sent;
1811 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
1812 if (sent <= 0)
1814 g_warning("command_from_backdrop_app: %s\n",
1815 g_strerror(errno));
1816 abandon_backdrop_app(pinboard);
1817 return;
1819 ok += sent;
1823 static void backdrop_from_child(Pinboard *pinboard,
1824 int src, GdkInputCondition cond)
1826 char buf[256];
1827 int got;
1829 got = read(src, buf, sizeof(buf));
1831 if (got <= 0)
1833 if (got < 0)
1834 g_warning("backdrop_from_child: %s\n",
1835 g_strerror(errno));
1836 abandon_backdrop_app(pinboard);
1837 return;
1840 g_string_append_len(pinboard->input_buffer, buf, got);
1842 while (pinboard->from_backdrop_app != -1)
1844 int len;
1845 char *nl, *command;
1847 nl = strchr(pinboard->input_buffer->str, '\n');
1848 if (!nl)
1849 return; /* Haven't got a whole line yet */
1851 len = nl - pinboard->input_buffer->str;
1852 command = g_strndup(pinboard->input_buffer->str, len);
1853 g_string_erase(pinboard->input_buffer, 0, len + 1);
1855 command_from_backdrop_app(pinboard, command);
1857 g_free(command);
1861 static void reload_backdrop(Pinboard *pinboard,
1862 const gchar *backdrop,
1863 BackdropStyle backdrop_style)
1865 GtkStyle *style;
1867 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
1869 const char *argv[] = {NULL, "--backdrop", NULL};
1870 GError *error = NULL;
1872 g_return_if_fail(pinboard->to_backdrop_app == -1);
1873 g_return_if_fail(pinboard->from_backdrop_app == -1);
1874 g_return_if_fail(pinboard->input_tag == -1);
1875 g_return_if_fail(pinboard->input_buffer == NULL);
1877 argv[0] = make_path(backdrop, "AppRun")->str;
1879 /* Run the program. It'll send us a SOAP message and we'll
1880 * get back here with a different style and image.
1883 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
1884 G_SPAWN_DO_NOT_REAP_CHILD |
1885 G_SPAWN_SEARCH_PATH,
1886 NULL, NULL, /* Child setup fn */
1887 NULL, /* Child PID */
1888 &pinboard->to_backdrop_app,
1889 &pinboard->from_backdrop_app,
1890 NULL, /* Standard error */
1891 &error))
1893 pinboard->input_buffer = g_string_new(NULL);
1894 pinboard->input_tag = gtk_input_add_full(
1895 pinboard->from_backdrop_app,
1896 GDK_INPUT_READ,
1897 (GdkInputFunction) backdrop_from_child,
1898 NULL, pinboard, NULL);
1900 else
1902 delayed_error("%s", error ? error->message : "(null)");
1903 g_error_free(error);
1905 return;
1908 /* Note: Copying a style does not ref the pixmaps! */
1910 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
1911 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
1913 if (backdrop)
1914 style->bg_pixmap[GTK_STATE_NORMAL] =
1915 load_backdrop(backdrop, backdrop_style);
1917 gtk_widget_set_style(pinboard->window, style);
1919 g_object_unref(style);
1921 gtk_widget_queue_draw(pinboard->window);
1924 /* Set and save (path, style) as the new backdrop.
1925 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
1926 * Otherwise, the image is displayed now.
1928 static void set_backdrop(const gchar *path, BackdropStyle style)
1930 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
1931 (path != NULL && style != BACKDROP_NONE));
1933 if (!current_pinboard)
1935 if (!path)
1936 return;
1937 pinboard_activate("Default");
1938 delayed_error(_("No pinboard was in use... "
1939 "the 'Default' pinboard has been selected. "
1940 "Use 'rox -p=Default' to turn it on in "
1941 "future."));
1942 g_return_if_fail(current_pinboard != NULL);
1945 abandon_backdrop_app(current_pinboard);
1947 g_free(current_pinboard->backdrop);
1948 current_pinboard->backdrop = g_strdup(path);
1949 current_pinboard->backdrop_style = style;
1950 reload_backdrop(current_pinboard,
1951 current_pinboard->backdrop,
1952 current_pinboard->backdrop_style);
1954 pinboard_save();