r1566: Do double buffering manually. This has no advantage at the moment, but
[rox-filer.git] / ROX-Filer / src / pinboard.c
blobba7f8ef24650713805003310206b662bbea668ae
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 <stdlib.h>
33 #include <libxml/parser.h>
34 #include <signal.h>
36 #include "global.h"
38 #include "pinboard.h"
39 #include "main.h"
40 #include "dnd.h"
41 #include "pixmaps.h"
42 #include "type.h"
43 #include "choices.h"
44 #include "support.h"
45 #include "gui_support.h"
46 #include "options.h"
47 #include "diritem.h"
48 #include "bind.h"
49 #include "icon.h"
50 #include "run.h"
51 #include "appinfo.h"
52 #include "menu.h"
53 #include "xml.h"
54 #include "tasklist.h"
55 #include "panel.h" /* For panel_mark_used() */
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 /* Used in options */
107 #define CORNER_TOP_LEFT 0
108 #define CORNER_TOP_RIGHT 1
109 #define CORNER_BOTTOM_LEFT 2
110 #define CORNER_BOTTOM_RIGHT 3
112 #define DIR_HORZ 0
113 #define DIR_VERT 1
115 static PinIcon *current_wink_icon = NULL;
116 static gint wink_timeout;
118 /* Used for the text colours (only) in the icons (and tasklist windows) */
119 GdkColor pin_text_fg_col, pin_text_bg_col;
121 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
122 static GtkStyle *pinicon_style = NULL;
124 Pinboard *current_pinboard = NULL;
125 static gint loading_pinboard = 0; /* Non-zero => loading */
127 /* The Icon that was used to start the current drag, if any */
128 Icon *pinboard_drag_in_progress = NULL;
130 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
131 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
132 static Option o_pinboard_tasklist, o_forward_button_3;
133 static Option o_iconify_start, o_iconify_dir;
135 /* Static prototypes */
136 static GType pin_icon_get_type(void);
137 static void set_size_and_style(PinIcon *pi);
138 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
139 static gint end_wink(gpointer data);
140 static gboolean button_release_event(GtkWidget *widget,
141 GdkEventButton *event,
142 PinIcon *pi);
143 static gboolean enter_notify(GtkWidget *widget,
144 GdkEventCrossing *event,
145 PinIcon *pi);
146 static gboolean button_press_event(GtkWidget *widget,
147 GdkEventButton *event,
148 PinIcon *pi);
149 static gint icon_motion_notify(GtkWidget *widget,
150 GdkEventMotion *event,
151 PinIcon *pi);
152 static const char *pin_from_file(gchar *line);
153 static void snap_to_grid(int *x, int *y);
154 static void offset_from_centre(PinIcon *pi, int *x, int *y);
155 static gboolean drag_motion(GtkWidget *widget,
156 GdkDragContext *context,
157 gint x,
158 gint y,
159 guint time,
160 PinIcon *pi);
161 static void drag_set_pinicon_dest(PinIcon *pi);
162 static void drag_leave(GtkWidget *widget,
163 GdkDragContext *context,
164 guint32 time,
165 PinIcon *pi);
166 static gboolean bg_drag_motion(GtkWidget *widget,
167 GdkDragContext *context,
168 gint x,
169 gint y,
170 guint time,
171 gpointer data);
172 static gboolean bg_drag_leave(GtkWidget *widget,
173 GdkDragContext *context,
174 guint32 time,
175 gpointer data);
176 static gboolean bg_expose(GtkWidget *window,
177 GdkEventExpose *event, gpointer data);
178 static void drag_end(GtkWidget *widget,
179 GdkDragContext *context,
180 PinIcon *pi);
181 static void reshape_all(void);
182 static void pinboard_check_options(void);
183 static void pinboard_load_from_xml(xmlDocPtr doc);
184 static void pinboard_clear(void);
185 static void pinboard_save(void);
186 static PinIcon *pin_icon_new(const char *pathname, const char *name);
187 static void pin_icon_destroyed(PinIcon *pi);
188 static void pin_icon_set_tip(PinIcon *pi);
189 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
190 static void create_pinboard_window(Pinboard *pinboard);
191 static void reload_backdrop(Pinboard *pinboard,
192 const gchar *backdrop,
193 BackdropStyle backdrop_style);
194 static void set_backdrop(const gchar *path, BackdropStyle style);
195 static void pinboard_reshape_icon(Icon *icon);
196 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
197 static void abandon_backdrop_app(Pinboard *pinboard);
198 static void drag_backdrop_dropped(GtkWidget *frame,
199 GdkDragContext *context,
200 gint x,
201 gint y,
202 GtkSelectionData *selection_data,
203 guint drag_info,
204 guint32 time,
205 GtkWidget *dialog);
206 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
207 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect);
210 /****************************************************************
211 * EXTERNAL INTERFACE *
212 ****************************************************************/
214 void pinboard_init(void)
216 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
217 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
219 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
220 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
221 GRID_STEP_COARSE);
222 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
223 option_add_int(&o_forward_button_3, "pinboard_forward_button_3", FALSE);
225 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
226 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
228 option_add_notify(pinboard_check_options);
230 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
231 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
234 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
235 * and make it the current pinboard.
236 * Any existing pinned items are removed. You must call this
237 * at least once before using the pinboard. NULL disables the
238 * pinboard.
240 void pinboard_activate(const gchar *name)
242 Pinboard *old_board = current_pinboard;
243 guchar *path, *slash;
245 /* Treat an empty name the same as NULL */
246 if (name && !*name)
247 name = NULL;
249 if (old_board)
250 pinboard_clear();
252 if (!name)
254 if (number_of_windows < 1 && gtk_main_level() > 0)
255 gtk_main_quit();
257 gdk_property_delete(gdk_get_default_root_window(),
258 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
259 return;
262 number_of_windows++;
264 slash = strchr(name, '/');
265 if (slash)
267 if (access(name, F_OK))
268 path = NULL; /* File does not (yet) exist */
269 else
270 path = g_strdup(name);
272 else
274 guchar *leaf;
276 leaf = g_strconcat("pb_", name, NULL);
277 path = choices_find_path_load(leaf, PROJECT);
278 g_free(leaf);
281 current_pinboard = g_new(Pinboard, 1);
282 current_pinboard->name = g_strdup(name);
283 current_pinboard->icons = NULL;
284 current_pinboard->window = NULL;
285 current_pinboard->backdrop = NULL;
286 current_pinboard->backdrop_style = BACKDROP_NONE;
287 current_pinboard->to_backdrop_app = -1;
288 current_pinboard->from_backdrop_app = -1;
289 current_pinboard->input_tag = -1;
290 current_pinboard->input_buffer = NULL;
292 create_pinboard_window(current_pinboard);
294 loading_pinboard++;
295 if (path)
297 xmlDocPtr doc;
298 doc = xmlParseFile(path);
299 if (doc)
301 pinboard_load_from_xml(doc);
302 xmlFreeDoc(doc);
303 reload_backdrop(current_pinboard,
304 current_pinboard->backdrop,
305 current_pinboard->backdrop_style);
307 else
309 parse_file(path, pin_from_file);
310 info_message(_("Your old pinboard file has been "
311 "converted to the new XML format."));
312 pinboard_save();
314 g_free(path);
316 else
317 pinboard_pin(home_dir, "Home",
318 4 + ICON_WIDTH / 2,
319 4 + ICON_HEIGHT / 2);
320 loading_pinboard--;
322 if (o_pinboard_tasklist.int_value)
323 tasklist_set_active(TRUE);
326 /* Return the window of the current pinboard, or NULL.
327 * Used to make sure lowering the panels doesn't lose them...
329 GdkWindow *pinboard_get_window(void)
331 if (current_pinboard)
332 return current_pinboard->window->window;
333 return NULL;
336 const char *pinboard_get_name(void)
338 g_return_val_if_fail(current_pinboard != NULL, NULL);
340 return current_pinboard->name;
343 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
344 * being cleared.
346 void pinboard_add_widget(GtkWidget *widget)
348 GtkRequisition req;
349 GdkRectangle rect;
351 g_return_if_fail(current_pinboard != NULL);
353 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
355 gtk_widget_size_request(widget, &req);
357 rect.width = req.width;
358 rect.height = req.height;
359 find_free_rect(current_pinboard, &rect);
361 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
362 widget, rect.x, rect.y);
365 /* Add a new icon to the background.
366 * 'path' should be an absolute pathname.
367 * 'x' and 'y' are the coordinates of the point in the middle of the text.
368 * 'name' is the name to use. If NULL then the leafname of path is used.
370 * name and path are in UTF-8 for Gtk+-2.0 only.
372 void pinboard_pin(const gchar *path, const gchar *name, int x, int y)
374 GtkWidget *align, *vbox;
375 GdkWindow *events;
376 PinIcon *pi;
377 Icon *icon;
379 g_return_if_fail(path != NULL);
380 g_return_if_fail(current_pinboard != NULL);
382 pi = pin_icon_new(path, name);
383 icon = (Icon *) pi;
384 pi->x = x;
385 pi->y = y;
387 /* This is a bit complicated...
389 * An icon needs to be a NO_WINDOW widget so that the image can
390 * blend with the background (A ParentRelative window also works, but
391 * is slow, causes the xfree86's memory consumption to grow without
392 * bound, and doesn't even get freed when the filer quits!).
394 * However, the icon also needs to have a window, so we get events
395 * delivered correctly. The solution is to float an InputOnly window
396 * over the icon. Since GtkButton works the same way, we just use
397 * that :-)
400 /* Button takes the initial ref of Icon */
401 pi->win = gtk_button_new();
402 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
403 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
404 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
406 vbox = gtk_vbox_new(FALSE, 0);
407 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
409 align = gtk_alignment_new(0.5, 0.5, 0, 0);
410 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
411 gtk_container_add(GTK_CONTAINER(align), pi->widget);
413 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
414 drag_set_pinicon_dest(pi);
415 g_signal_connect(pi->win, "drag_data_get",
416 G_CALLBACK(drag_data_get), NULL);
418 pi->label = gtk_label_new(icon->item->leafname);
419 gtk_label_set_line_wrap(GTK_LABEL(pi->label), TRUE);
420 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
422 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
424 snap_to_grid(&x, &y);
425 pi->x = x;
426 pi->y = y;
427 gtk_widget_show_all(pi->win);
428 pinboard_reshape_icon((Icon *) pi);
430 gtk_widget_realize(pi->win);
431 events = GTK_BUTTON(pi->win)->event_window;
432 gdk_window_set_events(events,
433 GDK_EXPOSURE_MASK |
434 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
435 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
436 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
437 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
438 g_signal_connect(pi->win, "enter-notify-event",
439 G_CALLBACK(enter_notify), pi);
440 g_signal_connect(pi->win, "button-press-event",
441 G_CALLBACK(button_press_event), pi);
442 g_signal_connect(pi->win, "button-release-event",
443 G_CALLBACK(button_release_event), pi);
444 g_signal_connect(pi->win, "motion-notify-event",
445 G_CALLBACK(icon_motion_notify), pi);
446 g_signal_connect(pi->win, "expose-event",
447 G_CALLBACK(draw_icon), pi);
448 g_signal_connect_swapped(pi->win, "style-set",
449 G_CALLBACK(pinboard_reshape_icon), pi);
450 g_signal_connect_swapped(pi->win, "destroy",
451 G_CALLBACK(pin_icon_destroyed), pi);
453 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
454 pin_icon_set_tip(pi);
456 if (!loading_pinboard)
457 pinboard_save();
460 /* Remove an icon from the pinboard */
461 /* XXX: use destroy */
462 void pinboard_unpin(PinIcon *pi)
464 g_return_if_fail(pi != NULL);
466 gtk_widget_destroy(pi->win);
467 pinboard_save();
470 /* Put a border around the icon, briefly.
471 * If icon is NULL then cancel any existing wink.
472 * The icon will automatically unhighlight unless timeout is FALSE,
473 * in which case you must call this function again (with NULL or another
474 * icon) to remove the highlight.
476 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
478 PinIcon *old = current_wink_icon;
480 if (old == pi)
481 return;
483 current_wink_icon = pi;
485 if (old)
487 gtk_widget_queue_draw(old->win);
488 gdk_window_process_updates(old->widget->window, TRUE);
490 if (wink_timeout != -1)
491 gtk_timeout_remove(wink_timeout);
494 if (pi)
496 gtk_widget_queue_draw(pi->win);
497 gdk_window_process_updates(pi->widget->window, TRUE);
499 if (timeout)
500 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
501 else
502 wink_timeout = -1;
506 /* 'app' is saved as the new application to set the backdrop. It will then be
507 * run, and should communicate with the filer as described in the manual.
509 void pinboard_set_backdrop_app(const gchar *app)
511 XMLwrapper *ai;
512 DirItem *item;
513 gboolean can_set;
515 item = diritem_new("");
516 diritem_restat(app, item, NULL);
517 if (!(item->flags & ITEM_FLAG_APPDIR))
519 delayed_error(_("The backdrop handler must be an application "
520 "directory. Drag an application directory "
521 "into the Set Backdrop dialog box, or (for "
522 "programmers) pass it to the SOAP "
523 "SetBackdropApp method."));
524 diritem_free(item);
525 return;
528 ai = appinfo_get(app, item);
529 diritem_free(item);
531 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
532 if (ai)
533 g_object_unref(ai);
535 if (can_set)
536 set_backdrop(app, BACKDROP_PROGRAM);
537 else
538 delayed_error(_("You can only set the backdrop to an image "
539 "or to a program which knows how to "
540 "manage ROX-Filer's backdrop.\n\n"
541 "Programmers: the application's AppInfo.xml "
542 "must contain the CanSetBackdrop element, as "
543 "described in ROX-Filer's manual."));
546 /* Open a dialog box allowing the user to set the backdrop */
547 void pinboard_set_backdrop(void)
549 GtkWidget *dialog, *frame, *label, *radio, *hbox;
550 GtkBox *vbox;
551 GtkTargetEntry targets[] = {
552 {"text/uri-list", 0, TARGET_URI_LIST},
555 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
556 GTK_DIALOG_NO_SEPARATOR,
557 GTK_STOCK_CLEAR, GTK_RESPONSE_NO,
558 GTK_STOCK_OK, GTK_RESPONSE_OK,
559 NULL);
560 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
562 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
564 label = gtk_label_new(_("Display backdrop image:"));
565 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
566 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
567 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
569 /* The Centred, Scaled, Tiled radios... */
570 hbox = gtk_hbox_new(TRUE, 2);
571 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
573 radio = gtk_radio_button_new_with_label(NULL, _("Centred"));
574 g_object_set_data(G_OBJECT(dialog), "radio_centred", radio);
575 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
577 radio = gtk_radio_button_new_with_label(
578 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
579 _("Scaled"));
580 g_object_set_data(G_OBJECT(dialog), "radio_scaled", radio);
581 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
583 radio = gtk_radio_button_new_with_label(
584 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
585 _("Tiled"));
586 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
588 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
590 /* The drop area... */
591 frame = gtk_frame_new(NULL);
592 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
593 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
594 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
596 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
597 targets, sizeof(targets) / sizeof(*targets),
598 GDK_ACTION_COPY);
599 g_signal_connect(frame, "drag_data_received",
600 G_CALLBACK(drag_backdrop_dropped), dialog);
602 label = gtk_label_new(_("Drop an image here"));
603 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
604 gtk_container_add(GTK_CONTAINER(frame), label);
606 g_signal_connect(dialog, "response",
607 G_CALLBACK(backdrop_response), NULL);
608 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
610 gtk_widget_show_all(dialog);
613 /****************************************************************
614 * INTERNAL FUNCTIONS *
615 ****************************************************************/
617 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
619 if (response == GTK_RESPONSE_NO)
620 set_backdrop(NULL, BACKDROP_NONE);
622 gtk_widget_destroy(dialog);
625 static void drag_backdrop_dropped(GtkWidget *frame,
626 GdkDragContext *context,
627 gint x,
628 gint y,
629 GtkSelectionData *selection_data,
630 guint drag_info,
631 guint32 time,
632 GtkWidget *dialog)
634 struct stat info;
635 const gchar *path = NULL;
636 GList *uris;
638 if (!selection_data->data)
639 return; /* Timeout? */
641 uris = uri_list_to_glist(selection_data->data);
643 if (g_list_length(uris) == 1)
644 path = get_local_path((guchar *) uris->data);
645 g_list_free(uris);
647 if (!path)
649 delayed_error(
650 _("You should drop a single (local) image file "
651 "onto the drop box - that image will be "
652 "used for the desktop background. You can also "
653 "drag certain applications onto this box."));
654 return;
657 if (mc_stat(path, &info))
659 delayed_error(
660 _("Can't access '%s':\n%s"), path,
661 g_strerror(errno));
662 return;
665 if (S_ISDIR(info.st_mode))
667 /* Use this program to set the backdrop */
668 pinboard_set_backdrop_app(path);
670 else if (S_ISREG(info.st_mode))
672 GtkWidget *radio;
674 radio = g_object_get_data(G_OBJECT(dialog), "radio_scaled");
675 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
677 set_backdrop(path, BACKDROP_SCALE);
678 return;
681 radio = g_object_get_data(G_OBJECT(dialog), "radio_centred");
682 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
684 set_backdrop(path, BACKDROP_CENTRE);
685 return;
688 set_backdrop(path, BACKDROP_TILE);
690 else
691 delayed_error(_("Only files (and certain applications) can be "
692 "used to set the background image."));
695 static void pinboard_check_options(void)
697 GdkColor n_fg, n_bg;
699 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
700 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
702 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
704 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
705 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0)
707 pin_text_fg_col = n_fg;
708 pin_text_bg_col = n_bg;
710 if (pinicon_style)
712 g_object_unref(G_OBJECT(pinicon_style));
713 pinicon_style = NULL;
716 if (current_pinboard)
718 GtkWidget *w = current_pinboard->window;
719 GdkColormap *cm;
721 cm = gtk_widget_get_colormap(w);
723 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
724 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
726 /* Only redraw the background if there is no image */
727 if (!current_pinboard->backdrop)
728 reload_backdrop(current_pinboard,
729 NULL, BACKDROP_NONE);
731 reshape_all();
734 tasklist_style_changed();
738 static gint end_wink(gpointer data)
740 pinboard_wink_item(NULL, FALSE);
741 return FALSE;
744 /* Updates the width, height, name_width and label fields, and resizes the
745 * window. Also sets the style to pinicon_style, generating it if needed.
747 static void set_size_and_style(PinIcon *pi)
749 Icon *icon = (Icon *) pi;
750 MaskedPixmap *image = icon->item->image;
751 int iwidth = image->width;
752 int iheight = image->height;
754 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
755 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
756 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
757 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
759 gtk_label_set_text(GTK_LABEL(pi->label), icon->item->leafname);
761 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
764 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
766 static GtkWidgetClass *parent_class = NULL;
767 Icon *icon = (Icon *) pi;
768 DirItem *item = icon->item;
769 MaskedPixmap *image = item->image;
770 int iwidth = image->width;
771 int iheight = image->height;
772 int x, y;
773 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
774 : GTK_STATE_NORMAL;
776 if (!parent_class)
778 gpointer c = ((GTypeInstance *) widget)->g_class;
779 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
782 x = pi->widget->allocation.x;
783 y = pi->widget->allocation.y;
785 gdk_pixbuf_render_to_drawable_alpha(
786 icon->selected ? image->pixbuf_lit : image->pixbuf,
787 pi->widget->window,
788 0, 0, /* src */
789 x, y, /* dest */
790 iwidth, iheight,
791 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
792 GDK_RGB_DITHER_NORMAL, 0, 0);
794 if (item->flags & ITEM_FLAG_SYMLINK)
796 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
797 pi->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);
804 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
806 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
807 ? im_mounted
808 : im_unmounted;
810 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
811 pi->widget->window,
812 0, 0, /* src */
813 x, y,
814 -1, -1,
815 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
816 GDK_RGB_DITHER_NORMAL, 0, 0);
819 if (icon->selected)
821 gtk_paint_flat_box(pi->label->style, pi->label->window,
822 state,
823 GTK_SHADOW_NONE,
824 NULL, pi->label, "text",
825 pi->label->allocation.x,
826 pi->label->allocation.y,
827 pi->label->allocation.width,
828 pi->label->allocation.height);
831 /* Draw children */
832 (parent_class->expose_event)(widget, event);
834 /* Stop the button effect */
835 return TRUE;
838 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
840 gint x, y, width, height;
842 if (current_wink_icon != pi)
843 return FALSE;
845 x = widget->allocation.x;
846 y = widget->allocation.y;
847 width = widget->allocation.width;
848 height = widget->allocation.height;
850 gdk_draw_rectangle(widget->window,
851 pi->widget->style->white_gc,
852 FALSE,
853 x, y, width - 1, height - 1);
854 gdk_draw_rectangle(widget->window,
855 pi->widget->style->black_gc,
856 FALSE,
857 x + 1, y + 1, width - 3, height - 3);
859 return FALSE;
862 static gboolean enter_notify(GtkWidget *widget,
863 GdkEventCrossing *event,
864 PinIcon *pi)
866 icon_may_update((Icon *) pi);
868 return FALSE;
871 static void perform_action(PinIcon *pi, GdkEventButton *event)
873 BindAction action;
874 Icon *icon = (Icon *) pi;
876 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
877 event);
879 /* Actions that can happen with or without an icon */
880 switch (action)
882 case ACT_CLEAR_SELECTION:
883 icon_select_only(NULL);
884 return;
885 case ACT_POPUP_MENU:
886 dnd_motion_ungrab();
887 pinboard_show_menu(event, pi);
888 return;
889 case ACT_IGNORE:
890 return;
891 default:
892 break;
895 g_return_if_fail(pi != NULL);
897 switch (action)
899 case ACT_OPEN_ITEM:
900 dnd_motion_ungrab();
901 pinboard_wink_item(pi, TRUE);
902 if (event->type == GDK_2BUTTON_PRESS)
903 icon_set_selected(icon, FALSE);
904 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
905 break;
906 case ACT_EDIT_ITEM:
907 dnd_motion_ungrab();
908 pinboard_wink_item(pi, TRUE);
909 if (event->type == GDK_2BUTTON_PRESS)
910 icon_set_selected(icon, FALSE);
911 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
912 break;
913 case ACT_PRIME_AND_SELECT:
914 if (!icon->selected)
915 icon_select_only(icon);
916 dnd_motion_start(MOTION_READY_FOR_DND);
917 break;
918 case ACT_PRIME_AND_TOGGLE:
919 icon_set_selected(icon, !icon->selected);
920 dnd_motion_start(MOTION_READY_FOR_DND);
921 break;
922 case ACT_PRIME_FOR_DND:
923 dnd_motion_start(MOTION_READY_FOR_DND);
924 break;
925 case ACT_TOGGLE_SELECTED:
926 icon_set_selected(icon, !icon->selected);
927 break;
928 case ACT_SELECT_EXCL:
929 icon_select_only(icon);
930 break;
931 default:
932 g_warning("Unsupported action : %d\n", action);
933 break;
937 static void forward_to_root(GdkEventButton *event)
939 XButtonEvent xev;
941 if (event->type == GDK_BUTTON_PRESS)
943 xev.type = ButtonPress;
944 XUngrabPointer(gdk_display, event->time);
946 else
947 xev.type = ButtonRelease;
949 xev.window = gdk_x11_get_default_root_xwindow();
950 xev.root = xev.window;
951 xev.subwindow = None;
952 xev.time = event->time;
953 xev.x = event->x;
954 xev.y = event->y;
955 xev.x_root = event->x_root;
956 xev.y_root = event->y_root;
957 xev.state = event->state;
958 xev.button = event->button;
959 xev.same_screen = True;
961 XSendEvent(gdk_display, xev.window, False,
962 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
965 #define FORWARDED_BUTTON(pi, button) ((button) == 2 || \
966 ((button) == 3 && o_forward_button_3.int_value && !pi))
968 /* pi is NULL if this is a root event */
969 static gboolean button_release_event(GtkWidget *widget,
970 GdkEventButton *event,
971 PinIcon *pi)
973 if (FORWARDED_BUTTON(pi, event->button))
974 forward_to_root(event);
975 else if (dnd_motion_release(event))
976 return TRUE;
978 perform_action(pi, event);
980 return TRUE;
983 /* pi is NULL if this is a root event */
984 static gboolean button_press_event(GtkWidget *widget,
985 GdkEventButton *event,
986 PinIcon *pi)
988 /* Just in case we've jumped in front of everything... */
989 gdk_window_lower(current_pinboard->window->window);
991 if (FORWARDED_BUTTON(pi, event->button))
992 forward_to_root(event);
993 else if (dnd_motion_press(widget, event))
994 perform_action(pi, event);
996 return TRUE;
999 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1001 GtkWidget *widget = pi->win;
1002 Icon *icon = (Icon *) pi;
1004 if (!icon->selected)
1006 tmp_icon_selected = TRUE;
1007 icon_select_only(icon);
1010 g_return_if_fail(icon_selection != NULL);
1012 pinboard_drag_in_progress = icon;
1014 if (icon_selection->next == NULL)
1015 drag_one_item(widget, event, icon->path, icon->item, NULL);
1016 else
1018 guchar *uri_list;
1020 uri_list = icon_create_uri_list();
1021 drag_selection(widget, event, uri_list);
1022 g_free(uri_list);
1026 /* An icon is being dragged around... */
1027 static gint icon_motion_notify(GtkWidget *widget,
1028 GdkEventMotion *event,
1029 PinIcon *pi)
1031 if (motion_state == MOTION_READY_FOR_DND)
1033 if (dnd_motion_moved(event))
1034 start_drag(pi, event);
1035 return TRUE;
1038 return FALSE;
1041 static void backdrop_from_xml(xmlNode *node)
1043 gchar *style;
1045 g_free(current_pinboard->backdrop);
1046 current_pinboard->backdrop = xmlNodeGetContent(node);
1048 style = xmlGetProp(node, "style");
1050 if (style)
1052 current_pinboard->backdrop_style =
1053 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1054 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1055 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1056 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1057 BACKDROP_NONE;
1058 g_free(style);
1060 else
1061 current_pinboard->backdrop_style = BACKDROP_TILE;
1064 /* Create one pinboard icon for each icon in the doc */
1065 static void pinboard_load_from_xml(xmlDocPtr doc)
1067 xmlNodePtr node, root;
1068 char *tmp, *label, *path;
1069 int x, y;
1071 root = xmlDocGetRootElement(doc);
1073 for (node = root->xmlChildrenNode; node; node = node->next)
1075 if (node->type != XML_ELEMENT_NODE)
1076 continue;
1077 if (strcmp(node->name, "backdrop") == 0)
1079 backdrop_from_xml(node);
1080 continue;
1082 if (strcmp(node->name, "icon") != 0)
1083 continue;
1085 tmp = xmlGetProp(node, "x");
1086 if (!tmp)
1087 continue;
1088 x = atoi(tmp);
1089 g_free(tmp);
1091 tmp = xmlGetProp(node, "y");
1092 if (!tmp)
1093 continue;
1094 y = atoi(tmp);
1095 g_free(tmp);
1097 label = xmlGetProp(node, "label");
1098 if (!label)
1099 label = g_strdup("<missing label>");
1100 path = xmlNodeGetContent(node);
1101 if (!path)
1102 path = g_strdup("<missing path>");
1104 pinboard_pin(path, label, x, y);
1106 g_free(path);
1107 g_free(label);
1111 /* Called for each line in the pinboard file while loading a new board.
1112 * Only used for old-format files when converting to XML.
1114 static const char *pin_from_file(gchar *line)
1116 gchar *leaf = NULL;
1117 int x, y, n;
1119 if (*line == '<')
1121 gchar *end;
1123 end = strchr(line + 1, '>');
1124 if (!end)
1125 return _("Missing '>' in icon label");
1127 leaf = g_strndup(line + 1, end - line - 1);
1129 line = end + 1;
1131 while (isspace(*line))
1132 line++;
1133 if (*line != ',')
1134 return _("Missing ',' after icon label");
1135 line++;
1138 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1139 return NULL; /* Ignore format errors */
1141 pinboard_pin(line + n, leaf, x, y);
1143 g_free(leaf);
1145 return NULL;
1148 /* Write the current state of the pinboard to the current pinboard file */
1149 static void pinboard_save(void)
1151 guchar *save = NULL;
1152 guchar *save_new = NULL;
1153 GList *next;
1154 xmlDocPtr doc = NULL;
1155 xmlNodePtr root;
1157 g_return_if_fail(current_pinboard != NULL);
1159 if (strchr(current_pinboard->name, '/'))
1160 save = g_strdup(current_pinboard->name);
1161 else
1163 guchar *leaf;
1165 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1166 save = choices_find_path_save(leaf, PROJECT, TRUE);
1167 g_free(leaf);
1170 if (!save)
1171 return;
1173 doc = xmlNewDoc("1.0");
1174 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1176 root = xmlDocGetRootElement(doc);
1178 if (current_pinboard->backdrop)
1180 BackdropStyle style = current_pinboard->backdrop_style;
1181 xmlNodePtr tree;
1183 tree = xmlNewTextChild(root, NULL, "backdrop",
1184 current_pinboard->backdrop);
1185 xmlSetProp(tree, "style",
1186 style == BACKDROP_TILE ? "Tiled" :
1187 style == BACKDROP_CENTRE ? "Centred" :
1188 style == BACKDROP_SCALE ? "Scaled" :
1189 "Program");
1192 for (next = current_pinboard->icons; next; next = next->next)
1194 xmlNodePtr tree;
1195 PinIcon *pi = (PinIcon *) next->data;
1196 Icon *icon = (Icon *) pi;
1197 char *tmp;
1199 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1201 tmp = g_strdup_printf("%d", pi->x);
1202 xmlSetProp(tree, "x", tmp);
1203 g_free(tmp);
1205 tmp = g_strdup_printf("%d", pi->y);
1206 xmlSetProp(tree, "y", tmp);
1207 g_free(tmp);
1209 xmlSetProp(tree, "label", icon->item->leafname);
1212 save_new = g_strconcat(save, ".new", NULL);
1213 if (save_xml_file(doc, save_new) || rename(save_new, save))
1214 delayed_error(_("Error saving pinboard %s: %s"),
1215 save, g_strerror(errno));
1216 g_free(save_new);
1218 g_free(save);
1219 if (doc)
1220 xmlFreeDoc(doc);
1223 static void snap_to_grid(int *x, int *y)
1225 int step = o_pinboard_grid_step.int_value;
1227 *x = ((*x + step / 2) / step) * step;
1228 *y = ((*y + step / 2) / step) * step;
1231 /* Convert (x,y) from a centre point to a window position */
1232 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1234 gboolean clamp = o_pinboard_clamp_icons.int_value;
1235 GtkRequisition req;
1237 gtk_widget_size_request(pi->win, &req);
1239 *x -= req.width >> 1;
1240 *y -= req.height >> 1;
1241 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1242 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1245 /* Same as drag_set_dest(), but for pinboard icons */
1246 static void drag_set_pinicon_dest(PinIcon *pi)
1248 GtkObject *obj = GTK_OBJECT(pi->win);
1250 make_drop_target(pi->win, 0);
1252 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1253 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1254 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1257 /* Called during the drag when the mouse is in a widget registered
1258 * as a drop target. Returns TRUE if we can accept the drop.
1260 static gboolean drag_motion(GtkWidget *widget,
1261 GdkDragContext *context,
1262 gint x,
1263 gint y,
1264 guint time,
1265 PinIcon *pi)
1267 GdkDragAction action = context->suggested_action;
1268 char *type = NULL;
1269 Icon *icon = (Icon *) pi;
1270 DirItem *item = icon->item;
1272 if (gtk_drag_get_source_widget(context) == widget)
1273 goto out; /* Can't drag something to itself! */
1275 if (icon->selected)
1276 goto out; /* Can't drag a selection to itself */
1278 type = dnd_motion_item(context, &item);
1280 if (!item)
1281 type = NULL;
1282 out:
1283 /* We actually must pretend to accept the drop, even if the
1284 * directory isn't writeable, so that the spring-opening
1285 * thing works.
1288 /* Don't allow drops to non-writeable directories */
1289 if (o_dnd_spring_open.int_value == FALSE &&
1290 type == drop_dest_dir &&
1291 access(icon->path, W_OK) != 0)
1293 type = NULL;
1296 g_dataset_set_data(context, "drop_dest_type", type);
1297 if (type)
1299 gdk_drag_status(context, action, time);
1300 g_dataset_set_data_full(context, "drop_dest_path",
1301 g_strdup(icon->path), g_free);
1302 if (type == drop_dest_dir)
1303 dnd_spring_load(context, NULL);
1305 pinboard_wink_item(pi, FALSE);
1307 else
1308 gdk_drag_status(context, 0, time);
1310 /* Always return TRUE to stop the pinboard getting the events */
1311 return TRUE;
1314 static gboolean pinboard_shadow = FALSE;
1315 static gint shadow_x, shadow_y;
1316 #define SHADOW_SIZE (ICON_WIDTH)
1318 static gboolean bg_expose(GtkWidget *widget,
1319 GdkEventExpose *event, gpointer data)
1321 gpointer gclass = ((GTypeInstance *) widget)->g_class;
1323 /* TODO: Split large regions into smaller chunks... */
1325 gdk_window_begin_paint_region(widget->window, event->region);
1327 if (pinboard_shadow)
1329 gdk_draw_rectangle(widget->window,
1330 widget->style->white_gc, FALSE,
1331 shadow_x, shadow_y,
1332 SHADOW_SIZE, SHADOW_SIZE);
1333 gdk_draw_rectangle(widget->window,
1334 widget->style->black_gc, FALSE,
1335 shadow_x + 1, shadow_y + 1,
1336 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1339 ((GtkWidgetClass *) gclass)->expose_event(widget, event);
1341 gdk_window_end_paint(widget->window);
1343 return TRUE;
1346 /* Draw a 'shadow' under an icon being dragged, showing where
1347 * it will land.
1349 static void pinboard_set_shadow(gboolean on)
1351 GdkRectangle area;
1353 if (pinboard_shadow)
1355 area.x = shadow_x;
1356 area.y = shadow_y;
1357 area.width = SHADOW_SIZE + 1;
1358 area.height = SHADOW_SIZE + 1;
1360 gdk_window_invalidate_rect(current_pinboard->window->window,
1361 &area, TRUE);
1364 if (on)
1366 int old_x = shadow_x, old_y = shadow_y;
1368 gdk_window_get_pointer(current_pinboard->fixed->window,
1369 &shadow_x, &shadow_y, NULL);
1370 snap_to_grid(&shadow_x, &shadow_y);
1371 shadow_x -= SHADOW_SIZE / 2;
1372 shadow_y -= SHADOW_SIZE / 2;
1375 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1376 return;
1378 area.x = shadow_x;
1379 area.y = shadow_y;
1380 area.width = SHADOW_SIZE + 1;
1381 area.height = SHADOW_SIZE + 1;
1383 gdk_window_invalidate_rect(current_pinboard->window->window,
1384 &area, TRUE);
1387 pinboard_shadow = on;
1390 /* Called when dragging some pinboard icons finishes */
1391 void pinboard_move_icons(void)
1393 int x = shadow_x, y = shadow_y;
1394 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1395 int width, height;
1397 g_return_if_fail(pi != NULL);
1399 x += SHADOW_SIZE / 2;
1400 y += SHADOW_SIZE / 2;
1401 snap_to_grid(&x, &y);
1403 if (pi->x == x && pi->y == y)
1404 return;
1406 pi->x = x;
1407 pi->y = y;
1408 gdk_drawable_get_size(pi->win->window, &width, &height);
1409 offset_from_centre(pi, &x, &y);
1411 fixed_move_fast(GTK_FIXED(current_pinboard->fixed), pi->win, x, y);
1413 pinboard_save();
1416 static void drag_leave(GtkWidget *widget,
1417 GdkDragContext *context,
1418 guint32 time,
1419 PinIcon *pi)
1421 pinboard_wink_item(NULL, FALSE);
1422 dnd_spring_abort();
1425 static gboolean bg_drag_leave(GtkWidget *widget,
1426 GdkDragContext *context,
1427 guint32 time,
1428 gpointer data)
1430 pinboard_set_shadow(FALSE);
1431 return TRUE;
1434 static gboolean bg_drag_motion(GtkWidget *widget,
1435 GdkDragContext *context,
1436 gint x,
1437 gint y,
1438 guint time,
1439 gpointer data)
1441 /* Dragging from the pinboard to the pinboard is not allowed */
1443 if (!provides(context, text_uri_list))
1444 return FALSE;
1446 pinboard_set_shadow(TRUE);
1448 gdk_drag_status(context,
1449 context->suggested_action == GDK_ACTION_ASK
1450 ? GDK_ACTION_LINK : context->suggested_action,
1451 time);
1452 return TRUE;
1455 static void drag_end(GtkWidget *widget,
1456 GdkDragContext *context,
1457 PinIcon *pi)
1459 pinboard_drag_in_progress = NULL;
1460 if (tmp_icon_selected)
1462 icon_select_only(NULL);
1463 tmp_icon_selected = FALSE;
1467 /* Something which affects all the icons has changed - reshape
1468 * and redraw all of them.
1470 static void reshape_all(void)
1472 GList *next;
1474 g_return_if_fail(current_pinboard != NULL);
1476 for (next = current_pinboard->icons; next; next = next->next)
1478 Icon *icon = (Icon *) next->data;
1479 pinboard_reshape_icon(icon);
1483 /* Turns off the pinboard. Does not call gtk_main_quit. */
1484 static void pinboard_clear(void)
1486 GList *next;
1488 g_return_if_fail(current_pinboard != NULL);
1490 tasklist_set_active(FALSE);
1492 next = current_pinboard->icons;
1493 while (next)
1495 PinIcon *pi = (PinIcon *) next->data;
1497 next = next->next;
1499 gtk_widget_destroy(pi->win);
1502 gtk_widget_destroy(current_pinboard->window);
1504 abandon_backdrop_app(current_pinboard);
1506 g_free(current_pinboard->name);
1507 g_free(current_pinboard);
1508 current_pinboard = NULL;
1510 number_of_windows--;
1513 static gpointer parent_class;
1515 static void pin_icon_destroy(Icon *icon)
1517 PinIcon *pi = (PinIcon *) icon;
1519 g_return_if_fail(pi->win != NULL);
1521 gtk_widget_destroy(pi->win);
1524 static void pinboard_remove_items(void)
1526 g_return_if_fail(icon_selection != NULL);
1528 while (icon_selection)
1529 icon_destroy((Icon *) icon_selection->data);
1531 pinboard_save();
1534 static void pin_icon_update(Icon *icon)
1536 pinboard_reshape_icon(icon);
1537 pinboard_save();
1540 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1542 return IS_PIN_ICON(other);
1545 static void pin_icon_class_init(gpointer gclass, gpointer data)
1547 IconClass *icon = (IconClass *) gclass;
1549 parent_class = g_type_class_peek_parent(gclass);
1551 icon->destroy = pin_icon_destroy;
1552 icon->redraw = pinboard_reshape_icon;
1553 icon->update = pin_icon_update;
1554 icon->remove_items = pinboard_remove_items;
1555 icon->same_group = pin_icon_same_group;
1558 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1562 static GType pin_icon_get_type(void)
1564 static GType type = 0;
1566 if (!type)
1568 static const GTypeInfo info =
1570 sizeof (PinIconClass),
1571 NULL, /* base_init */
1572 NULL, /* base_finalise */
1573 pin_icon_class_init,
1574 NULL, /* class_finalise */
1575 NULL, /* class_data */
1576 sizeof(PinIcon),
1577 0, /* n_preallocs */
1578 pin_icon_init
1581 type = g_type_register_static(icon_get_type(),
1582 "PinIcon", &info, 0);
1585 return type;
1588 static PinIcon *pin_icon_new(const char *pathname, const char *name)
1590 PinIcon *pi;
1591 Icon *icon;
1593 pi = g_object_new(pin_icon_get_type(), NULL);
1594 icon = (Icon *) pi;
1596 icon_set_path(icon, pathname, name);
1598 return pi;
1601 /* Called when the window widget is somehow destroyed */
1602 static void pin_icon_destroyed(PinIcon *pi)
1604 g_return_if_fail(pi->win != NULL);
1606 pi->win = NULL;
1608 pinboard_wink_item(NULL, FALSE);
1610 if (pinboard_drag_in_progress == (Icon *) pi)
1611 pinboard_drag_in_progress = NULL;
1613 if (current_pinboard)
1614 current_pinboard->icons =
1615 g_list_remove(current_pinboard->icons, pi);
1617 g_object_unref(pi);
1620 /* Set the tooltip */
1621 static void pin_icon_set_tip(PinIcon *pi)
1623 XMLwrapper *ai;
1624 xmlNode *node;
1625 Icon *icon = (Icon *) pi;
1627 g_return_if_fail(pi != NULL);
1629 ai = appinfo_get(icon->path, icon->item);
1631 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
1633 guchar *str;
1634 str = xmlNodeListGetString(node->doc,
1635 node->xmlChildrenNode, 1);
1636 if (str)
1638 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
1639 g_free(str);
1642 else
1643 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
1645 if (ai)
1646 g_object_unref(ai);
1649 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
1651 int pos[3];
1653 pos[0] = event->x_root;
1654 pos[1] = event->y_root;
1655 pos[2] = 1;
1657 icon_prepare_menu((Icon *) pi, TRUE);
1659 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
1660 position_menu,
1661 (gpointer) pos, event->button, event->time);
1664 static void create_pinboard_window(Pinboard *pinboard)
1666 GtkWidget *win;
1668 g_return_if_fail(pinboard->window == NULL);
1670 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1672 gtk_widget_modify_bg(win, GTK_STATE_NORMAL, &pin_text_bg_col);
1674 gtk_widget_set_double_buffered(win, FALSE);
1675 gtk_widget_set_app_paintable(win, TRUE);
1676 gtk_widget_set_name(win, "rox-pinboard");
1677 pinboard->window = win;
1678 pinboard->fixed = gtk_fixed_new();
1679 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
1681 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
1683 gtk_widget_set_size_request(win, screen_width, screen_height);
1684 gtk_widget_realize(win);
1685 gtk_window_move(GTK_WINDOW(win), 0, 0);
1686 make_panel_window(win);
1688 /* TODO: Use gdk function when it supports this type */
1690 GdkAtom desktop_type;
1692 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1693 FALSE);
1694 gdk_property_change(win->window,
1695 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
1696 gdk_atom_intern("ATOM", FALSE), 32,
1697 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
1700 gtk_widget_add_events(win,
1701 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1702 GDK_EXPOSURE_MASK);
1703 g_signal_connect(win, "button-press-event",
1704 G_CALLBACK(button_press_event), NULL);
1705 g_signal_connect(win, "button-release-event",
1706 G_CALLBACK(button_release_event), NULL);
1707 g_signal_connect(pinboard->fixed, "expose_event",
1708 G_CALLBACK(bg_expose), NULL);
1710 /* Drag and drop handlers */
1711 drag_set_pinboard_dest(win);
1712 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
1713 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
1715 gtk_widget_show_all(win);
1716 gdk_window_lower(win->window);
1719 /* Load image 'path' and scale according to 'style' */
1720 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
1722 GdkPixmap *pixmap;
1723 GdkPixbuf *pixbuf;
1724 GError *error = NULL;
1726 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1727 if (error)
1729 delayed_error(_("Error loading backdrop image:\n%s\n"
1730 "Backdrop removed."),
1731 error->message);
1732 g_error_free(error);
1733 set_backdrop(NULL, BACKDROP_NONE);
1734 return NULL;
1737 if (style == BACKDROP_SCALE)
1739 GdkPixbuf *old = pixbuf;
1741 pixbuf = gdk_pixbuf_scale_simple(old,
1742 screen_width, screen_height,
1743 GDK_INTERP_HYPER);
1745 g_object_unref(old);
1747 else if (style == BACKDROP_CENTRE)
1749 GdkPixbuf *old = pixbuf;
1750 int x, y, width, height;
1752 width = gdk_pixbuf_get_width(pixbuf);
1753 height = gdk_pixbuf_get_height(pixbuf);
1755 pixbuf = gdk_pixbuf_new(
1756 gdk_pixbuf_get_colorspace(pixbuf), 0,
1757 8, screen_width, screen_height);
1758 gdk_pixbuf_fill(pixbuf, 0);
1760 x = (screen_width - width) / 2;
1761 y = (screen_height - height) / 2;
1762 x = MAX(x, 0);
1763 y = MAX(y, 0);
1765 gdk_pixbuf_composite(old, pixbuf,
1766 x, y,
1767 MIN(screen_width, width),
1768 MIN(screen_height, height),
1769 x, y, 1, 1,
1770 GDK_INTERP_NEAREST, 255);
1771 g_object_unref(old);
1774 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
1775 &pixmap, NULL, 0);
1776 g_object_unref(pixbuf);
1778 return pixmap;
1781 static void abandon_backdrop_app(Pinboard *pinboard)
1783 g_return_if_fail(pinboard != NULL);
1785 if (pinboard->to_backdrop_app != -1)
1787 close(pinboard->to_backdrop_app);
1788 close(pinboard->from_backdrop_app);
1789 gtk_input_remove(pinboard->input_tag);
1790 g_string_free(pinboard->input_buffer, TRUE);
1791 pinboard->to_backdrop_app = -1;
1792 pinboard->from_backdrop_app = -1;
1793 pinboard->input_tag = -1;
1794 pinboard->input_buffer = NULL;
1797 g_return_if_fail(pinboard->to_backdrop_app == -1);
1798 g_return_if_fail(pinboard->from_backdrop_app == -1);
1799 g_return_if_fail(pinboard->input_tag == -1);
1800 g_return_if_fail(pinboard->input_buffer == NULL);
1803 /* A single line has been read from the child.
1804 * Processes the command, and replies 'ok' (or abandons the child on error).
1806 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
1808 BackdropStyle style;
1809 const char *ok = "ok\n";
1811 if (strncmp(command, "tile ", 5) == 0)
1813 style = BACKDROP_TILE;
1814 command += 5;
1816 else if (strncmp(command, "scale ", 6) == 0)
1818 style = BACKDROP_SCALE;
1819 command += 6;
1821 else if (strncmp(command, "centre ", 7) == 0)
1823 style = BACKDROP_CENTRE;
1824 command += 7;
1826 else
1828 g_warning("Invalid command '%s' from backdrop app\n",
1829 command);
1830 abandon_backdrop_app(pinboard);
1831 return;
1834 while (*ok)
1836 int sent;
1838 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
1839 if (sent <= 0)
1841 /* Remote app quit? Not an error. */
1842 abandon_backdrop_app(pinboard);
1843 break;
1845 ok += sent;
1848 reload_backdrop(pinboard, command, style);
1851 static void backdrop_from_child(Pinboard *pinboard,
1852 int src, GdkInputCondition cond)
1854 char buf[256];
1855 int got;
1857 got = read(src, buf, sizeof(buf));
1859 if (got <= 0)
1861 if (got < 0)
1862 g_warning("backdrop_from_child: %s\n",
1863 g_strerror(errno));
1864 abandon_backdrop_app(pinboard);
1865 return;
1868 g_string_append_len(pinboard->input_buffer, buf, got);
1870 while (pinboard->from_backdrop_app != -1)
1872 int len;
1873 char *nl, *command;
1875 nl = strchr(pinboard->input_buffer->str, '\n');
1876 if (!nl)
1877 return; /* Haven't got a whole line yet */
1879 len = nl - pinboard->input_buffer->str;
1880 command = g_strndup(pinboard->input_buffer->str, len);
1881 g_string_erase(pinboard->input_buffer, 0, len + 1);
1883 command_from_backdrop_app(pinboard, command);
1885 g_free(command);
1889 static void reload_backdrop(Pinboard *pinboard,
1890 const gchar *backdrop,
1891 BackdropStyle backdrop_style)
1893 GtkStyle *style;
1895 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
1897 const char *argv[] = {NULL, "--backdrop", NULL};
1898 GError *error = NULL;
1900 g_return_if_fail(pinboard->to_backdrop_app == -1);
1901 g_return_if_fail(pinboard->from_backdrop_app == -1);
1902 g_return_if_fail(pinboard->input_tag == -1);
1903 g_return_if_fail(pinboard->input_buffer == NULL);
1905 argv[0] = make_path(backdrop, "AppRun")->str;
1907 /* Run the program. It'll send us a SOAP message and we'll
1908 * get back here with a different style and image.
1911 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
1912 G_SPAWN_DO_NOT_REAP_CHILD |
1913 G_SPAWN_SEARCH_PATH,
1914 NULL, NULL, /* Child setup fn */
1915 NULL, /* Child PID */
1916 &pinboard->to_backdrop_app,
1917 &pinboard->from_backdrop_app,
1918 NULL, /* Standard error */
1919 &error))
1921 pinboard->input_buffer = g_string_new(NULL);
1922 pinboard->input_tag = gtk_input_add_full(
1923 pinboard->from_backdrop_app,
1924 GDK_INPUT_READ,
1925 (GdkInputFunction) backdrop_from_child,
1926 NULL, pinboard, NULL);
1928 else
1930 delayed_error("%s", error ? error->message : "(null)");
1931 g_error_free(error);
1933 return;
1936 /* Note: Copying a style does not ref the pixmaps! */
1938 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
1939 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
1941 if (backdrop)
1942 style->bg_pixmap[GTK_STATE_NORMAL] =
1943 load_backdrop(backdrop, backdrop_style);
1945 gdk_color_parse(o_pinboard_bg_colour.value,
1946 &style->bg[GTK_STATE_NORMAL]);
1948 gtk_widget_set_style(pinboard->window, style);
1950 g_object_unref(style);
1952 gtk_widget_queue_draw(pinboard->window);
1954 /* Also update root window property (for transparent xterms, etc) */
1955 if (style->bg_pixmap[GTK_STATE_NORMAL])
1957 XID id = GDK_DRAWABLE_XID(style->bg_pixmap[GTK_STATE_NORMAL]);
1958 gdk_property_change(gdk_get_default_root_window(),
1959 gdk_atom_intern("_XROOTPMAP_ID", FALSE),
1960 gdk_atom_intern("PIXMAP", FALSE),
1961 32, GDK_PROP_MODE_REPLACE,
1962 (guchar *) &id, 1);
1964 else
1966 gdk_property_delete(gdk_get_default_root_window(),
1967 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
1971 /* Set and save (path, style) as the new backdrop.
1972 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
1973 * Otherwise, the image is displayed now.
1975 static void set_backdrop(const gchar *path, BackdropStyle style)
1977 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
1978 (path != NULL && style != BACKDROP_NONE));
1980 if (!current_pinboard)
1982 if (!path)
1983 return;
1984 pinboard_activate("Default");
1985 delayed_error(_("No pinboard was in use... "
1986 "the 'Default' pinboard has been selected. "
1987 "Use 'rox -p=Default' to turn it on in "
1988 "future."));
1989 g_return_if_fail(current_pinboard != NULL);
1992 /* We might have just run the old backdrop program and now
1993 * we're going to set a new one! Seems a bit mean...
1996 abandon_backdrop_app(current_pinboard);
1998 g_free(current_pinboard->backdrop);
1999 current_pinboard->backdrop = g_strdup(path);
2000 current_pinboard->backdrop_style = style;
2001 reload_backdrop(current_pinboard,
2002 current_pinboard->backdrop,
2003 current_pinboard->backdrop_style);
2005 pinboard_save();
2008 #define SEARCH_STEP 32
2010 static void search_free(GdkRectangle *rect, GdkRegion *used,
2011 int *outer, int od, int omax,
2012 int *inner, int id, int imax)
2014 *outer = od > 0 ? 0 : omax;
2015 while (*outer >= 0 && *outer <= omax)
2017 *inner = id > 0 ? 0 : imax;
2018 while (*inner >= 0 && *inner <= imax)
2020 if (gdk_region_rect_in(used, rect) ==
2021 GDK_OVERLAP_RECTANGLE_OUT)
2022 return;
2023 *inner += id;
2026 *outer += od;
2029 rect->x = -1;
2030 rect->y = -1;
2033 /* Finds a free area on the pinboard large enough for the width and height
2034 * of the given rectangle, by filling in the x and y fields of 'rect'.
2035 * The search order respects user preferences.
2036 * If no area is free, returns any old area.
2038 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect)
2040 GdkRegion *used;
2041 GList *next;
2042 GdkRectangle used_rect;
2043 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2045 used = gdk_region_new();
2047 panel_mark_used(used);
2049 /* Subtract the used areas... */
2051 next = GTK_FIXED(pinboard->fixed)->children;
2052 for (; next; next = next->next)
2054 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2056 if (!GTK_WIDGET_VISIBLE(fix->widget))
2057 continue;
2059 used_rect.x = fix->x;
2060 used_rect.y = fix->y;
2061 used_rect.width = fix->widget->requisition.width;
2062 used_rect.height = fix->widget->requisition.height;
2064 gdk_region_union_with_rect(used, &used_rect);
2067 /* Find the first free area (yes, this isn't exactly pretty, but
2068 * it works). If you know a better (fast!) algorithm, let me know!
2072 if (o_iconify_start.int_value == CORNER_TOP_RIGHT ||
2073 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2074 dx = -SEARCH_STEP;
2076 if (o_iconify_start.int_value == CORNER_BOTTOM_LEFT ||
2077 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2078 dy = -SEARCH_STEP;
2080 if (o_iconify_dir.int_value == DIR_VERT)
2082 search_free(rect, used,
2083 &rect->x, dx, screen_width - rect->width,
2084 &rect->y, dy, screen_height - rect->height);
2086 else
2088 search_free(rect, used,
2089 &rect->y, dy, screen_height - rect->height,
2090 &rect->x, dx, screen_width - rect->width);
2093 gdk_region_destroy(used);
2095 if (rect->x == -1)
2097 rect->x = 0;
2098 rect->y = 0;
2102 /* Icon's size, shape or appearance has changed - update the display */
2103 static void pinboard_reshape_icon(Icon *icon)
2105 PinIcon *pi = (PinIcon *) icon;
2106 int x = pi->x, y = pi->y;
2108 set_size_and_style(pi);
2109 offset_from_centre(pi, &x, &y);
2111 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
2113 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
2114 pi->win, x, y);