r3660: Panel and pinboard icons can now have a single argument set for them.
[rox-filer/dt.git] / ROX-Filer / src / pinboard.c
blob58d3da770b73c48d3a39884eff2189a870d35e43
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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 <stdlib.h>
33 #include <math.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"
56 #include "panel.h" /* For panel_mark_used() */
57 #include "wrapped.h"
58 #include "dropbox.h"
60 static gboolean tmp_icon_selected = FALSE; /* When dragging */
62 static GtkWidget *set_backdrop_dialog = NULL;
64 struct _Pinboard {
65 guchar *name; /* Leaf name */
66 GList *icons;
67 GtkStyle *style;
69 gchar *backdrop; /* Pathname */
70 BackdropStyle backdrop_style;
71 gint to_backdrop_app; /* pipe FD, or -1 */
72 gint from_backdrop_app; /* pipe FD, or -1 */
73 gint input_tag;
74 GString *input_buffer;
76 GtkWidget *window; /* Screen-sized window */
77 GtkWidget *fixed;
78 GdkGC *shadow_gc;
81 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
83 typedef struct _PinIconClass PinIconClass;
84 typedef struct _PinIcon PinIcon;
86 struct _PinIconClass {
87 IconClass parent;
90 struct _PinIcon {
91 Icon icon;
93 int x, y;
94 GtkWidget *win;
95 GtkWidget *widget; /* The drawing area for the icon */
96 GtkWidget *label;
99 /* The number of pixels between the bottom of the image and the top
100 * of the text.
102 #define GAP 4
104 /* The size of the border around the icon which is used when winking */
105 #define WINK_FRAME 2
107 /* Grid sizes */
108 #define GRID_STEP_FINE 2
109 #define GRID_STEP_MED 16
110 #define GRID_STEP_COARSE 32
112 /* Used in options */
113 #define CORNER_TOP_LEFT 0
114 #define CORNER_TOP_RIGHT 1
115 #define CORNER_BOTTOM_LEFT 2
116 #define CORNER_BOTTOM_RIGHT 3
118 #define DIR_HORZ 0
119 #define DIR_VERT 1
121 static PinIcon *current_wink_icon = NULL;
122 static gint wink_timeout;
124 /* Used for the text colours (only) in the icons */
125 GdkColor pin_text_fg_col, pin_text_bg_col;
126 PangoFontDescription *pinboard_font = NULL; /* NULL => Gtk default */
128 static GdkColor pin_text_shadow_col;
130 Pinboard *current_pinboard = NULL;
131 static gint loading_pinboard = 0; /* Non-zero => loading */
133 /* The Icon that was used to start the current drag, if any */
134 Icon *pinboard_drag_in_progress = NULL;
136 /* For selecting groups of icons */
137 static gboolean lasso_in_progress = FALSE;
138 static int lasso_rect_x1, lasso_rect_x2;
139 static int lasso_rect_y1, lasso_rect_y2;
141 /* Tracking icon positions */
142 static GHashTable *placed_icons=NULL;
144 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
145 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
146 static Option o_pinboard_tasklist, o_forward_buttons_13;
147 static Option o_iconify_start, o_iconify_dir;
148 static Option o_label_font, o_pinboard_shadow_colour;
149 static Option o_pinboard_shadow_labels;
150 static Option o_blackbox_hack;
152 static Option o_top_margin, o_bottom_margin;
154 /* Static prototypes */
155 static GType pin_icon_get_type(void);
156 static void set_size_and_style(PinIcon *pi);
157 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
158 static gint end_wink(gpointer data);
159 static gboolean button_release_event(GtkWidget *widget,
160 GdkEventButton *event,
161 PinIcon *pi);
162 static gboolean enter_notify(GtkWidget *widget,
163 GdkEventCrossing *event,
164 PinIcon *pi);
165 static gint leave_notify(GtkWidget *widget,
166 GdkEventCrossing *event,
167 PinIcon *pi);
168 static gboolean button_press_event(GtkWidget *widget,
169 GdkEventButton *event,
170 PinIcon *pi);
171 static gboolean scroll_event(GtkWidget *widget,
172 GdkEventScroll *event);
173 static gint icon_motion_notify(GtkWidget *widget,
174 GdkEventMotion *event,
175 PinIcon *pi);
176 static const char *pin_from_file(gchar *line);
177 static void snap_to_grid(int *x, int *y);
178 static void offset_from_centre(PinIcon *pi, int *x, int *y);
179 static gboolean drag_motion(GtkWidget *widget,
180 GdkDragContext *context,
181 gint x,
182 gint y,
183 guint time,
184 PinIcon *pi);
185 static void drag_set_pinicon_dest(PinIcon *pi);
186 static void drag_leave(GtkWidget *widget,
187 GdkDragContext *context,
188 guint32 time,
189 PinIcon *pi);
190 static gboolean bg_drag_motion(GtkWidget *widget,
191 GdkDragContext *context,
192 gint x,
193 gint y,
194 guint time,
195 gpointer data);
196 static gboolean bg_drag_leave(GtkWidget *widget,
197 GdkDragContext *context,
198 guint32 time,
199 gpointer data);
200 static gboolean bg_expose(GtkWidget *window,
201 GdkEventExpose *event, gpointer data);
202 static void drag_end(GtkWidget *widget,
203 GdkDragContext *context,
204 PinIcon *pi);
205 static void reshape_all(void);
206 static void pinboard_check_options(void);
207 static void pinboard_load_from_xml(xmlDocPtr doc);
208 static void pinboard_clear(void);
209 static void pinboard_save(void);
210 static PinIcon *pin_icon_new(const char *pathname, const char *name);
211 static void pin_icon_destroyed(PinIcon *pi);
212 static void pin_icon_set_tip(PinIcon *pi);
213 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
214 static void create_pinboard_window(Pinboard *pinboard);
215 static void reload_backdrop(Pinboard *pinboard,
216 const gchar *backdrop,
217 BackdropStyle backdrop_style);
218 static void pinboard_reshape_icon(Icon *icon);
219 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
220 static void abandon_backdrop_app(Pinboard *pinboard);
221 static void drag_backdrop_dropped(GtkWidget *drop_box,
222 const guchar *path,
223 GtkWidget *dialog);
224 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
225 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
226 gboolean old);
227 static void update_pinboard_font(void);
228 static void draw_lasso(void);
229 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d);
230 static void clear_backdrop(GtkWidget *drop_box, gpointer data);
231 static void radios_changed(gpointer data);
232 static void update_radios(GtkWidget *dialog);
234 /****************************************************************
235 * EXTERNAL INTERFACE *
236 ****************************************************************/
238 void pinboard_init(void)
240 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
241 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
242 option_add_string(&o_pinboard_shadow_colour, "pinboard_shadow_colour",
243 "#000");
244 option_add_string(&o_label_font, "label_font", "");
245 option_add_int(&o_pinboard_shadow_labels, "pinboard_shadow_labels", 1);
247 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
248 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
249 GRID_STEP_COARSE);
250 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
251 option_add_int(&o_forward_buttons_13, "pinboard_forward_buttons_13",
252 FALSE);
254 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
255 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
257 option_add_int(&o_blackbox_hack, "blackbox_hack", FALSE);
259 option_add_int(&o_top_margin, "pinboard_top_margin", 0);
260 option_add_int(&o_bottom_margin, "pinboard_bottom_margin", 0);
262 option_add_notify(pinboard_check_options);
264 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
265 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
266 gdk_color_parse(o_pinboard_shadow_colour.value, &pin_text_shadow_col);
267 update_pinboard_font();
269 placed_icons=g_hash_table_new(g_str_hash, g_str_equal);
272 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
273 * and make it the current pinboard.
274 * Any existing pinned items are removed. You must call this
275 * at least once before using the pinboard. NULL disables the
276 * pinboard.
278 void pinboard_activate(const gchar *name)
280 Pinboard *old_board = current_pinboard;
281 guchar *path, *slash;
283 /* Treat an empty name the same as NULL */
284 if (name && !*name)
285 name = NULL;
287 if (old_board)
288 pinboard_clear();
290 if (!name)
292 if (number_of_windows < 1 && gtk_main_level() > 0)
293 gtk_main_quit();
295 gdk_property_delete(gdk_get_default_root_window(),
296 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
297 return;
300 number_of_windows++;
302 slash = strchr(name, '/');
303 if (slash)
305 if (access(name, F_OK))
306 path = NULL; /* File does not (yet) exist */
307 else
308 path = g_strdup(name);
310 else
312 guchar *leaf;
314 leaf = g_strconcat("pb_", name, NULL);
315 path = choices_find_path_load(leaf, PROJECT);
316 g_free(leaf);
319 current_pinboard = g_new(Pinboard, 1);
320 current_pinboard->name = g_strdup(name);
321 current_pinboard->icons = NULL;
322 current_pinboard->window = NULL;
323 current_pinboard->backdrop = NULL;
324 current_pinboard->backdrop_style = BACKDROP_NONE;
325 current_pinboard->to_backdrop_app = -1;
326 current_pinboard->from_backdrop_app = -1;
327 current_pinboard->input_tag = -1;
328 current_pinboard->input_buffer = NULL;
330 create_pinboard_window(current_pinboard);
332 loading_pinboard++;
333 if (path)
335 xmlDocPtr doc;
336 doc = xmlParseFile(path);
337 if (doc)
339 pinboard_load_from_xml(doc);
340 xmlFreeDoc(doc);
341 reload_backdrop(current_pinboard,
342 current_pinboard->backdrop,
343 current_pinboard->backdrop_style);
345 else
347 parse_file(path, pin_from_file);
348 info_message(_("Your old pinboard file has been "
349 "converted to the new XML format."));
350 pinboard_save();
352 g_free(path);
354 else
355 pinboard_pin(home_dir, "Home",
356 4 + ICON_WIDTH / 2,
357 4 + ICON_HEIGHT / 2,
358 NULL);
359 loading_pinboard--;
361 if (o_pinboard_tasklist.int_value)
362 tasklist_set_active(TRUE);
365 /* Return the window of the current pinboard, or NULL.
366 * Used to make sure lowering the panels doesn't lose them...
368 GdkWindow *pinboard_get_window(void)
370 if (current_pinboard)
371 return current_pinboard->window->window;
372 return NULL;
375 const char *pinboard_get_name(void)
377 g_return_val_if_fail(current_pinboard != NULL, NULL);
379 return current_pinboard->name;
382 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
383 * being cleared.
385 void pinboard_add_widget(GtkWidget *widget, const gchar *name)
387 GtkRequisition req;
388 GdkRectangle *rect=NULL;
389 gboolean found=FALSE;
391 g_return_if_fail(current_pinboard != NULL);
393 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
395 gtk_widget_size_request(widget, &req);
397 if(name) {
398 rect=g_hash_table_lookup(placed_icons, name);
399 if(rect)
400 found=TRUE;
402 /*printf("%s at %p %d\n", name? name: "(nil)", rect, found);*/
404 if(rect) {
405 if(rect->width<req.width || rect->height<req.height) {
406 found=FALSE;
408 } else {
409 rect=g_new(GdkRectangle, 1);
410 rect->width = req.width;
411 rect->height = req.height;
413 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
414 rect->y, found);*/
415 find_free_rect(current_pinboard, rect, found);
416 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
417 rect->y, found);*/
419 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
420 widget, rect->x, rect->y);
422 /* Store the new position (key and value are never freed) */
423 if(name)
424 g_hash_table_insert(placed_icons, g_strdup(name),
425 rect);
428 void pinboard_moved_widget(GtkWidget *widget, const gchar *name,
429 int x, int y)
431 GdkRectangle *rect;
433 if(!name)
434 return;
435 rect=g_hash_table_lookup(placed_icons, name);
436 if(!rect)
437 return;
439 rect->x=x;
440 rect->y=y;
443 /* Add a new icon to the background.
444 * 'path' should be an absolute pathname.
445 * 'x' and 'y' are the coordinates of the point in the middle of the text.
446 * 'name' is the name to use. If NULL then the leafname of path is used.
448 void pinboard_pin_with_arg(const gchar *path, const gchar *name, int x, int y,
449 const gchar *shortcut, const gchar *arg)
451 GtkWidget *align, *vbox;
452 GdkWindow *events;
453 PinIcon *pi;
454 Icon *icon;
456 g_return_if_fail(path != NULL);
457 g_return_if_fail(current_pinboard != NULL);
459 pi = pin_icon_new(path, name);
460 icon = (Icon *) pi;
461 pi->x = x;
462 pi->y = y;
464 /* This is a bit complicated...
466 * An icon needs to be a NO_WINDOW widget so that the image can
467 * blend with the background (A ParentRelative window also works, but
468 * is slow, causes the xfree86's memory consumption to grow without
469 * bound, and doesn't even get freed when the filer quits!).
471 * However, the icon also needs to have a window, so we get events
472 * delivered correctly. The solution is to float an InputOnly window
473 * over the icon. Since GtkButton works the same way, we just use
474 * that :-)
477 /* Button takes the initial ref of Icon */
478 pi->win = gtk_button_new();
479 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
480 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
481 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
483 vbox = gtk_vbox_new(FALSE, 0);
484 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
486 align = gtk_alignment_new(0.5, 0.5, 0, 0);
487 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
488 gtk_container_add(GTK_CONTAINER(align), pi->widget);
490 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
491 drag_set_pinicon_dest(pi);
492 g_signal_connect(pi->win, "drag_data_get",
493 G_CALLBACK(drag_data_get), NULL);
495 pi->label = wrapped_label_new(icon->item->leafname, 180);
496 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
498 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
500 snap_to_grid(&x, &y);
501 pi->x = x;
502 pi->y = y;
503 gtk_widget_show_all(pi->win);
504 pinboard_reshape_icon((Icon *) pi);
506 gtk_widget_realize(pi->win);
507 events = GTK_BUTTON(pi->win)->event_window;
508 gdk_window_set_events(events,
509 GDK_EXPOSURE_MASK |
510 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
511 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
512 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
513 GDK_BUTTON3_MOTION_MASK);
514 g_signal_connect(pi->win, "enter-notify-event",
515 G_CALLBACK(enter_notify), pi);
516 g_signal_connect(pi->win, "leave-notify-event",
517 G_CALLBACK(leave_notify), pi);
518 g_signal_connect(pi->win, "button-press-event",
519 G_CALLBACK(button_press_event), pi);
520 g_signal_connect(pi->win, "button-release-event",
521 G_CALLBACK(button_release_event), pi);
522 g_signal_connect(pi->win, "motion-notify-event",
523 G_CALLBACK(icon_motion_notify), pi);
524 g_signal_connect(pi->win, "expose-event",
525 G_CALLBACK(draw_icon), pi);
526 g_signal_connect_swapped(pi->win, "style-set",
527 G_CALLBACK(pinboard_reshape_icon), pi);
528 g_signal_connect_swapped(pi->win, "destroy",
529 G_CALLBACK(pin_icon_destroyed), pi);
531 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
532 pin_icon_set_tip(pi);
534 icon_set_shortcut(icon, shortcut);
535 icon_set_argument(icon, arg);
537 if (!loading_pinboard)
538 pinboard_save();
541 void pinboard_pin(const gchar *path, const gchar *name, int x, int y,
542 const gchar *shortcut)
544 pinboard_pin_with_arg(path, name, x, y, shortcut, NULL);
547 /* Put a border around the icon, briefly.
548 * If icon is NULL then cancel any existing wink.
549 * The icon will automatically unhighlight unless timeout is FALSE,
550 * in which case you must call this function again (with NULL or another
551 * icon) to remove the highlight.
553 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
555 PinIcon *old = current_wink_icon;
557 if (old == pi)
558 return;
560 current_wink_icon = pi;
562 if (old)
564 gtk_widget_queue_draw(old->win);
565 gdk_window_process_updates(old->widget->window, TRUE);
567 if (wink_timeout != -1)
568 g_source_remove(wink_timeout);
571 if (pi)
573 gtk_widget_queue_draw(pi->win);
574 gdk_window_process_updates(pi->widget->window, TRUE);
576 if (timeout)
577 wink_timeout = g_timeout_add(300, end_wink, NULL);
578 else
579 wink_timeout = -1;
583 /* 'app' is saved as the new application to set the backdrop. It will then be
584 * run, and should communicate with the filer as described in the manual.
586 void pinboard_set_backdrop_app(const gchar *app)
588 XMLwrapper *ai;
589 DirItem *item;
590 gboolean can_set;
592 item = diritem_new("");
593 diritem_restat(app, item, NULL);
594 if (!(item->flags & ITEM_FLAG_APPDIR))
596 delayed_error(_("The backdrop handler must be an application "
597 "directory. Drag an application directory "
598 "into the Set Backdrop dialog box, or (for "
599 "programmers) pass it to the SOAP "
600 "SetBackdropApp method."));
601 diritem_free(item);
602 return;
605 ai = appinfo_get(app, item);
606 diritem_free(item);
608 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
609 if (ai)
610 g_object_unref(ai);
612 if (can_set)
613 pinboard_set_backdrop(app, BACKDROP_PROGRAM);
614 else
615 delayed_error(_("You can only set the backdrop to an image "
616 "or to a program which knows how to "
617 "manage ROX-Filer's backdrop.\n\n"
618 "Programmers: the application's AppInfo.xml "
619 "must contain the CanSetBackdrop element, as "
620 "described in ROX-Filer's manual."));
623 /* Open a dialog box allowing the user to set the backdrop */
624 void pinboard_set_backdrop_box(void)
626 GtkWidget *dialog, *frame, *label, *hbox;
627 GtkBox *vbox;
628 Radios *radios;
630 g_return_if_fail(current_pinboard != NULL);
632 if (set_backdrop_dialog)
633 gtk_widget_destroy(set_backdrop_dialog);
635 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
636 GTK_DIALOG_NO_SEPARATOR,
637 GTK_STOCK_CLOSE, GTK_RESPONSE_OK,
638 NULL);
639 set_backdrop_dialog = dialog;
640 g_signal_connect(dialog, "destroy",
641 G_CALLBACK(gtk_widget_destroyed), &set_backdrop_dialog);
642 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
644 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
646 label = gtk_label_new(_("Choose a style and drag an image in:"));
647 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
648 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
649 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
651 /* The Centred, Scaled, Tiled radios... */
652 hbox = gtk_hbox_new(TRUE, 2);
653 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
655 radios = radios_new(radios_changed, dialog);
656 g_object_set_data(G_OBJECT(dialog), "rox-radios", radios);
657 g_object_set_data(G_OBJECT(dialog), "rox-radios-hbox", hbox);
659 radios_add(radios, _("Centre the image without scaling it"),
660 BACKDROP_CENTRE, _("Centre"));
661 radios_add(radios, _("Scale the image to fit the backdrop area, "
662 "without distorting it"),
663 BACKDROP_SCALE, _("Scale"));
664 radios_add(radios, _("Stretch the image to fill the backdrop area"),
665 BACKDROP_STRETCH, _("Stretch"));
666 radios_add(radios, _("Tile the image over the backdrop area"),
667 BACKDROP_TILE, _("Tile"));
669 update_radios(dialog);
671 /* The drop area... */
672 frame = drop_box_new(_("Drop an image here"));
673 g_object_set_data(G_OBJECT(dialog), "rox-dropbox", frame);
675 radios_pack(radios, GTK_BOX(hbox));
676 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
678 drop_box_set_path(DROP_BOX(frame), current_pinboard->backdrop);
680 g_signal_connect(frame, "path_dropped",
681 G_CALLBACK(drag_backdrop_dropped), dialog);
682 g_signal_connect(frame, "clear",
683 G_CALLBACK(clear_backdrop), dialog);
685 g_signal_connect(dialog, "response",
686 G_CALLBACK(backdrop_response), NULL);
687 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
689 gtk_widget_show_all(dialog);
692 /* Also used by tasklist.c */
693 void draw_label_shadow(WrappedLabel *wl, GdkRegion *region)
695 GtkWidget *widget;
696 gint x, y;
698 if (!o_pinboard_shadow_labels.int_value)
699 return;
701 gdk_gc_set_clip_region(current_pinboard->shadow_gc, region);
703 widget = GTK_WIDGET(wl);
705 y = widget->allocation.y - wl->y_off;
706 x = widget->allocation.x - wl->x_off -
707 ((wl->text_width - widget->allocation.width) >> 1);
709 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
710 x + 1, y + 1, wl->layout);
712 if (o_pinboard_shadow_labels.int_value > 1)
713 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
714 x + 2, y + 2, wl->layout);
716 gdk_gc_set_clip_region(current_pinboard->shadow_gc, NULL);
719 /* Set and save (path, style) as the new backdrop.
720 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
721 * Otherwise, the image is displayed now.
723 void pinboard_set_backdrop(const gchar *path, BackdropStyle style)
725 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
726 (path != NULL && style != BACKDROP_NONE));
728 if (!current_pinboard)
730 if (!path)
731 return;
732 pinboard_activate("Default");
733 delayed_error(_("No pinboard was in use... "
734 "the 'Default' pinboard has been selected. "
735 "Use 'rox -p=Default' to turn it on in "
736 "future."));
737 g_return_if_fail(current_pinboard != NULL);
740 /* We might have just run the old backdrop program and now
741 * we're going to set a new one! Seems a bit mean...
744 abandon_backdrop_app(current_pinboard);
746 g_free(current_pinboard->backdrop);
747 current_pinboard->backdrop = g_strdup(path);
748 current_pinboard->backdrop_style = style;
749 reload_backdrop(current_pinboard,
750 current_pinboard->backdrop,
751 current_pinboard->backdrop_style);
753 pinboard_save();
755 if (set_backdrop_dialog)
757 DropBox *box = g_object_get_data(G_OBJECT(set_backdrop_dialog),
758 "rox-dropbox");
759 g_return_if_fail(box != NULL);
760 drop_box_set_path(box, current_pinboard->backdrop);
761 update_radios(set_backdrop_dialog);
765 /* Called on xrandr screen resizes */
766 void pinboard_update_size(void)
768 int width, height;
770 gtk_window_get_size(GTK_WINDOW(current_pinboard->window),
771 &width, &height);
773 /* Only update the pinboard's size if the screen gets bigger,
774 * not smaller. Not sure what to do about icons that end up
775 * offscreen if the screen shrinks, but perhaps a good policy
776 * is to leave them there until the screen size is increased
777 * again rather than mess them around. */
778 width = MAX(width, screen_width);
779 height = MAX(height, screen_height);
781 gtk_widget_set_size_request(current_pinboard->window, width, height);
784 /****************************************************************
785 * INTERNAL FUNCTIONS *
786 ****************************************************************/
788 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
790 gtk_widget_destroy(dialog);
793 static void clear_backdrop(GtkWidget *drop_box, gpointer data)
795 pinboard_set_backdrop(NULL, BACKDROP_NONE);
798 static void drag_backdrop_dropped(GtkWidget *drop_box,
799 const guchar *path,
800 GtkWidget *dialog)
802 struct stat info;
803 Radios *radios;
805 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
806 g_return_if_fail(radios != NULL);
808 if (mc_stat(path, &info))
810 delayed_error(
811 _("Can't access '%s':\n%s"), path,
812 g_strerror(errno));
813 return;
816 if (S_ISDIR(info.st_mode))
818 /* Use this program to set the backdrop */
819 pinboard_set_backdrop_app(path);
821 else if (S_ISREG(info.st_mode))
822 pinboard_set_backdrop(path, radios_get_value(radios));
823 else
824 delayed_error(_("Only files (and certain applications) can be "
825 "used to set the background image."));
828 /* Do this in the idle loop so that we don't try to put an unmanaged
829 * pinboard behind a managed panel (crashes some WMs).
831 static gboolean recreate_pinboard(gchar *name)
833 pinboard_activate(name);
834 g_free(name);
836 return FALSE;
839 static void pinboard_check_options(void)
841 GdkColor n_fg, n_bg, n_shadow;
843 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
844 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
845 gdk_color_parse(o_pinboard_shadow_colour.value, &n_shadow);
847 if (o_override_redirect.has_changed && current_pinboard)
849 gchar *name;
850 name = g_strdup(current_pinboard->name);
851 pinboard_activate(NULL);
852 g_idle_add((GtkFunction) recreate_pinboard, name);
855 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
857 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
858 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0 ||
859 gdk_color_equal(&n_shadow, &pin_text_shadow_col) == 0 ||
860 o_pinboard_shadow_labels.has_changed ||
861 o_label_font.has_changed)
863 pin_text_fg_col = n_fg;
864 pin_text_bg_col = n_bg;
865 pin_text_shadow_col = n_shadow;
866 update_pinboard_font();
868 if (current_pinboard)
870 GtkWidget *w = current_pinboard->window;
871 GdkColormap *cm;
873 cm = gtk_widget_get_colormap(w);
875 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
876 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
878 gdk_gc_set_rgb_fg_color(current_pinboard->shadow_gc,
879 &n_shadow);
881 abandon_backdrop_app(current_pinboard);
882 reload_backdrop(current_pinboard,
883 current_pinboard->backdrop,
884 current_pinboard->backdrop_style);
886 reshape_all();
889 tasklist_style_changed();
893 static gint end_wink(gpointer data)
895 pinboard_wink_item(NULL, FALSE);
896 return FALSE;
899 /* Sets the appearance from the options and updates the size request of
900 * the image.
902 static void set_size_and_style(PinIcon *pi)
904 Icon *icon = (Icon *) pi;
905 MaskedPixmap *image = icon->item->image;
906 int iwidth = image->width;
907 int iheight = image->height;
909 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
910 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
911 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
912 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
913 widget_modify_font(pi->label, pinboard_font);
915 wrapped_label_set_text(WRAPPED_LABEL(pi->label), icon->item->leafname);
917 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
920 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
922 static GtkWidgetClass *parent_class = NULL;
923 Icon *icon = (Icon *) pi;
924 DirItem *item = icon->item;
925 MaskedPixmap *image = item->image;
926 int iwidth = image->width;
927 int iheight = image->height;
928 int x, y;
930 if (!parent_class)
932 gpointer c = ((GTypeInstance *) widget)->g_class;
933 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
936 x = pi->widget->allocation.x;
937 y = pi->widget->allocation.y;
939 gdk_gc_set_clip_region(pi->widget->style->black_gc, event->region);
941 render_pixbuf(icon->selected ? image->pixbuf_lit : image->pixbuf,
942 pi->widget->window,
943 pi->widget->style->black_gc,
944 x, y, iwidth, iheight);
946 if (item->flags & ITEM_FLAG_SYMLINK)
948 render_pixbuf(im_symlink->pixbuf, pi->widget->window,
949 pi->widget->style->black_gc,
950 x, y, -1, -1);
952 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
954 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
955 ? im_mounted
956 : im_unmounted;
958 render_pixbuf(mp->pixbuf, pi->widget->window,
959 pi->widget->style->black_gc,
960 x, y, -1, -1);
963 gdk_gc_set_clip_region(pi->widget->style->black_gc, NULL);
965 if (icon->selected)
967 GtkStyle *style = pi->label->style;
968 GdkGC *gc = style->bg_gc[GTK_STATE_SELECTED];
970 gdk_gc_set_clip_region(gc, event->region);
971 gdk_draw_rectangle(pi->label->window, gc, TRUE,
972 pi->label->allocation.x,
973 pi->label->allocation.y,
974 pi->label->allocation.width,
975 pi->label->allocation.height);
976 gdk_gc_set_clip_region(gc, NULL);
978 else if (o_pinboard_shadow_labels.int_value)
979 draw_label_shadow((WrappedLabel *) pi->label, event->region);
981 /* Draw children */
982 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL],
983 event->region);
984 (parent_class->expose_event)(widget, event);
985 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL], NULL);
987 /* Stop the button effect */
988 return TRUE;
991 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
993 gint x, y, width, height;
995 if (current_wink_icon != pi)
996 return FALSE;
998 x = widget->allocation.x;
999 y = widget->allocation.y;
1000 width = widget->allocation.width;
1001 height = widget->allocation.height;
1003 gdk_draw_rectangle(widget->window,
1004 pi->widget->style->white_gc,
1005 FALSE,
1006 x, y, width - 1, height - 1);
1007 gdk_draw_rectangle(widget->window,
1008 pi->widget->style->black_gc,
1009 FALSE,
1010 x + 1, y + 1, width - 3, height - 3);
1012 return FALSE;
1015 static gboolean enter_notify(GtkWidget *widget,
1016 GdkEventCrossing *event,
1017 PinIcon *pi)
1019 icon_may_update((Icon *) pi);
1020 pin_icon_set_tip(pi);
1021 return TRUE;
1024 static gint leave_notify(GtkWidget *widget,
1025 GdkEventCrossing *event,
1026 PinIcon *pi)
1028 return TRUE;
1031 static void select_lasso(void)
1033 GList *next;
1034 int minx, miny, maxx, maxy;
1036 g_return_if_fail(lasso_in_progress == TRUE);
1038 minx = MIN(lasso_rect_x1, lasso_rect_x2);
1039 miny = MIN(lasso_rect_y1, lasso_rect_y2);
1040 maxx = MAX(lasso_rect_x1, lasso_rect_x2);
1041 maxy = MAX(lasso_rect_y1, lasso_rect_y2);
1043 for (next = current_pinboard->icons; next; next = next->next)
1045 PinIcon *pi = (PinIcon *) next->data;
1046 GtkAllocation *alloc = &pi->win->allocation;
1047 int cx = alloc->x + alloc->width / 2;
1048 int cy = alloc->y + alloc->height / 2;
1050 if (cx > minx && cx < maxx && cy > miny && cy < maxy)
1051 icon_set_selected((Icon *) pi, TRUE);
1055 static void cancel_lasso(void)
1057 draw_lasso();
1058 lasso_in_progress = FALSE;
1061 static void pinboard_lasso_box(int start_x, int start_y)
1063 if (lasso_in_progress)
1064 cancel_lasso();
1065 lasso_in_progress = TRUE;
1066 lasso_rect_x1 = lasso_rect_x2 = start_x;
1067 lasso_rect_y1 = lasso_rect_y2 = start_y;
1069 draw_lasso();
1072 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d)
1074 if (!lasso_in_progress)
1075 return FALSE;
1077 if (lasso_rect_x2 != event->x || lasso_rect_y2 != event->y)
1079 draw_lasso();
1080 lasso_rect_x2 = event->x;
1081 lasso_rect_y2 = event->y;
1082 draw_lasso();
1085 return FALSE;
1088 /* Mark the area of the screen covered by the lasso box for redraw */
1089 static void draw_lasso(void)
1091 GdkRectangle area, edge;
1093 if (!lasso_in_progress)
1094 return;
1096 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1097 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1098 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1099 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1101 edge.x = area.x;
1102 edge.y = area.y;
1103 edge.width = area.width;
1105 edge.height = 2; /* Top */
1106 gdk_window_invalidate_rect(current_pinboard->window->window,
1107 &edge, TRUE);
1109 edge.y += area.height - 2; /* Bottom */
1110 gdk_window_invalidate_rect(current_pinboard->window->window,
1111 &edge, TRUE);
1113 edge.y = area.y;
1114 edge.height = area.height;
1115 edge.width = 2; /* Left */
1116 gdk_window_invalidate_rect(current_pinboard->window->window,
1117 &edge, TRUE);
1119 edge.x += area.width - 2; /* Right */
1120 gdk_window_invalidate_rect(current_pinboard->window->window,
1121 &edge, TRUE);
1124 static void perform_action(PinIcon *pi, GdkEventButton *event)
1126 BindAction action;
1127 Icon *icon = (Icon *) pi;
1129 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
1130 event);
1132 /* Actions that can happen with or without an icon */
1133 switch (action)
1135 case ACT_LASSO_CLEAR:
1136 icon_select_only(NULL);
1137 /* (no break) */
1138 case ACT_LASSO_MODIFY:
1139 pinboard_lasso_box(event->x, event->y);
1140 return;
1141 case ACT_CLEAR_SELECTION:
1142 icon_select_only(NULL);
1143 return;
1144 case ACT_POPUP_MENU:
1145 dnd_motion_ungrab();
1146 pinboard_show_menu(event, pi);
1147 return;
1148 case ACT_IGNORE:
1149 return;
1150 default:
1151 break;
1154 g_return_if_fail(pi != NULL);
1156 switch (action)
1158 case ACT_OPEN_ITEM:
1159 dnd_motion_ungrab();
1160 pinboard_wink_item(pi, TRUE);
1161 if (event->type == GDK_2BUTTON_PRESS)
1162 icon_set_selected(icon, FALSE);
1163 run_diritem_with_arg(icon->path, icon->item,
1164 icon->arg, NULL, NULL, FALSE);
1165 break;
1166 case ACT_EDIT_ITEM:
1167 dnd_motion_ungrab();
1168 pinboard_wink_item(pi, TRUE);
1169 if (event->type == GDK_2BUTTON_PRESS)
1170 icon_set_selected(icon, FALSE);
1171 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
1172 break;
1173 case ACT_PRIME_AND_SELECT:
1174 if (!icon->selected)
1175 icon_select_only(icon);
1176 dnd_motion_start(MOTION_READY_FOR_DND);
1177 break;
1178 case ACT_PRIME_AND_TOGGLE:
1179 icon_set_selected(icon, !icon->selected);
1180 dnd_motion_start(MOTION_READY_FOR_DND);
1181 break;
1182 case ACT_PRIME_FOR_DND:
1183 dnd_motion_start(MOTION_READY_FOR_DND);
1184 break;
1185 case ACT_TOGGLE_SELECTED:
1186 icon_set_selected(icon, !icon->selected);
1187 break;
1188 case ACT_SELECT_EXCL:
1189 icon_select_only(icon);
1190 break;
1191 default:
1192 g_warning("Unsupported action : %d\n", action);
1193 break;
1197 static void forward_to_root(GdkEventButton *event)
1199 XButtonEvent xev;
1201 if (event->type == GDK_BUTTON_PRESS)
1203 xev.type = ButtonPress;
1204 if (!o_blackbox_hack.int_value)
1205 XUngrabPointer(gdk_display, event->time);
1207 else
1208 xev.type = ButtonRelease;
1210 xev.window = gdk_x11_get_default_root_xwindow();
1211 xev.root = xev.window;
1212 xev.subwindow = None;
1213 xev.time = event->time;
1214 xev.x = event->x_root; /* Needed for icewm */
1215 xev.y = event->y_root;
1216 xev.x_root = event->x_root;
1217 xev.y_root = event->y_root;
1218 xev.state = event->state;
1219 xev.button = event->button;
1220 xev.same_screen = True;
1222 XSendEvent(gdk_display, xev.window, False,
1223 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
1226 #define FORWARDED_BUTTON(pi, b) ((b) == 2 || \
1227 (((b) == 3 || (b) == 1) && o_forward_buttons_13.int_value && !pi))
1229 /* pi is NULL if this is a root event */
1230 static gboolean button_release_event(GtkWidget *widget,
1231 GdkEventButton *event,
1232 PinIcon *pi)
1234 if (FORWARDED_BUTTON(pi, event->button))
1235 forward_to_root(event);
1236 else if (dnd_motion_release(event))
1238 if (motion_buttons_pressed == 0 && lasso_in_progress)
1240 select_lasso();
1241 cancel_lasso();
1243 return FALSE;
1246 perform_action(pi, event);
1248 return TRUE;
1251 /* pi is NULL if this is a root event */
1252 static gboolean button_press_event(GtkWidget *widget,
1253 GdkEventButton *event,
1254 PinIcon *pi)
1256 /* Just in case we've jumped in front of everything... */
1257 gdk_window_lower(current_pinboard->window->window);
1259 if (FORWARDED_BUTTON(pi, event->button))
1260 forward_to_root(event);
1261 else if (dnd_motion_press(widget, event))
1262 perform_action(pi, event);
1264 return TRUE;
1267 /* Forward mouse scroll events as buttons 4 and 5 to the window manager
1268 * (for old window managers that don't catch the buttons themselves)
1270 static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event)
1272 XButtonEvent xev;
1274 xev.type = ButtonPress;
1275 xev.window = gdk_x11_get_default_root_xwindow();
1276 xev.root = xev.window;
1277 xev.subwindow = None;
1278 xev.time = event->time;
1279 xev.x = event->x_root; /* Needed for icewm */
1280 xev.y = event->y_root;
1281 xev.x_root = event->x_root;
1282 xev.y_root = event->y_root;
1283 xev.state = event->state;
1284 xev.same_screen = True;
1286 if (event->direction == GDK_SCROLL_UP)
1287 xev.button = 4;
1288 else if (event->direction == GDK_SCROLL_DOWN)
1289 xev.button = 5;
1290 else
1291 return FALSE;
1293 XSendEvent(gdk_display, xev.window, False,
1294 ButtonPressMask, (XEvent *) &xev);
1296 return TRUE;
1299 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1301 GtkWidget *widget = pi->win;
1302 Icon *icon = (Icon *) pi;
1304 if (!icon->selected)
1306 tmp_icon_selected = TRUE;
1307 icon_select_only(icon);
1310 g_return_if_fail(icon_selection != NULL);
1312 pinboard_drag_in_progress = icon;
1314 if (icon_selection->next == NULL)
1315 drag_one_item(widget, event, icon->path, icon->item, NULL);
1316 else
1318 guchar *uri_list;
1320 uri_list = icon_create_uri_list();
1321 drag_selection(widget, event, uri_list);
1322 g_free(uri_list);
1326 /* An icon is being dragged around... */
1327 static gint icon_motion_notify(GtkWidget *widget,
1328 GdkEventMotion *event,
1329 PinIcon *pi)
1331 if (motion_state == MOTION_READY_FOR_DND)
1333 if (dnd_motion_moved(event))
1334 start_drag(pi, event);
1335 return TRUE;
1338 return FALSE;
1341 static void backdrop_from_xml(xmlNode *node)
1343 gchar *style;
1345 g_free(current_pinboard->backdrop);
1346 current_pinboard->backdrop = xmlNodeGetContent(node);
1348 style = xmlGetProp(node, "style");
1350 if (style)
1352 current_pinboard->backdrop_style =
1353 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1354 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1355 g_strcasecmp(style, "Stretched") == 0 ? BACKDROP_STRETCH :
1356 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1357 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1358 BACKDROP_NONE;
1359 g_free(style);
1361 else
1362 current_pinboard->backdrop_style = BACKDROP_TILE;
1365 /* Create one pinboard icon for each icon in the doc */
1366 static void pinboard_load_from_xml(xmlDocPtr doc)
1368 xmlNodePtr node, root;
1369 char *tmp, *label, *path, *shortcut, *arg;
1370 int x, y;
1372 root = xmlDocGetRootElement(doc);
1374 for (node = root->xmlChildrenNode; node; node = node->next)
1376 if (node->type != XML_ELEMENT_NODE)
1377 continue;
1378 if (strcmp(node->name, "backdrop") == 0)
1380 backdrop_from_xml(node);
1381 continue;
1383 if (strcmp(node->name, "icon") != 0)
1384 continue;
1386 tmp = xmlGetProp(node, "x");
1387 if (!tmp)
1388 continue;
1389 x = atoi(tmp);
1390 g_free(tmp);
1392 tmp = xmlGetProp(node, "y");
1393 if (!tmp)
1394 continue;
1395 y = atoi(tmp);
1396 g_free(tmp);
1398 label = xmlGetProp(node, "label");
1399 if (!label)
1400 label = g_strdup("<missing label>");
1401 path = xmlNodeGetContent(node);
1402 if (!path)
1403 path = g_strdup("<missing path>");
1404 shortcut = xmlGetProp(node, "shortcut");
1405 arg = xmlGetProp(node, "arg");
1407 pinboard_pin_with_arg(path, label, x, y, shortcut, arg);
1409 g_free(path);
1410 g_free(label);
1411 g_free(shortcut);
1412 g_free(arg);
1416 /* Called for each line in the pinboard file while loading a new board.
1417 * Only used for old-format files when converting to XML.
1419 static const char *pin_from_file(gchar *line)
1421 gchar *leaf = NULL;
1422 int x, y, n;
1424 if (*line == '<')
1426 gchar *end;
1428 end = strchr(line + 1, '>');
1429 if (!end)
1430 return _("Missing '>' in icon label");
1432 leaf = g_strndup(line + 1, end - line - 1);
1434 line = end + 1;
1436 while (g_ascii_isspace(*line))
1437 line++;
1438 if (*line != ',')
1439 return _("Missing ',' after icon label");
1440 line++;
1443 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1444 return NULL; /* Ignore format errors */
1446 pinboard_pin(line + n, leaf, x, y, NULL);
1448 g_free(leaf);
1450 return NULL;
1453 /* Write the current state of the pinboard to the current pinboard file */
1454 static void pinboard_save(void)
1456 guchar *save = NULL;
1457 guchar *save_new = NULL;
1458 GList *next;
1459 xmlDocPtr doc = NULL;
1460 xmlNodePtr root;
1462 g_return_if_fail(current_pinboard != NULL);
1464 if (strchr(current_pinboard->name, '/'))
1465 save = g_strdup(current_pinboard->name);
1466 else
1468 guchar *leaf;
1470 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1471 save = choices_find_path_save(leaf, PROJECT, TRUE);
1472 g_free(leaf);
1475 if (!save)
1476 return;
1478 doc = xmlNewDoc("1.0");
1479 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1481 root = xmlDocGetRootElement(doc);
1483 if (current_pinboard->backdrop)
1485 BackdropStyle style = current_pinboard->backdrop_style;
1486 xmlNodePtr tree;
1488 tree = xmlNewTextChild(root, NULL, "backdrop",
1489 current_pinboard->backdrop);
1490 xmlSetProp(tree, "style",
1491 style == BACKDROP_TILE ? "Tiled" :
1492 style == BACKDROP_CENTRE ? "Centred" :
1493 style == BACKDROP_SCALE ? "Scaled" :
1494 style == BACKDROP_STRETCH ? "Stretched" :
1495 "Program");
1498 for (next = current_pinboard->icons; next; next = next->next)
1500 xmlNodePtr tree;
1501 PinIcon *pi = (PinIcon *) next->data;
1502 Icon *icon = (Icon *) pi;
1503 char *tmp;
1505 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1507 tmp = g_strdup_printf("%d", pi->x);
1508 xmlSetProp(tree, "x", tmp);
1509 g_free(tmp);
1511 tmp = g_strdup_printf("%d", pi->y);
1512 xmlSetProp(tree, "y", tmp);
1513 g_free(tmp);
1515 xmlSetProp(tree, "label", icon->item->leafname);
1516 if (icon->shortcut)
1517 xmlSetProp(tree, "shortcut", icon->shortcut);
1518 if (icon->arg)
1519 xmlSetProp(tree, "arg", icon->arg);
1522 save_new = g_strconcat(save, ".new", NULL);
1523 if (save_xml_file(doc, save_new) || rename(save_new, save))
1524 delayed_error(_("Error saving pinboard %s: %s"),
1525 save, g_strerror(errno));
1526 g_free(save_new);
1528 g_free(save);
1529 if (doc)
1530 xmlFreeDoc(doc);
1533 static void snap_to_grid(int *x, int *y)
1535 int step = o_pinboard_grid_step.int_value;
1537 *x = ((*x + step / 2) / step) * step;
1538 *y = ((*y + step / 2) / step) * step;
1541 /* Convert (x,y) from a centre point to a window position */
1542 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1544 gboolean clamp = o_pinboard_clamp_icons.int_value;
1545 GtkRequisition req;
1547 gtk_widget_size_request(pi->win, &req);
1549 *x -= req.width >> 1;
1550 *y -= req.height >> 1;
1551 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1552 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1555 /* Same as drag_set_dest(), but for pinboard icons */
1556 static void drag_set_pinicon_dest(PinIcon *pi)
1558 GtkObject *obj = GTK_OBJECT(pi->win);
1560 make_drop_target(pi->win, 0);
1562 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1563 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1564 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1567 /* Called during the drag when the mouse is in a widget registered
1568 * as a drop target. Returns TRUE if we can accept the drop.
1570 static gboolean drag_motion(GtkWidget *widget,
1571 GdkDragContext *context,
1572 gint x,
1573 gint y,
1574 guint time,
1575 PinIcon *pi)
1577 GdkDragAction action = context->suggested_action;
1578 const char *type = NULL;
1579 Icon *icon = (Icon *) pi;
1580 DirItem *item = icon->item;
1582 if (gtk_drag_get_source_widget(context) == widget)
1584 g_dataset_set_data(context, "drop_dest_type",
1585 (gpointer) drop_dest_pass_through);
1586 return FALSE; /* Can't drag something to itself! */
1589 if (icon->selected)
1590 goto out; /* Can't drag a selection to itself */
1592 type = dnd_motion_item(context, &item);
1594 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value
1595 && type != drop_dest_prog)
1597 guint state;
1598 gdk_window_get_pointer(NULL, NULL, NULL, &state);
1599 if (state & GDK_BUTTON1_MASK)
1600 action = GDK_ACTION_ASK;
1603 if (!item)
1604 type = NULL;
1605 out:
1606 /* We actually must pretend to accept the drop, even if the
1607 * directory isn't writeable, so that the spring-opening
1608 * thing works.
1611 /* Don't allow drops to non-writeable directories */
1612 if (o_dnd_spring_open.int_value == FALSE &&
1613 type == drop_dest_dir &&
1614 access(icon->path, W_OK) != 0)
1616 type = NULL;
1619 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
1620 if (type)
1622 gdk_drag_status(context, action, time);
1623 g_dataset_set_data_full(context, "drop_dest_path",
1624 g_strdup(icon->path), g_free);
1625 if (type == drop_dest_dir)
1626 dnd_spring_load(context, NULL);
1628 pinboard_wink_item(pi, FALSE);
1630 else
1631 gdk_drag_status(context, 0, time);
1633 /* Always return TRUE to stop the pinboard getting the events */
1634 return TRUE;
1637 static gboolean pinboard_shadow = FALSE;
1638 static gint shadow_x, shadow_y;
1639 #define SHADOW_SIZE (ICON_WIDTH)
1641 static gboolean bg_expose(GtkWidget *widget,
1642 GdkEventExpose *event, gpointer data)
1644 GdkRectangle clipbox;
1645 gpointer gclass = ((GTypeInstance *) widget)->g_class;
1646 gboolean double_buffer;
1648 gdk_gc_set_clip_region(widget->style->white_gc, event->region);
1649 gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1651 gdk_region_get_clipbox(event->region, &clipbox);
1653 double_buffer = (clipbox.width * clipbox.height) < 20000;
1654 if (double_buffer)
1655 gdk_window_begin_paint_region(widget->window, event->region);
1657 /* Clear the area to the background image */
1659 GtkStyle *style = current_pinboard->window->style;
1660 GdkGC *gc = style->bg_gc[GTK_STATE_NORMAL];
1662 gdk_gc_set_clip_region(gc, event->region);
1663 if (style->bg_pixmap[GTK_STATE_NORMAL])
1665 gdk_gc_set_ts_origin(gc, 0, 0);
1666 gdk_gc_set_fill(gc, GDK_TILED);
1667 gdk_gc_set_tile(gc, style->bg_pixmap[GTK_STATE_NORMAL]);
1670 gdk_draw_rectangle(current_pinboard->window->window, gc, TRUE,
1671 clipbox.x, clipbox.y,
1672 clipbox.width, clipbox.height);
1673 if (style->bg_pixmap[GTK_STATE_NORMAL])
1674 gdk_gc_set_fill(gc, GDK_SOLID);
1676 gdk_gc_set_clip_region(gc, NULL);
1679 if (pinboard_shadow)
1681 gdk_draw_rectangle(widget->window,
1682 widget->style->white_gc, FALSE,
1683 shadow_x, shadow_y,
1684 SHADOW_SIZE, SHADOW_SIZE);
1685 gdk_draw_rectangle(widget->window,
1686 widget->style->black_gc, FALSE,
1687 shadow_x + 1, shadow_y + 1,
1688 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1691 if (lasso_in_progress)
1693 GdkRectangle area;
1695 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1696 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1697 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1698 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1700 if (area.width > 4 && area.height > 4)
1702 gdk_draw_rectangle(widget->window,
1703 widget->style->white_gc, FALSE,
1704 area.x, area.y,
1705 area.width - 1, area.height - 1);
1706 gdk_draw_rectangle(widget->window,
1707 widget->style->black_gc, FALSE,
1708 area.x + 1, area.y + 1,
1709 area.width - 3, area.height - 3);
1713 gdk_gc_set_clip_region(widget->style->white_gc, NULL);
1714 gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1716 ((GtkWidgetClass *) gclass)->expose_event(widget, event);
1718 if (double_buffer)
1719 gdk_window_end_paint(widget->window);
1721 return TRUE;
1724 /* Draw a 'shadow' under an icon being dragged, showing where
1725 * it will land.
1727 static void pinboard_set_shadow(gboolean on)
1729 GdkRectangle area;
1731 if (pinboard_shadow)
1733 area.x = shadow_x;
1734 area.y = shadow_y;
1735 area.width = SHADOW_SIZE + 1;
1736 area.height = SHADOW_SIZE + 1;
1738 gdk_window_invalidate_rect(current_pinboard->window->window,
1739 &area, TRUE);
1742 if (on)
1744 int old_x = shadow_x, old_y = shadow_y;
1746 gdk_window_get_pointer(current_pinboard->fixed->window,
1747 &shadow_x, &shadow_y, NULL);
1748 snap_to_grid(&shadow_x, &shadow_y);
1749 shadow_x -= SHADOW_SIZE / 2;
1750 shadow_y -= SHADOW_SIZE / 2;
1753 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1754 return;
1756 area.x = shadow_x;
1757 area.y = shadow_y;
1758 area.width = SHADOW_SIZE + 1;
1759 area.height = SHADOW_SIZE + 1;
1761 gdk_window_invalidate_rect(current_pinboard->window->window,
1762 &area, TRUE);
1765 pinboard_shadow = on;
1768 /* Called when dragging some pinboard icons finishes */
1769 void pinboard_move_icons(void)
1771 int x = shadow_x, y = shadow_y;
1772 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1773 int width, height;
1774 int dx, dy;
1775 GList *next;
1777 g_return_if_fail(pi != NULL);
1779 x += SHADOW_SIZE / 2;
1780 y += SHADOW_SIZE / 2;
1781 snap_to_grid(&x, &y);
1783 if (pi->x == x && pi->y == y)
1784 return;
1786 /* Find out how much the dragged icon moved (after snapping).
1787 * Move all selected icons by the same amount.
1789 dx = x - pi->x;
1790 dy = y - pi->y;
1792 /* Move the other selected icons to keep the same relative
1793 * position.
1795 for (next = icon_selection; next; next = next->next)
1797 PinIcon *pi = (PinIcon *) next->data;
1798 int nx, ny;
1800 g_return_if_fail(IS_PIN_ICON(pi));
1802 pi->x += dx;
1803 pi->y += dy;
1804 nx = pi->x;
1805 ny = pi->y;
1807 gdk_drawable_get_size(pi->win->window, &width, &height);
1808 offset_from_centre(pi, &nx, &ny);
1810 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
1811 pi->win, nx, ny);
1814 pinboard_save();
1817 static void drag_leave(GtkWidget *widget,
1818 GdkDragContext *context,
1819 guint32 time,
1820 PinIcon *pi)
1822 pinboard_wink_item(NULL, FALSE);
1823 dnd_spring_abort();
1826 static gboolean bg_drag_leave(GtkWidget *widget,
1827 GdkDragContext *context,
1828 guint32 time,
1829 gpointer data)
1831 pinboard_set_shadow(FALSE);
1832 return TRUE;
1835 static gboolean bg_drag_motion(GtkWidget *widget,
1836 GdkDragContext *context,
1837 gint x,
1838 gint y,
1839 guint time,
1840 gpointer data)
1842 /* Dragging from the pinboard to the pinboard is not allowed */
1844 if (!provides(context, text_uri_list))
1845 return FALSE;
1847 pinboard_set_shadow(TRUE);
1849 gdk_drag_status(context,
1850 context->suggested_action == GDK_ACTION_ASK
1851 ? GDK_ACTION_LINK : context->suggested_action,
1852 time);
1853 return TRUE;
1856 static void drag_end(GtkWidget *widget,
1857 GdkDragContext *context,
1858 PinIcon *pi)
1860 pinboard_drag_in_progress = NULL;
1861 if (tmp_icon_selected)
1863 icon_select_only(NULL);
1864 tmp_icon_selected = FALSE;
1868 /* Something which affects all the icons has changed - reshape
1869 * and redraw all of them.
1871 static void reshape_all(void)
1873 GList *next;
1875 g_return_if_fail(current_pinboard != NULL);
1877 for (next = current_pinboard->icons; next; next = next->next)
1879 Icon *icon = (Icon *) next->data;
1880 pinboard_reshape_icon(icon);
1884 /* Turns off the pinboard. Does not call gtk_main_quit. */
1885 static void pinboard_clear(void)
1887 GList *next;
1889 g_return_if_fail(current_pinboard != NULL);
1891 tasklist_set_active(FALSE);
1893 next = current_pinboard->icons;
1894 while (next)
1896 PinIcon *pi = (PinIcon *) next->data;
1898 next = next->next;
1900 gtk_widget_destroy(pi->win);
1903 gtk_widget_destroy(current_pinboard->window);
1905 abandon_backdrop_app(current_pinboard);
1907 g_object_unref(current_pinboard->shadow_gc);
1908 current_pinboard->shadow_gc = NULL;
1910 g_free(current_pinboard->name);
1911 null_g_free(&current_pinboard);
1913 number_of_windows--;
1916 static gpointer parent_class;
1918 static void pin_icon_destroy(Icon *icon)
1920 PinIcon *pi = (PinIcon *) icon;
1922 g_return_if_fail(pi->win != NULL);
1924 gtk_widget_hide(pi->win); /* Stops flicker - stupid GtkFixed! */
1925 gtk_widget_destroy(pi->win);
1928 static void pinboard_remove_items(void)
1930 g_return_if_fail(icon_selection != NULL);
1932 while (icon_selection)
1933 icon_destroy((Icon *) icon_selection->data);
1935 pinboard_save();
1938 static void pin_icon_update(Icon *icon)
1940 pinboard_reshape_icon(icon);
1941 pinboard_save();
1944 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1946 return IS_PIN_ICON(other);
1949 static void pin_wink_icon(Icon *icon)
1951 pinboard_wink_item((PinIcon *) icon, TRUE);
1954 static void pin_icon_class_init(gpointer gclass, gpointer data)
1956 IconClass *icon = (IconClass *) gclass;
1958 parent_class = g_type_class_peek_parent(gclass);
1960 icon->destroy = pin_icon_destroy;
1961 icon->redraw = pinboard_reshape_icon;
1962 icon->update = pin_icon_update;
1963 icon->wink = pin_wink_icon;
1964 icon->remove_items = pinboard_remove_items;
1965 icon->same_group = pin_icon_same_group;
1968 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1972 static GType pin_icon_get_type(void)
1974 static GType type = 0;
1976 if (!type)
1978 static const GTypeInfo info =
1980 sizeof (PinIconClass),
1981 NULL, /* base_init */
1982 NULL, /* base_finalise */
1983 pin_icon_class_init,
1984 NULL, /* class_finalise */
1985 NULL, /* class_data */
1986 sizeof(PinIcon),
1987 0, /* n_preallocs */
1988 pin_icon_init
1991 type = g_type_register_static(icon_get_type(),
1992 "PinIcon", &info, 0);
1995 return type;
1998 static PinIcon *pin_icon_new(const char *pathname, const char *name)
2000 PinIcon *pi;
2001 Icon *icon;
2003 pi = g_object_new(pin_icon_get_type(), NULL);
2004 icon = (Icon *) pi;
2006 icon_set_path(icon, pathname, name);
2008 return pi;
2011 /* Called when the window widget is somehow destroyed */
2012 static void pin_icon_destroyed(PinIcon *pi)
2014 g_return_if_fail(pi->win != NULL);
2016 pi->win = NULL;
2018 pinboard_wink_item(NULL, FALSE);
2020 if (pinboard_drag_in_progress == (Icon *) pi)
2021 pinboard_drag_in_progress = NULL;
2023 if (current_pinboard)
2024 current_pinboard->icons =
2025 g_list_remove(current_pinboard->icons, pi);
2027 g_object_unref(pi);
2030 /* Set the tooltip */
2031 static void pin_icon_set_tip(PinIcon *pi)
2033 XMLwrapper *ai;
2034 xmlNode *node;
2035 Icon *icon = (Icon *) pi;
2037 g_return_if_fail(pi != NULL);
2039 ai = appinfo_get(icon->path, icon->item);
2041 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
2043 guchar *str;
2044 str = xmlNodeListGetString(node->doc,
2045 node->xmlChildrenNode, 1);
2046 if (str)
2048 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
2049 g_free(str);
2052 else
2053 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
2055 if (ai)
2056 g_object_unref(ai);
2059 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
2061 int pos[3];
2062 GList *list;
2064 pos[0] = event->x_root;
2065 pos[1] = event->y_root;
2067 icon_prepare_menu((Icon *) pi, TRUE);
2069 list = gtk_container_get_children(GTK_CONTAINER(icon_menu));
2070 pos[2] = g_list_length(list) - 6;
2071 g_list_free(list);
2073 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
2074 position_menu,
2075 (gpointer) pos, event->button, event->time);
2078 static void create_pinboard_window(Pinboard *pinboard)
2080 GtkWidget *win;
2082 g_return_if_fail(pinboard->window == NULL);
2084 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2085 gtk_widget_set_style(win, gtk_widget_get_default_style());
2087 gtk_widget_set_double_buffered(win, FALSE);
2088 gtk_widget_set_app_paintable(win, TRUE);
2089 gtk_widget_set_name(win, "rox-pinboard");
2090 pinboard->window = win;
2091 pinboard->fixed = gtk_fixed_new();
2092 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
2094 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
2096 gtk_widget_set_size_request(win, screen_width, screen_height);
2097 gtk_widget_realize(win);
2098 gtk_window_move(GTK_WINDOW(win), 0, 0);
2099 make_panel_window(win);
2101 /* TODO: Use gdk function when it supports this type */
2103 GdkAtom desktop_type;
2105 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
2106 FALSE);
2107 gdk_property_change(win->window,
2108 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
2109 gdk_atom_intern("ATOM", FALSE), 32,
2110 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
2113 gtk_widget_add_events(win,
2114 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
2115 GDK_EXPOSURE_MASK |
2116 GDK_BUTTON1_MOTION_MASK |
2117 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
2118 g_signal_connect(win, "button-press-event",
2119 G_CALLBACK(button_press_event), NULL);
2120 g_signal_connect(win, "button-release-event",
2121 G_CALLBACK(button_release_event), NULL);
2122 g_signal_connect(win, "motion-notify-event",
2123 G_CALLBACK(lasso_motion), NULL);
2124 g_signal_connect(pinboard->fixed, "expose_event",
2125 G_CALLBACK(bg_expose), NULL);
2127 /* Some window managers use scroll events on the root to switch
2128 * desktops, but don't cope with our pinboard window, so we forward
2129 * them manually in that case.
2131 g_signal_connect(win, "scroll-event", G_CALLBACK(scroll_event), NULL);
2133 /* Drag and drop handlers */
2134 drag_set_pinboard_dest(win);
2135 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
2136 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
2138 pinboard->shadow_gc = gdk_gc_new(win->window);
2139 gdk_gc_set_rgb_fg_color(pinboard->shadow_gc, &pin_text_shadow_col);
2141 reload_backdrop(current_pinboard, NULL, BACKDROP_NONE);
2143 gtk_widget_show_all(win);
2144 gdk_window_lower(win->window);
2147 /* Load image 'path' and scale according to 'style' */
2148 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
2150 GdkPixmap *pixmap;
2151 GdkPixbuf *pixbuf;
2152 GError *error = NULL;
2154 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2155 if (error)
2157 delayed_error(_("Error loading backdrop image:\n%s\n"
2158 "Backdrop removed."),
2159 error->message);
2160 g_error_free(error);
2161 pinboard_set_backdrop(NULL, BACKDROP_NONE);
2162 return NULL;
2165 if (style == BACKDROP_STRETCH)
2167 GdkPixbuf *old = pixbuf;
2169 pixbuf = gdk_pixbuf_scale_simple(old,
2170 screen_width, screen_height,
2171 GDK_INTERP_HYPER);
2173 g_object_unref(old);
2175 else if (style == BACKDROP_CENTRE || style == BACKDROP_SCALE)
2177 GdkPixbuf *old = pixbuf;
2178 int x, y, width, height;
2179 float scale;
2181 width = gdk_pixbuf_get_width(pixbuf);
2182 height = gdk_pixbuf_get_height(pixbuf);
2184 if (style == BACKDROP_SCALE)
2186 float scale_x, scale_y;
2187 scale_x = screen_width / ((float) width);
2188 scale_y = screen_height / ((float) height);
2189 scale = MIN(scale_x, scale_y);
2191 else
2192 scale = 1;
2194 pixbuf = gdk_pixbuf_new(
2195 gdk_pixbuf_get_colorspace(pixbuf), FALSE,
2196 8, screen_width, screen_height);
2197 gdk_pixbuf_fill(pixbuf, ((pin_text_bg_col.red & 0xff00) << 16) |
2198 ((pin_text_bg_col.green & 0xff00) << 8) |
2199 ((pin_text_bg_col.blue & 0xff00)));
2201 x = (screen_width - width * scale) / 2;
2202 y = (screen_height - height * scale) / 2;
2203 x = MAX(x, 0);
2204 y = MAX(y, 0);
2206 gdk_pixbuf_composite(old, pixbuf,
2207 x, y,
2208 MIN(screen_width, width * scale),
2209 MIN(screen_height, height * scale),
2210 x, y, scale, scale,
2211 GDK_INTERP_NEAREST, 255);
2212 g_object_unref(old);
2215 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
2216 &pixmap, NULL, 0);
2217 g_object_unref(pixbuf);
2219 return pixmap;
2222 static void abandon_backdrop_app(Pinboard *pinboard)
2224 g_return_if_fail(pinboard != NULL);
2226 if (pinboard->to_backdrop_app != -1)
2228 close(pinboard->to_backdrop_app);
2229 close(pinboard->from_backdrop_app);
2230 g_source_remove(pinboard->input_tag);
2231 g_string_free(pinboard->input_buffer, TRUE);
2232 pinboard->to_backdrop_app = -1;
2233 pinboard->from_backdrop_app = -1;
2234 pinboard->input_tag = -1;
2235 pinboard->input_buffer = NULL;
2238 g_return_if_fail(pinboard->to_backdrop_app == -1);
2239 g_return_if_fail(pinboard->from_backdrop_app == -1);
2240 g_return_if_fail(pinboard->input_tag == -1);
2241 g_return_if_fail(pinboard->input_buffer == NULL);
2244 /* A single line has been read from the child.
2245 * Processes the command, and replies 'ok' (or abandons the child on error).
2247 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
2249 BackdropStyle style;
2250 const char *ok = "ok\n";
2252 if (strncmp(command, "tile ", 5) == 0)
2254 style = BACKDROP_TILE;
2255 command += 5;
2257 else if (strncmp(command, "scale ", 6) == 0)
2259 style = BACKDROP_SCALE;
2260 command += 6;
2262 else if (strncmp(command, "stretch ", 8) == 0)
2264 style = BACKDROP_STRETCH;
2265 command += 8;
2267 else if (strncmp(command, "centre ", 7) == 0)
2269 style = BACKDROP_CENTRE;
2270 command += 7;
2272 else
2274 g_warning("Invalid command '%s' from backdrop app\n",
2275 command);
2276 abandon_backdrop_app(pinboard);
2277 return;
2280 /* Load the backdrop. May abandon the program if loading fails. */
2281 reload_backdrop(pinboard, command, style);
2283 if (pinboard->to_backdrop_app == -1)
2284 return;
2286 while (*ok)
2288 int sent;
2290 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
2291 if (sent <= 0)
2293 /* Remote app quit? Not an error. */
2294 abandon_backdrop_app(pinboard);
2295 break;
2297 ok += sent;
2301 static void backdrop_from_child(Pinboard *pinboard,
2302 int src, GdkInputCondition cond)
2304 char buf[256];
2305 int got;
2307 got = read(src, buf, sizeof(buf));
2309 if (got <= 0)
2311 if (got < 0)
2312 g_warning("backdrop_from_child: %s\n",
2313 g_strerror(errno));
2314 abandon_backdrop_app(pinboard);
2315 return;
2318 g_string_append_len(pinboard->input_buffer, buf, got);
2320 while (pinboard->from_backdrop_app != -1)
2322 int len;
2323 char *nl, *command;
2325 nl = strchr(pinboard->input_buffer->str, '\n');
2326 if (!nl)
2327 return; /* Haven't got a whole line yet */
2329 len = nl - pinboard->input_buffer->str;
2330 command = g_strndup(pinboard->input_buffer->str, len);
2331 g_string_erase(pinboard->input_buffer, 0, len + 1);
2333 command_from_backdrop_app(pinboard, command);
2335 g_free(command);
2339 static void reload_backdrop(Pinboard *pinboard,
2340 const gchar *backdrop,
2341 BackdropStyle backdrop_style)
2343 GtkStyle *style;
2345 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
2347 const char *argv[] = {NULL, "--backdrop", NULL};
2348 GError *error = NULL;
2350 g_return_if_fail(pinboard->to_backdrop_app == -1);
2351 g_return_if_fail(pinboard->from_backdrop_app == -1);
2352 g_return_if_fail(pinboard->input_tag == -1);
2353 g_return_if_fail(pinboard->input_buffer == NULL);
2355 argv[0] = make_path(backdrop, "AppRun");
2357 /* Run the program. It'll send us a SOAP message and we'll
2358 * get back here with a different style and image.
2361 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
2362 G_SPAWN_DO_NOT_REAP_CHILD |
2363 G_SPAWN_SEARCH_PATH,
2364 NULL, NULL, /* Child setup fn */
2365 NULL, /* Child PID */
2366 &pinboard->to_backdrop_app,
2367 &pinboard->from_backdrop_app,
2368 NULL, /* Standard error */
2369 &error))
2371 pinboard->input_buffer = g_string_new(NULL);
2372 pinboard->input_tag = gdk_input_add_full(
2373 pinboard->from_backdrop_app,
2374 GDK_INPUT_READ,
2375 (GdkInputFunction) backdrop_from_child,
2376 pinboard, NULL);
2378 else
2380 delayed_error("%s", error ? error->message : "(null)");
2381 g_error_free(error);
2383 return;
2386 /* Note: Copying a style does not ref the pixmaps! */
2388 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
2389 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
2391 if (backdrop)
2392 style->bg_pixmap[GTK_STATE_NORMAL] =
2393 load_backdrop(backdrop, backdrop_style);
2395 gdk_color_parse(o_pinboard_bg_colour.value,
2396 &style->bg[GTK_STATE_NORMAL]);
2398 gtk_widget_set_style(pinboard->window, style);
2400 g_object_unref(style);
2402 gtk_widget_queue_draw(pinboard->window);
2404 /* Also update root window property (for transparent xterms, etc) */
2405 if (style->bg_pixmap[GTK_STATE_NORMAL])
2407 XID id = GDK_DRAWABLE_XID(style->bg_pixmap[GTK_STATE_NORMAL]);
2408 gdk_property_change(gdk_get_default_root_window(),
2409 gdk_atom_intern("_XROOTPMAP_ID", FALSE),
2410 gdk_atom_intern("PIXMAP", FALSE),
2411 32, GDK_PROP_MODE_REPLACE,
2412 (guchar *) &id, 1);
2414 else
2416 gdk_property_delete(gdk_get_default_root_window(),
2417 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
2421 #define SEARCH_STEP 32
2423 static void search_free(GdkRectangle *rect, GdkRegion *used,
2424 int *outer, int od, int omax,
2425 int *inner, int id, int imax)
2427 *outer = od > 0 ? 0 : omax;
2428 while (*outer >= 0 && *outer <= omax)
2430 *inner = id > 0 ? 0 : imax;
2431 while (*inner >= 0 && *inner <= imax)
2433 if (gdk_region_rect_in(used, rect) ==
2434 GDK_OVERLAP_RECTANGLE_OUT)
2435 return;
2436 *inner += id;
2439 *outer += od;
2442 rect->x = -1;
2443 rect->y = -1;
2446 /* Finds a free area on the pinboard large enough for the width and height
2447 * of the given rectangle, by filling in the x and y fields of 'rect'.
2448 * If 'old' is true, 'rect' has a previous position and we first check
2449 * if it is viable.
2450 * The search order respects user preferences.
2451 * If no area is free, returns any old area.
2453 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
2454 gboolean old)
2456 GdkRegion *used;
2457 GList *next;
2458 GdkRectangle used_rect;
2459 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2461 used = gdk_region_new();
2463 panel_mark_used(used);
2465 /* Subtract the no-go areas... */
2467 if (o_top_margin.int_value > 0)
2469 used_rect.x = 0;
2470 used_rect.y = 0;
2471 used_rect.width = gdk_screen_width();
2472 used_rect.height = o_top_margin.int_value;
2473 gdk_region_union_with_rect(used, &used_rect);
2476 if (o_bottom_margin.int_value > 0)
2478 used_rect.x = 0;
2479 used_rect.y = gdk_screen_height() - o_bottom_margin.int_value;
2480 used_rect.width = gdk_screen_width();
2481 used_rect.height = o_bottom_margin.int_value;
2482 gdk_region_union_with_rect(used, &used_rect);
2485 /* Subtract the used areas... */
2487 next = GTK_FIXED(pinboard->fixed)->children;
2488 for (; next; next = next->next)
2490 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2492 if (!GTK_WIDGET_VISIBLE(fix->widget))
2493 continue;
2495 used_rect.x = fix->x;
2496 used_rect.y = fix->y;
2497 used_rect.width = fix->widget->requisition.width;
2498 used_rect.height = fix->widget->requisition.height;
2500 gdk_region_union_with_rect(used, &used_rect);
2503 /* Check the previous area */
2504 if(old) {
2505 if(gdk_region_rect_in(used, rect)==GDK_OVERLAP_RECTANGLE_OUT) {
2506 gdk_region_destroy(used);
2507 return;
2511 /* Find the first free area (yes, this isn't exactly pretty, but
2512 * it works). If you know a better (fast!) algorithm, let me know!
2516 if (o_iconify_start.int_value == CORNER_TOP_RIGHT ||
2517 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2518 dx = -SEARCH_STEP;
2520 if (o_iconify_start.int_value == CORNER_BOTTOM_LEFT ||
2521 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2522 dy = -SEARCH_STEP;
2524 if (o_iconify_dir.int_value == DIR_VERT)
2526 search_free(rect, used,
2527 &rect->x, dx, screen_width - rect->width,
2528 &rect->y, dy, screen_height - rect->height);
2530 else
2532 search_free(rect, used,
2533 &rect->y, dy, screen_height - rect->height,
2534 &rect->x, dx, screen_width - rect->width);
2537 gdk_region_destroy(used);
2539 if (rect->x == -1)
2541 rect->x = 0;
2542 rect->y = 0;
2546 /* Icon's size, shape or appearance has changed - update the display */
2547 static void pinboard_reshape_icon(Icon *icon)
2549 PinIcon *pi = (PinIcon *) icon;
2550 int x = pi->x, y = pi->y;
2552 set_size_and_style(pi);
2553 offset_from_centre(pi, &x, &y);
2555 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
2557 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
2558 pi->win, x, y);
2562 /* Sets the pinboard_font global from the option. Doesn't do anything else. */
2563 static void update_pinboard_font(void)
2565 if (pinboard_font)
2566 pango_font_description_free(pinboard_font);
2567 pinboard_font = o_label_font.value[0] != '\0'
2568 ? pango_font_description_from_string(o_label_font.value)
2569 : NULL;
2572 static void radios_changed(gpointer data)
2574 GObject *dialog = G_OBJECT(data);
2575 DropBox *drop_box;
2576 const guchar *path;
2577 Radios *radios;
2579 g_return_if_fail(dialog != NULL);
2581 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
2582 drop_box = g_object_get_data(G_OBJECT(dialog), "rox-dropbox");
2584 g_return_if_fail(radios != NULL);
2585 g_return_if_fail(drop_box != NULL);
2586 g_return_if_fail(current_pinboard != NULL);
2588 if (current_pinboard->backdrop_style != BACKDROP_PROGRAM)
2590 path = drop_box_get_path(drop_box);
2591 if (path)
2592 pinboard_set_backdrop(path, radios_get_value(radios));
2596 static void update_radios(GtkWidget *dialog)
2598 Radios *radios;
2599 GtkWidget *hbox;
2601 g_return_if_fail(dialog != NULL);
2603 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
2604 hbox = g_object_get_data(G_OBJECT(dialog), "rox-radios-hbox");
2606 g_return_if_fail(current_pinboard != NULL);
2607 g_return_if_fail(radios != NULL);
2608 g_return_if_fail(hbox != NULL);
2610 switch (current_pinboard->backdrop_style)
2612 case BACKDROP_TILE:
2613 case BACKDROP_STRETCH:
2614 case BACKDROP_SCALE:
2615 case BACKDROP_CENTRE:
2616 radios_set_value(radios,
2617 current_pinboard->backdrop_style);
2618 gtk_widget_set_sensitive(hbox, TRUE);
2619 break;
2620 default:
2621 gtk_widget_set_sensitive(hbox, FALSE);
2622 radios_set_value(radios, BACKDROP_TILE);
2623 break;