r1497: Untranslated string (Andras Mohari).
[rox-filer.git] / ROX-Filer / src / pinboard.c
blob157ce81ee1859df54b1855b9acc5be916c16ce6c
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pinboard.c - icons on the desktop background */
24 #include "config.h"
26 #include <ctype.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkx.h>
32 #include <gtk/gtkinvisible.h>
33 #include <stdlib.h>
34 #include <libxml/parser.h>
35 #include <signal.h>
37 #include "global.h"
39 #include "pinboard.h"
40 #include "main.h"
41 #include "dnd.h"
42 #include "pixmaps.h"
43 #include "type.h"
44 #include "choices.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "options.h"
48 #include "diritem.h"
49 #include "bind.h"
50 #include "icon.h"
51 #include "run.h"
52 #include "appinfo.h"
53 #include "menu.h"
54 #include "xml.h"
55 #include "tasklist.h"
56 #include "panel.h" /* For panel_mark_used() */
58 static gboolean tmp_icon_selected = FALSE; /* When dragging */
60 struct _Pinboard {
61 guchar *name; /* Leaf name */
62 GList *icons;
63 GtkStyle *style;
65 gchar *backdrop; /* Pathname */
66 BackdropStyle backdrop_style;
67 gint to_backdrop_app; /* pipe FD, or -1 */
68 gint from_backdrop_app; /* pipe FD, or -1 */
69 gint input_tag;
70 GString *input_buffer;
72 GtkWidget *window; /* Screen-sized window */
73 GtkWidget *fixed;
76 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
78 typedef struct _PinIconClass PinIconClass;
79 typedef struct _PinIcon PinIcon;
81 struct _PinIconClass {
82 IconClass parent;
85 struct _PinIcon {
86 Icon icon;
88 int x, y;
89 GtkWidget *win;
90 GtkWidget *widget; /* The drawing area for the icon */
91 GtkWidget *label;
94 /* The number of pixels between the bottom of the image and the top
95 * of the text.
97 #define GAP 4
99 /* The size of the border around the icon which is used when winking */
100 #define WINK_FRAME 2
102 /* Grid sizes */
103 #define GRID_STEP_FINE 2
104 #define GRID_STEP_MED 16
105 #define GRID_STEP_COARSE 32
107 /* Used in options */
108 #define CORNER_TOP_LEFT 0
109 #define CORNER_TOP_RIGHT 1
110 #define CORNER_BOTTOM_LEFT 2
111 #define CORNER_BOTTOM_RIGHT 3
113 #define DIR_HORZ 0
114 #define DIR_VERT 1
116 static PinIcon *current_wink_icon = NULL;
117 static gint wink_timeout;
119 /* Used for the text colours (only) in the icons (and tasklist windows) */
120 GdkColor pin_text_fg_col, pin_text_bg_col;
122 /* Style that all the icons should use. NULL => regenerate from text_fg/bg */
123 static GtkStyle *pinicon_style = NULL;
125 Pinboard *current_pinboard = NULL;
126 static gint loading_pinboard = 0; /* Non-zero => loading */
128 /* The Icon that was used to start the current drag, if any */
129 Icon *pinboard_drag_in_progress = NULL;
131 typedef enum {
132 TEXT_BG_NONE = 0,
133 TEXT_BG_OUTLINE = 1,
134 TEXT_BG_SOLID = 2,
135 } TextBgType;
137 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
138 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
139 static Option o_pinboard_tasklist, o_forward_button_3;
140 static Option o_iconify_start, o_iconify_dir;
142 /* Static prototypes */
143 static GType pin_icon_get_type(void);
144 static void set_size_and_style(PinIcon *pi);
145 static gint stop_expose(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
146 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
147 static gint end_wink(gpointer data);
148 static gboolean button_release_event(GtkWidget *widget,
149 GdkEventButton *event,
150 PinIcon *pi);
151 static gboolean enter_notify(GtkWidget *widget,
152 GdkEventCrossing *event,
153 PinIcon *pi);
154 static gboolean button_press_event(GtkWidget *widget,
155 GdkEventButton *event,
156 PinIcon *pi);
157 static gint icon_motion_notify(GtkWidget *widget,
158 GdkEventMotion *event,
159 PinIcon *pi);
160 static const char *pin_from_file(gchar *line);
161 static void snap_to_grid(int *x, int *y);
162 static void offset_from_centre(PinIcon *pi, int *x, int *y);
163 static gboolean drag_motion(GtkWidget *widget,
164 GdkDragContext *context,
165 gint x,
166 gint y,
167 guint time,
168 PinIcon *pi);
169 static void drag_set_pinicon_dest(PinIcon *pi);
170 static void drag_leave(GtkWidget *widget,
171 GdkDragContext *context,
172 guint32 time,
173 PinIcon *pi);
174 static gboolean bg_drag_motion(GtkWidget *widget,
175 GdkDragContext *context,
176 gint x,
177 gint y,
178 guint time,
179 gpointer data);
180 static gboolean bg_drag_leave(GtkWidget *widget,
181 GdkDragContext *context,
182 guint32 time,
183 gpointer data);
184 static gboolean bg_expose(GtkWidget *window,
185 GdkEventExpose *event, gpointer data);
186 static void drag_end(GtkWidget *widget,
187 GdkDragContext *context,
188 PinIcon *pi);
189 static void reshape_all(void);
190 static void pinboard_check_options(void);
191 static void pinboard_load_from_xml(xmlDocPtr doc);
192 static void pinboard_clear(void);
193 static void pinboard_save(void);
194 static PinIcon *pin_icon_new(const char *pathname, const char *name);
195 static void pin_icon_destroyed(PinIcon *pi);
196 static void pin_icon_set_tip(PinIcon *pi);
197 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
198 static void create_pinboard_window(Pinboard *pinboard);
199 static void reload_backdrop(Pinboard *pinboard,
200 const gchar *backdrop,
201 BackdropStyle backdrop_style);
202 static void set_backdrop(const gchar *path, BackdropStyle style);
203 void pinboard_reshape_icon(Icon *icon);
204 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
205 static void abandon_backdrop_app(Pinboard *pinboard);
206 static void drag_backdrop_dropped(GtkWidget *frame,
207 GdkDragContext *context,
208 gint x,
209 gint y,
210 GtkSelectionData *selection_data,
211 guint drag_info,
212 guint32 time,
213 GtkWidget *dialog);
214 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
215 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect);
218 /****************************************************************
219 * EXTERNAL INTERFACE *
220 ****************************************************************/
222 void pinboard_init(void)
224 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
225 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
227 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
228 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
229 GRID_STEP_COARSE);
230 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
231 option_add_int(&o_forward_button_3, "pinboard_forward_button_3", FALSE);
233 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
234 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
236 option_add_notify(pinboard_check_options);
238 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
239 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
242 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
243 * and make it the current pinboard.
244 * Any existing pinned items are removed. You must call this
245 * at least once before using the pinboard. NULL disables the
246 * pinboard.
248 void pinboard_activate(const gchar *name)
250 Pinboard *old_board = current_pinboard;
251 guchar *path, *slash;
253 /* Treat an empty name the same as NULL */
254 if (name && !*name)
255 name = NULL;
257 if (old_board)
258 pinboard_clear();
260 if (!name)
262 if (number_of_windows < 1 && gtk_main_level() > 0)
263 gtk_main_quit();
264 return;
267 number_of_windows++;
269 slash = strchr(name, '/');
270 if (slash)
272 if (access(name, F_OK))
273 path = NULL; /* File does not (yet) exist */
274 else
275 path = g_strdup(name);
277 else
279 guchar *leaf;
281 leaf = g_strconcat("pb_", name, NULL);
282 path = choices_find_path_load(leaf, PROJECT);
283 g_free(leaf);
286 current_pinboard = g_new(Pinboard, 1);
287 current_pinboard->name = g_strdup(name);
288 current_pinboard->icons = NULL;
289 current_pinboard->window = NULL;
290 current_pinboard->backdrop = NULL;
291 current_pinboard->backdrop_style = BACKDROP_NONE;
292 current_pinboard->to_backdrop_app = -1;
293 current_pinboard->from_backdrop_app = -1;
294 current_pinboard->input_tag = -1;
295 current_pinboard->input_buffer = NULL;
297 create_pinboard_window(current_pinboard);
299 loading_pinboard++;
300 if (path)
302 xmlDocPtr doc;
303 doc = xmlParseFile(path);
304 if (doc)
306 pinboard_load_from_xml(doc);
307 xmlFreeDoc(doc);
308 reload_backdrop(current_pinboard,
309 current_pinboard->backdrop,
310 current_pinboard->backdrop_style);
312 else
314 parse_file(path, pin_from_file);
315 info_message(_("Your old pinboard file has been "
316 "converted to the new XML format."));
317 pinboard_save();
319 g_free(path);
321 else
322 pinboard_pin(home_dir, "Home",
323 4 + ICON_WIDTH / 2,
324 4 + ICON_HEIGHT / 2);
325 loading_pinboard--;
327 if (o_pinboard_tasklist.int_value)
328 tasklist_set_active(TRUE);
331 /* Return the window of the current pinboard, or NULL.
332 * Used to make sure lowering the panels doesn't lose them...
334 GdkWindow *pinboard_get_window(void)
336 if (current_pinboard)
337 return current_pinboard->window->window;
338 return NULL;
341 const char *pinboard_get_name(void)
343 g_return_val_if_fail(current_pinboard != NULL, NULL);
345 return current_pinboard->name;
348 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
349 * being cleared.
351 void pinboard_add_widget(GtkWidget *widget)
353 GtkRequisition req;
354 GdkRectangle rect;
356 g_return_if_fail(current_pinboard != NULL);
358 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
360 gtk_widget_size_request(widget, &req);
362 rect.width = req.width;
363 rect.height = req.height;
364 find_free_rect(current_pinboard, &rect);
366 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
367 widget, rect.x, rect.y);
370 /* Add a new icon to the background.
371 * 'path' should be an absolute pathname.
372 * 'x' and 'y' are the coordinates of the point in the middle of the text.
373 * 'name' is the name to use. If NULL then the leafname of path is used.
375 * name and path are in UTF-8 for Gtk+-2.0 only.
377 void pinboard_pin(const gchar *path, const gchar *name, int x, int y)
379 GtkWidget *align, *vbox;
380 GdkWindow *events;
381 PinIcon *pi;
382 Icon *icon;
384 g_return_if_fail(path != NULL);
385 g_return_if_fail(current_pinboard != NULL);
387 pi = pin_icon_new(path, name);
388 icon = (Icon *) pi;
389 pi->x = x;
390 pi->y = y;
392 /* This is a bit complicated...
394 * An icon needs to be a NO_WINDOW widget so that the image can
395 * blend with the background (A ParentRelative window also works, but
396 * is slow, causes the xfree86's memory consumption to grow without
397 * bound, and doesn't even get freed when the filer quits!).
399 * However, the icon also needs to have a window, so we get events
400 * delivered correctly. The solution is to float an InputOnly window
401 * over the icon. Since GtkButton works the same way, we just use
402 * that :-)
405 /* Button takes the initial ref of Icon */
406 pi->win = gtk_button_new();
407 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
408 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
409 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
411 vbox = gtk_vbox_new(FALSE, 0);
412 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
414 align = gtk_alignment_new(0.5, 0.5, 0, 0);
415 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
416 gtk_container_add(GTK_CONTAINER(align), pi->widget);
418 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
419 drag_set_pinicon_dest(pi);
420 g_signal_connect(pi->win, "drag_data_get",
421 G_CALLBACK(drag_data_get), NULL);
423 pi->label = gtk_label_new(icon->item->leafname);
424 gtk_label_set_line_wrap(GTK_LABEL(pi->label), TRUE);
425 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
427 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
429 snap_to_grid(&x, &y);
430 pi->x = x;
431 pi->y = y;
432 gtk_widget_show_all(pi->win);
433 pinboard_reshape_icon((Icon *) pi);
435 gtk_widget_realize(pi->win);
436 events = GTK_BUTTON(pi->win)->event_window;
437 gdk_window_set_events(events,
438 GDK_EXPOSURE_MASK |
439 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
440 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
441 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
442 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
443 g_signal_connect(pi->win, "enter-notify-event",
444 G_CALLBACK(enter_notify), pi);
445 g_signal_connect(pi->win, "button-press-event",
446 G_CALLBACK(button_press_event), pi);
447 g_signal_connect(pi->win, "button-release-event",
448 G_CALLBACK(button_release_event), pi);
449 g_signal_connect(pi->win, "motion-notify-event",
450 G_CALLBACK(icon_motion_notify), pi);
451 g_signal_connect(pi->win, "expose-event",
452 G_CALLBACK(stop_expose), pi);
453 g_signal_connect(pi->widget, "expose-event",
454 G_CALLBACK(draw_icon), pi);
455 g_signal_connect_swapped(pi->win, "destroy",
456 G_CALLBACK(pin_icon_destroyed), pi);
458 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
459 pin_icon_set_tip(pi);
461 if (!loading_pinboard)
462 pinboard_save();
465 /* Remove an icon from the pinboard */
466 /* XXX: use destroy */
467 void pinboard_unpin(PinIcon *pi)
469 g_return_if_fail(pi != NULL);
471 gtk_widget_destroy(pi->win);
472 pinboard_save();
475 /* Put a border around the icon, briefly.
476 * If icon is NULL then cancel any existing wink.
477 * The icon will automatically unhighlight unless timeout is FALSE,
478 * in which case you must call this function again (with NULL or another
479 * icon) to remove the highlight.
481 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
483 PinIcon *old = current_wink_icon;
485 if (old == pi)
486 return;
488 current_wink_icon = pi;
490 if (old)
492 gtk_widget_queue_draw(old->win);
493 gdk_window_process_updates(old->widget->window, TRUE);
495 if (wink_timeout != -1)
496 gtk_timeout_remove(wink_timeout);
499 if (pi)
501 gtk_widget_queue_draw(pi->win);
502 gdk_window_process_updates(pi->widget->window, TRUE);
504 if (timeout)
505 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
506 else
507 wink_timeout = -1;
511 /* Icon's size, shape or appearance has changed - update the display */
512 void pinboard_reshape_icon(Icon *icon)
514 PinIcon *pi = (PinIcon *) icon;
515 int x = pi->x, y = pi->y;
517 set_size_and_style(pi);
518 offset_from_centre(pi, &x, &y);
520 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
522 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
523 pi->win, x, y);
527 /* 'app' is saved as the new application to set the backdrop. It will then be
528 * run, and should communicate with the filer as described in the manual.
530 void pinboard_set_backdrop_app(const gchar *app)
532 XMLwrapper *ai;
533 DirItem *item;
534 gboolean can_set;
536 item = diritem_new("");
537 diritem_restat(app, item, NULL);
538 ai = appinfo_get(app, item);
539 diritem_free(item);
541 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
542 if (ai)
543 g_object_unref(ai);
545 if (can_set)
546 set_backdrop(app, BACKDROP_PROGRAM);
547 else
548 delayed_error(_("You can only set the backdrop to an image "
549 "or to a program which knows how to "
550 "manage ROX-Filer's backdrop."));
553 /* Open a dialog box allowing the user to set the backdrop */
554 void pinboard_set_backdrop(void)
556 GtkWidget *dialog, *frame, *label, *radio, *hbox;
557 GtkBox *vbox;
558 GtkTargetEntry targets[] = {
559 {"text/uri-list", 0, TARGET_URI_LIST},
562 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
563 GTK_DIALOG_NO_SEPARATOR,
564 GTK_STOCK_CLEAR, GTK_RESPONSE_NO,
565 GTK_STOCK_OK, GTK_RESPONSE_OK,
566 NULL);
567 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
569 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
571 label = gtk_label_new(_("Display backdrop image:"));
572 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
573 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
574 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
576 /* The Centred, Scaled, Tiled radios... */
577 hbox = gtk_hbox_new(TRUE, 2);
578 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
580 radio = gtk_radio_button_new_with_label(NULL, _("Centred"));
581 g_object_set_data(G_OBJECT(dialog), "radio_centred", radio);
582 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
584 radio = gtk_radio_button_new_with_label(
585 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
586 _("Scaled"));
587 g_object_set_data(G_OBJECT(dialog), "radio_scaled", radio);
588 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
590 radio = gtk_radio_button_new_with_label(
591 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
592 _("Tiled"));
593 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
595 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
597 /* The drop area... */
598 frame = gtk_frame_new(NULL);
599 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
600 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
601 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
603 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
604 targets, sizeof(targets) / sizeof(*targets),
605 GDK_ACTION_COPY);
606 g_signal_connect(frame, "drag_data_received",
607 G_CALLBACK(drag_backdrop_dropped), dialog);
609 label = gtk_label_new(_("Drop an image here"));
610 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
611 gtk_container_add(GTK_CONTAINER(frame), label);
613 g_signal_connect(dialog, "response",
614 G_CALLBACK(backdrop_response), NULL);
615 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
617 gtk_widget_show_all(dialog);
620 /****************************************************************
621 * INTERNAL FUNCTIONS *
622 ****************************************************************/
624 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
626 if (response == GTK_RESPONSE_NO)
627 set_backdrop(NULL, BACKDROP_NONE);
629 gtk_widget_destroy(dialog);
632 static void drag_backdrop_dropped(GtkWidget *frame,
633 GdkDragContext *context,
634 gint x,
635 gint y,
636 GtkSelectionData *selection_data,
637 guint drag_info,
638 guint32 time,
639 GtkWidget *dialog)
641 struct stat info;
642 const gchar *path = NULL;
643 GList *uris;
645 if (!selection_data->data)
646 return; /* Timeout? */
648 uris = uri_list_to_glist(selection_data->data);
650 if (g_list_length(uris) == 1)
651 path = get_local_path((guchar *) uris->data);
652 g_list_free(uris);
654 if (!path)
656 delayed_error(
657 _("You should drop a single (local) image file "
658 "onto the drop box - that image will be "
659 "used for the desktop background. You can also "
660 "drag certain applications onto this box."));
661 return;
664 if (mc_stat(path, &info))
666 delayed_error(
667 _("Can't access '%s':\n%s"), path,
668 g_strerror(errno));
669 return;
672 if (S_ISDIR(info.st_mode))
674 /* Use this program to set the backdrop */
675 pinboard_set_backdrop_app(path);
677 else if (S_ISREG(info.st_mode))
679 GtkWidget *radio;
681 radio = g_object_get_data(G_OBJECT(dialog), "radio_scaled");
682 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
684 set_backdrop(path, BACKDROP_SCALE);
685 return;
688 radio = g_object_get_data(G_OBJECT(dialog), "radio_centred");
689 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
691 set_backdrop(path, BACKDROP_CENTRE);
692 return;
695 set_backdrop(path, BACKDROP_TILE);
697 else
698 delayed_error(_("Only files (and certain applications) can be "
699 "used to set the background image."));
702 static void pinboard_check_options(void)
704 GdkColor n_fg, n_bg;
706 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
707 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
709 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
711 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
712 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0)
714 pin_text_fg_col = n_fg;
715 pin_text_bg_col = n_bg;
717 if (pinicon_style)
719 g_object_unref(G_OBJECT(pinicon_style));
720 pinicon_style = NULL;
723 if (current_pinboard)
725 GtkWidget *w = current_pinboard->window;
726 GdkColormap *cm;
728 cm = gtk_widget_get_colormap(w);
730 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
731 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
733 /* Only redraw the background if there is no image */
734 if (!current_pinboard->backdrop)
735 reload_backdrop(current_pinboard,
736 NULL, BACKDROP_NONE);
738 reshape_all();
741 tasklist_style_changed();
745 static gint end_wink(gpointer data)
747 pinboard_wink_item(NULL, FALSE);
748 return FALSE;
751 /* Updates the width, height, name_width and label fields, and resizes the
752 * window. Also sets the style to pinicon_style, generating it if needed.
754 static void set_size_and_style(PinIcon *pi)
756 Icon *icon = (Icon *) pi;
757 MaskedPixmap *image = icon->item->image;
758 int iwidth = image->width;
759 int iheight = image->height;
761 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
762 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
763 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
764 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
766 gtk_label_set_text(GTK_LABEL(pi->label), icon->item->leafname);
768 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
771 /* Don't draw the normal button effect */
772 static gint stop_expose(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
774 static GtkWidgetClass *parent_class = NULL;
776 if (!parent_class)
778 gpointer c = ((GTypeInstance *) widget)->g_class;
779 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
782 (parent_class->expose_event)(widget, event);
783 return TRUE;
786 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
788 Icon *icon = (Icon *) pi;
789 DirItem *item = icon->item;
790 MaskedPixmap *image = item->image;
791 int iwidth = image->width;
792 int iheight = image->height;
793 int x, y;
794 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
795 : GTK_STATE_NORMAL;
798 x = widget->allocation.x;
799 y = widget->allocation.y;
801 gdk_pixbuf_render_to_drawable_alpha(
802 icon->selected ? image->pixbuf_lit : image->pixbuf,
803 widget->window,
804 0, 0, /* src */
805 x, y, /* dest */
806 iwidth, iheight,
807 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
808 GDK_RGB_DITHER_NORMAL, 0, 0);
810 if (item->flags & ITEM_FLAG_SYMLINK)
812 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
813 widget->window,
814 0, 0, /* src */
815 x, y,
816 -1, -1,
817 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
818 GDK_RGB_DITHER_NORMAL, 0, 0);
820 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
822 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
823 ? im_mounted
824 : im_unmounted;
826 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
827 widget->window,
828 0, 0, /* src */
829 x, y,
830 -1, -1,
831 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
832 GDK_RGB_DITHER_NORMAL, 0, 0);
835 if (icon->selected)
837 gtk_paint_flat_box(pi->label->style, pi->label->window,
838 state,
839 GTK_SHADOW_NONE,
840 NULL, pi->label, "text",
841 pi->label->allocation.x,
842 pi->label->allocation.y,
843 pi->label->allocation.width,
844 pi->label->allocation.height);
847 return FALSE;
850 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
852 gint x, y, width, height;
854 if (current_wink_icon != pi)
855 return FALSE;
857 x = widget->allocation.x;
858 y = widget->allocation.y;
859 width = widget->allocation.width;
860 height = widget->allocation.height;
862 gdk_draw_rectangle(widget->window,
863 pi->widget->style->white_gc,
864 FALSE,
865 x, y, width - 1, height - 1);
866 gdk_draw_rectangle(widget->window,
867 pi->widget->style->black_gc,
868 FALSE,
869 x + 1, y + 1, width - 3, height - 3);
871 return FALSE;
874 static gboolean enter_notify(GtkWidget *widget,
875 GdkEventCrossing *event,
876 PinIcon *pi)
878 icon_may_update((Icon *) pi);
880 return FALSE;
883 static void perform_action(PinIcon *pi, GdkEventButton *event)
885 BindAction action;
886 Icon *icon = (Icon *) pi;
888 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
889 event);
891 /* Actions that can happen with or without an icon */
892 switch (action)
894 case ACT_CLEAR_SELECTION:
895 icon_select_only(NULL);
896 return;
897 case ACT_POPUP_MENU:
898 dnd_motion_ungrab();
899 pinboard_show_menu(event, pi);
900 return;
901 case ACT_IGNORE:
902 return;
903 default:
904 break;
907 g_return_if_fail(pi != NULL);
909 switch (action)
911 case ACT_OPEN_ITEM:
912 dnd_motion_ungrab();
913 pinboard_wink_item(pi, TRUE);
914 if (event->type == GDK_2BUTTON_PRESS)
915 icon_set_selected(icon, FALSE);
916 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
917 break;
918 case ACT_EDIT_ITEM:
919 dnd_motion_ungrab();
920 pinboard_wink_item(pi, TRUE);
921 if (event->type == GDK_2BUTTON_PRESS)
922 icon_set_selected(icon, FALSE);
923 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
924 break;
925 case ACT_PRIME_AND_SELECT:
926 if (!icon->selected)
927 icon_select_only(icon);
928 dnd_motion_start(MOTION_READY_FOR_DND);
929 break;
930 case ACT_PRIME_AND_TOGGLE:
931 icon_set_selected(icon, !icon->selected);
932 dnd_motion_start(MOTION_READY_FOR_DND);
933 break;
934 case ACT_PRIME_FOR_DND:
935 dnd_motion_start(MOTION_READY_FOR_DND);
936 break;
937 case ACT_TOGGLE_SELECTED:
938 icon_set_selected(icon, !icon->selected);
939 break;
940 case ACT_SELECT_EXCL:
941 icon_select_only(icon);
942 break;
943 default:
944 g_warning("Unsupported action : %d\n", action);
945 break;
949 static void forward_to_root(GdkEventButton *event)
951 XButtonEvent xev;
953 if (event->type == GDK_BUTTON_PRESS)
955 xev.type = ButtonPress;
956 XUngrabPointer(gdk_display, event->time);
958 else
959 xev.type = ButtonRelease;
961 xev.window = gdk_x11_get_default_root_xwindow();
962 xev.root = xev.window;
963 xev.subwindow = None;
964 xev.time = event->time;
965 xev.x = event->x;
966 xev.y = event->y;
967 xev.x_root = event->x_root;
968 xev.y_root = event->y_root;
969 xev.state = event->state;
970 xev.button = event->button;
971 xev.same_screen = True;
973 XSendEvent(gdk_display, xev.window, False,
974 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
977 #define FORWARDED_BUTTON(pi, button) ((button) == 2 || \
978 ((button) == 3 && o_forward_button_3.int_value && !pi))
980 /* pi is NULL if this is a root event */
981 static gboolean button_release_event(GtkWidget *widget,
982 GdkEventButton *event,
983 PinIcon *pi)
985 if (FORWARDED_BUTTON(pi, event->button))
986 forward_to_root(event);
987 else if (dnd_motion_release(event))
988 return TRUE;
990 perform_action(pi, event);
992 return TRUE;
995 /* pi is NULL if this is a root event */
996 static gboolean button_press_event(GtkWidget *widget,
997 GdkEventButton *event,
998 PinIcon *pi)
1000 /* Just in case we've jumped in front of everything... */
1001 gdk_window_lower(current_pinboard->window->window);
1003 if (FORWARDED_BUTTON(pi, event->button))
1004 forward_to_root(event);
1005 else if (dnd_motion_press(widget, event))
1006 perform_action(pi, event);
1008 return TRUE;
1011 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1013 GtkWidget *widget = pi->win;
1014 Icon *icon = (Icon *) pi;
1016 if (!icon->selected)
1018 tmp_icon_selected = TRUE;
1019 icon_select_only(icon);
1022 g_return_if_fail(icon_selection != NULL);
1024 pinboard_drag_in_progress = icon;
1026 if (icon_selection->next == NULL)
1027 drag_one_item(widget, event, icon->path, icon->item, NULL);
1028 else
1030 guchar *uri_list;
1032 uri_list = icon_create_uri_list();
1033 drag_selection(widget, event, uri_list);
1034 g_free(uri_list);
1038 /* An icon is being dragged around... */
1039 static gint icon_motion_notify(GtkWidget *widget,
1040 GdkEventMotion *event,
1041 PinIcon *pi)
1043 if (motion_state == MOTION_READY_FOR_DND)
1045 if (dnd_motion_moved(event))
1046 start_drag(pi, event);
1047 return TRUE;
1050 return FALSE;
1053 static void backdrop_from_xml(xmlNode *node)
1055 gchar *style;
1057 g_free(current_pinboard->backdrop);
1058 current_pinboard->backdrop = xmlNodeGetContent(node);
1060 style = xmlGetProp(node, "style");
1062 if (style)
1064 current_pinboard->backdrop_style =
1065 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1066 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1067 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1068 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1069 BACKDROP_NONE;
1070 g_free(style);
1072 else
1073 current_pinboard->backdrop_style = BACKDROP_TILE;
1076 /* Create one pinboard icon for each icon in the doc */
1077 static void pinboard_load_from_xml(xmlDocPtr doc)
1079 xmlNodePtr node, root;
1080 char *tmp, *label, *path;
1081 int x, y;
1083 root = xmlDocGetRootElement(doc);
1085 for (node = root->xmlChildrenNode; node; node = node->next)
1087 if (node->type != XML_ELEMENT_NODE)
1088 continue;
1089 if (strcmp(node->name, "backdrop") == 0)
1091 backdrop_from_xml(node);
1092 continue;
1094 if (strcmp(node->name, "icon") != 0)
1095 continue;
1097 tmp = xmlGetProp(node, "x");
1098 if (!tmp)
1099 continue;
1100 x = atoi(tmp);
1101 g_free(tmp);
1103 tmp = xmlGetProp(node, "y");
1104 if (!tmp)
1105 continue;
1106 y = atoi(tmp);
1107 g_free(tmp);
1109 label = xmlGetProp(node, "label");
1110 if (!label)
1111 label = g_strdup("<missing label>");
1112 path = xmlNodeGetContent(node);
1113 if (!path)
1114 path = g_strdup("<missing path>");
1116 pinboard_pin(path, label, x, y);
1118 g_free(path);
1119 g_free(label);
1123 /* Called for each line in the pinboard file while loading a new board.
1124 * Only used for old-format files when converting to XML.
1126 static const char *pin_from_file(gchar *line)
1128 gchar *leaf = NULL;
1129 int x, y, n;
1131 if (*line == '<')
1133 gchar *end;
1135 end = strchr(line + 1, '>');
1136 if (!end)
1137 return _("Missing '>' in icon label");
1139 leaf = g_strndup(line + 1, end - line - 1);
1141 line = end + 1;
1143 while (isspace(*line))
1144 line++;
1145 if (*line != ',')
1146 return _("Missing ',' after icon label");
1147 line++;
1150 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1151 return NULL; /* Ignore format errors */
1153 pinboard_pin(line + n, leaf, x, y);
1155 g_free(leaf);
1157 return NULL;
1160 /* Write the current state of the pinboard to the current pinboard file */
1161 static void pinboard_save(void)
1163 guchar *save = NULL;
1164 guchar *save_new = NULL;
1165 GList *next;
1166 xmlDocPtr doc = NULL;
1167 xmlNodePtr root;
1169 g_return_if_fail(current_pinboard != NULL);
1171 if (strchr(current_pinboard->name, '/'))
1172 save = g_strdup(current_pinboard->name);
1173 else
1175 guchar *leaf;
1177 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1178 save = choices_find_path_save(leaf, PROJECT, TRUE);
1179 g_free(leaf);
1182 if (!save)
1183 return;
1185 doc = xmlNewDoc("1.0");
1186 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1188 root = xmlDocGetRootElement(doc);
1190 if (current_pinboard->backdrop)
1192 BackdropStyle style = current_pinboard->backdrop_style;
1193 xmlNodePtr tree;
1195 tree = xmlNewTextChild(root, NULL, "backdrop",
1196 current_pinboard->backdrop);
1197 xmlSetProp(tree, "style",
1198 style == BACKDROP_TILE ? "Tiled" :
1199 style == BACKDROP_CENTRE ? "Centred" :
1200 style == BACKDROP_SCALE ? "Scaled" :
1201 "Program");
1204 for (next = current_pinboard->icons; next; next = next->next)
1206 xmlNodePtr tree;
1207 PinIcon *pi = (PinIcon *) next->data;
1208 Icon *icon = (Icon *) pi;
1209 char *tmp;
1211 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1213 tmp = g_strdup_printf("%d", pi->x);
1214 xmlSetProp(tree, "x", tmp);
1215 g_free(tmp);
1217 tmp = g_strdup_printf("%d", pi->y);
1218 xmlSetProp(tree, "y", tmp);
1219 g_free(tmp);
1221 xmlSetProp(tree, "label", icon->item->leafname);
1224 save_new = g_strconcat(save, ".new", NULL);
1225 if (save_xml_file(doc, save_new) || rename(save_new, save))
1226 delayed_error(_("Error saving pinboard %s: %s"),
1227 save, g_strerror(errno));
1228 g_free(save_new);
1230 g_free(save);
1231 if (doc)
1232 xmlFreeDoc(doc);
1235 static void snap_to_grid(int *x, int *y)
1237 int step = o_pinboard_grid_step.int_value;
1239 *x = ((*x + step / 2) / step) * step;
1240 *y = ((*y + step / 2) / step) * step;
1243 /* Convert (x,y) from a centre point to a window position */
1244 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1246 gboolean clamp = o_pinboard_clamp_icons.int_value;
1247 GtkRequisition req;
1249 gtk_widget_size_request(pi->win, &req);
1251 *x -= req.width >> 1;
1252 *y -= req.height >> 1;
1253 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1254 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1257 /* Same as drag_set_dest(), but for pinboard icons */
1258 static void drag_set_pinicon_dest(PinIcon *pi)
1260 GtkObject *obj = GTK_OBJECT(pi->win);
1262 make_drop_target(pi->win, 0);
1264 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1265 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1266 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1269 /* Called during the drag when the mouse is in a widget registered
1270 * as a drop target. Returns TRUE if we can accept the drop.
1272 static gboolean drag_motion(GtkWidget *widget,
1273 GdkDragContext *context,
1274 gint x,
1275 gint y,
1276 guint time,
1277 PinIcon *pi)
1279 GdkDragAction action = context->suggested_action;
1280 char *type = NULL;
1281 Icon *icon = (Icon *) pi;
1282 DirItem *item = icon->item;
1284 if (gtk_drag_get_source_widget(context) == widget)
1285 goto out; /* Can't drag something to itself! */
1287 if (icon->selected)
1288 goto out; /* Can't drag a selection to itself */
1290 type = dnd_motion_item(context, &item);
1292 if (!item)
1293 type = NULL;
1294 out:
1295 /* We actually must pretend to accept the drop, even if the
1296 * directory isn't writeable, so that the spring-opening
1297 * thing works.
1300 /* Don't allow drops to non-writeable directories */
1301 if (o_dnd_spring_open.int_value == FALSE &&
1302 type == drop_dest_dir &&
1303 access(icon->path, W_OK) != 0)
1305 type = NULL;
1308 g_dataset_set_data(context, "drop_dest_type", type);
1309 if (type)
1311 gdk_drag_status(context, action, time);
1312 g_dataset_set_data_full(context, "drop_dest_path",
1313 g_strdup(icon->path), g_free);
1314 if (type == drop_dest_dir)
1315 dnd_spring_load(context, NULL);
1317 pinboard_wink_item(pi, FALSE);
1319 else
1320 gdk_drag_status(context, 0, time);
1322 /* Always return TRUE to stop the pinboard getting the events */
1323 return TRUE;
1326 static gboolean pinboard_shadow = FALSE;
1327 static gint shadow_x, shadow_y;
1328 #define SHADOW_SIZE (ICON_WIDTH)
1330 static gboolean bg_expose(GtkWidget *widget,
1331 GdkEventExpose *event, gpointer data)
1333 if (!pinboard_shadow)
1334 return FALSE;
1336 gdk_draw_rectangle(widget->window,
1337 widget->style->white_gc, FALSE,
1338 shadow_x, shadow_y,
1339 SHADOW_SIZE, SHADOW_SIZE);
1340 gdk_draw_rectangle(widget->window,
1341 widget->style->black_gc, FALSE,
1342 shadow_x + 1, shadow_y + 1,
1343 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1345 return FALSE;
1348 /* Draw a 'shadow' under an icon being dragged, showing where
1349 * it will land.
1351 static void pinboard_set_shadow(gboolean on)
1353 GdkRectangle area;
1355 if (pinboard_shadow)
1357 area.x = shadow_x;
1358 area.y = shadow_y;
1359 area.width = SHADOW_SIZE + 1;
1360 area.height = SHADOW_SIZE + 1;
1362 gdk_window_invalidate_rect(current_pinboard->window->window,
1363 &area, TRUE);
1366 if (on)
1368 int old_x = shadow_x, old_y = shadow_y;
1370 gdk_window_get_pointer(current_pinboard->fixed->window,
1371 &shadow_x, &shadow_y, NULL);
1372 snap_to_grid(&shadow_x, &shadow_y);
1373 shadow_x -= SHADOW_SIZE / 2;
1374 shadow_y -= SHADOW_SIZE / 2;
1377 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1378 return;
1380 area.x = shadow_x;
1381 area.y = shadow_y;
1382 area.width = SHADOW_SIZE + 1;
1383 area.height = SHADOW_SIZE + 1;
1385 gdk_window_invalidate_rect(current_pinboard->window->window,
1386 &area, TRUE);
1389 pinboard_shadow = on;
1392 /* Called when dragging some pinboard icons finishes */
1393 void pinboard_move_icons(void)
1395 int x = shadow_x, y = shadow_y;
1396 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1397 int width, height;
1399 g_return_if_fail(pi != NULL);
1401 x += SHADOW_SIZE / 2;
1402 y += SHADOW_SIZE / 2;
1403 snap_to_grid(&x, &y);
1405 if (pi->x == x && pi->y == y)
1406 return;
1408 pi->x = x;
1409 pi->y = y;
1410 gdk_drawable_get_size(pi->win->window, &width, &height);
1411 offset_from_centre(pi, &x, &y);
1413 fixed_move_fast(GTK_FIXED(current_pinboard->fixed), pi->win, x, y);
1415 pinboard_save();
1418 static void drag_leave(GtkWidget *widget,
1419 GdkDragContext *context,
1420 guint32 time,
1421 PinIcon *pi)
1423 pinboard_wink_item(NULL, FALSE);
1424 dnd_spring_abort();
1427 static gboolean bg_drag_leave(GtkWidget *widget,
1428 GdkDragContext *context,
1429 guint32 time,
1430 gpointer data)
1432 pinboard_set_shadow(FALSE);
1433 return TRUE;
1436 static gboolean bg_drag_motion(GtkWidget *widget,
1437 GdkDragContext *context,
1438 gint x,
1439 gint y,
1440 guint time,
1441 gpointer data)
1443 /* Dragging from the pinboard to the pinboard is not allowed */
1445 if (!provides(context, text_uri_list))
1446 return FALSE;
1448 pinboard_set_shadow(TRUE);
1450 gdk_drag_status(context,
1451 context->suggested_action == GDK_ACTION_ASK
1452 ? GDK_ACTION_LINK : context->suggested_action,
1453 time);
1454 return TRUE;
1457 static void drag_end(GtkWidget *widget,
1458 GdkDragContext *context,
1459 PinIcon *pi)
1461 pinboard_drag_in_progress = NULL;
1462 if (tmp_icon_selected)
1464 icon_select_only(NULL);
1465 tmp_icon_selected = FALSE;
1469 /* Something which affects all the icons has changed - reshape
1470 * and redraw all of them.
1472 static void reshape_all(void)
1474 GList *next;
1476 g_return_if_fail(current_pinboard != NULL);
1478 for (next = current_pinboard->icons; next; next = next->next)
1480 Icon *icon = (Icon *) next->data;
1481 pinboard_reshape_icon(icon);
1485 /* Turns off the pinboard. Does not call gtk_main_quit. */
1486 static void pinboard_clear(void)
1488 GList *next;
1490 g_return_if_fail(current_pinboard != NULL);
1492 tasklist_set_active(FALSE);
1494 next = current_pinboard->icons;
1495 while (next)
1497 PinIcon *pi = (PinIcon *) next->data;
1499 next = next->next;
1501 gtk_widget_destroy(pi->win);
1504 gtk_widget_destroy(current_pinboard->window);
1506 abandon_backdrop_app(current_pinboard);
1508 g_free(current_pinboard->name);
1509 g_free(current_pinboard);
1510 current_pinboard = NULL;
1512 number_of_windows--;
1515 static gpointer parent_class;
1517 static void pin_icon_destroy(Icon *icon)
1519 PinIcon *pi = (PinIcon *) icon;
1521 g_return_if_fail(pi->win != NULL);
1523 gtk_widget_destroy(pi->win);
1526 static void pinboard_remove_items(void)
1528 g_return_if_fail(icon_selection != NULL);
1530 while (icon_selection)
1531 icon_destroy((Icon *) icon_selection->data);
1533 pinboard_save();
1536 static void pin_icon_update(Icon *icon)
1538 pinboard_reshape_icon(icon);
1539 pinboard_save();
1542 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1544 return IS_PIN_ICON(other);
1547 static void pin_icon_class_init(gpointer gclass, gpointer data)
1549 IconClass *icon = (IconClass *) gclass;
1551 parent_class = g_type_class_peek_parent(gclass);
1553 icon->destroy = pin_icon_destroy;
1554 icon->redraw = pinboard_reshape_icon;
1555 icon->update = pin_icon_update;
1556 icon->remove_items = pinboard_remove_items;
1557 icon->same_group = pin_icon_same_group;
1560 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1564 static GType pin_icon_get_type(void)
1566 static GType type = 0;
1568 if (!type)
1570 static const GTypeInfo info =
1572 sizeof (PinIconClass),
1573 NULL, /* base_init */
1574 NULL, /* base_finalise */
1575 pin_icon_class_init,
1576 NULL, /* class_finalise */
1577 NULL, /* class_data */
1578 sizeof(PinIcon),
1579 0, /* n_preallocs */
1580 pin_icon_init
1583 type = g_type_register_static(icon_get_type(),
1584 "PinIcon", &info, 0);
1587 return type;
1590 static PinIcon *pin_icon_new(const char *pathname, const char *name)
1592 PinIcon *pi;
1593 Icon *icon;
1595 pi = g_object_new(pin_icon_get_type(), NULL);
1596 icon = (Icon *) pi;
1598 icon_set_path(icon, pathname, name);
1600 return pi;
1603 /* Called when the window widget is somehow destroyed */
1604 static void pin_icon_destroyed(PinIcon *pi)
1606 g_return_if_fail(pi->win != NULL);
1608 pi->win = NULL;
1610 pinboard_wink_item(NULL, FALSE);
1612 if (pinboard_drag_in_progress == (Icon *) pi)
1613 pinboard_drag_in_progress = NULL;
1615 if (current_pinboard)
1616 current_pinboard->icons =
1617 g_list_remove(current_pinboard->icons, pi);
1619 g_object_unref(pi);
1622 /* Set the tooltip */
1623 static void pin_icon_set_tip(PinIcon *pi)
1625 XMLwrapper *ai;
1626 xmlNode *node;
1627 Icon *icon = (Icon *) pi;
1629 g_return_if_fail(pi != NULL);
1631 ai = appinfo_get(icon->path, icon->item);
1633 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
1635 guchar *str;
1636 str = xmlNodeListGetString(node->doc,
1637 node->xmlChildrenNode, 1);
1638 if (str)
1640 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
1641 g_free(str);
1644 else
1645 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
1647 if (ai)
1648 g_object_unref(ai);
1651 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
1653 int pos[3];
1655 pos[0] = event->x_root;
1656 pos[1] = event->y_root;
1657 pos[2] = 1;
1659 icon_prepare_menu((Icon *) pi, TRUE);
1661 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
1662 position_menu,
1663 (gpointer) pos, event->button, event->time);
1666 static void create_pinboard_window(Pinboard *pinboard)
1668 GtkWidget *win;
1670 g_return_if_fail(pinboard->window == NULL);
1672 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1674 gtk_widget_modify_bg(win, GTK_STATE_NORMAL, &pin_text_bg_col);
1676 gtk_widget_set_app_paintable(win, TRUE);
1677 gtk_widget_set_name(win, "rox-pinboard");
1678 pinboard->window = win;
1679 pinboard->fixed = gtk_fixed_new();
1680 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
1682 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
1684 gtk_widget_set_size_request(win, screen_width, screen_height);
1685 gtk_widget_realize(win);
1686 gtk_window_move(GTK_WINDOW(win), 0, 0);
1687 make_panel_window(win);
1689 /* TODO: Use gdk function when it supports this type */
1691 GdkAtom desktop_type;
1693 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1694 FALSE);
1695 gdk_property_change(win->window,
1696 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
1697 gdk_atom_intern("ATOM", FALSE), 32,
1698 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
1701 gtk_widget_add_events(win,
1702 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1703 GDK_EXPOSURE_MASK);
1704 g_signal_connect(win, "button-press-event",
1705 G_CALLBACK(button_press_event), NULL);
1706 g_signal_connect(win, "button-release-event",
1707 G_CALLBACK(button_release_event), NULL);
1708 g_signal_connect(pinboard->fixed, "expose_event",
1709 G_CALLBACK(bg_expose), NULL);
1711 /* Drag and drop handlers */
1712 drag_set_pinboard_dest(win);
1713 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
1714 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
1716 gtk_widget_show_all(win);
1717 gdk_window_lower(win->window);
1720 /* Load image 'path' and scale according to 'style' */
1721 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
1723 GdkPixmap *pixmap;
1724 GdkPixbuf *pixbuf;
1725 GError *error = NULL;
1727 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1728 if (error)
1730 delayed_error(_("Error loading backdrop image:\n%s\n"
1731 "Backdrop removed."),
1732 error->message);
1733 g_error_free(error);
1734 set_backdrop(NULL, BACKDROP_NONE);
1735 return NULL;
1738 if (style == BACKDROP_SCALE)
1740 GdkPixbuf *old = pixbuf;
1742 pixbuf = gdk_pixbuf_scale_simple(old,
1743 screen_width, screen_height,
1744 GDK_INTERP_HYPER);
1746 g_object_unref(old);
1748 else if (style == BACKDROP_CENTRE)
1750 GdkPixbuf *old = pixbuf;
1751 int x, y, width, height;
1753 width = gdk_pixbuf_get_width(pixbuf);
1754 height = gdk_pixbuf_get_height(pixbuf);
1756 pixbuf = gdk_pixbuf_new(
1757 gdk_pixbuf_get_colorspace(pixbuf), 0,
1758 8, screen_width, screen_height);
1759 gdk_pixbuf_fill(pixbuf, 0);
1761 x = (screen_width - width) / 2;
1762 y = (screen_height - height) / 2;
1763 x = MAX(x, 0);
1764 y = MAX(y, 0);
1766 gdk_pixbuf_composite(old, pixbuf,
1767 x, y,
1768 MIN(screen_width, width),
1769 MIN(screen_height, height),
1770 x, y, 1, 1,
1771 GDK_INTERP_NEAREST, 255);
1772 g_object_unref(old);
1775 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
1776 &pixmap, NULL, 0);
1777 g_object_unref(pixbuf);
1779 return pixmap;
1782 static void abandon_backdrop_app(Pinboard *pinboard)
1784 g_return_if_fail(pinboard != NULL);
1786 if (pinboard->to_backdrop_app != -1)
1788 close(pinboard->to_backdrop_app);
1789 close(pinboard->from_backdrop_app);
1790 gtk_input_remove(pinboard->input_tag);
1791 g_string_free(pinboard->input_buffer, TRUE);
1792 pinboard->to_backdrop_app = -1;
1793 pinboard->from_backdrop_app = -1;
1794 pinboard->input_tag = -1;
1795 pinboard->input_buffer = NULL;
1798 g_return_if_fail(pinboard->to_backdrop_app == -1);
1799 g_return_if_fail(pinboard->from_backdrop_app == -1);
1800 g_return_if_fail(pinboard->input_tag == -1);
1801 g_return_if_fail(pinboard->input_buffer == NULL);
1804 /* A single line has been read from the child.
1805 * Processes the command, and replies 'ok' (or abandons the child on error).
1807 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
1809 BackdropStyle style;
1810 const char *ok = "ok\n";
1812 if (strncmp(command, "tile ", 5) == 0)
1814 style = BACKDROP_TILE;
1815 command += 5;
1817 else if (strncmp(command, "scale ", 6) == 0)
1819 style = BACKDROP_SCALE;
1820 command += 6;
1822 else if (strncmp(command, "centre ", 7) == 0)
1824 style = BACKDROP_CENTRE;
1825 command += 7;
1827 else
1829 g_warning("Invalid command '%s' from backdrop app\n",
1830 command);
1831 abandon_backdrop_app(pinboard);
1832 return;
1835 reload_backdrop(pinboard, command, style);
1837 while (*ok)
1839 int sent;
1841 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
1842 if (sent <= 0)
1844 /* Remote app quit? Not an error. */
1845 abandon_backdrop_app(pinboard);
1846 return;
1848 ok += sent;
1852 static void backdrop_from_child(Pinboard *pinboard,
1853 int src, GdkInputCondition cond)
1855 char buf[256];
1856 int got;
1858 got = read(src, buf, sizeof(buf));
1860 if (got <= 0)
1862 if (got < 0)
1863 g_warning("backdrop_from_child: %s\n",
1864 g_strerror(errno));
1865 abandon_backdrop_app(pinboard);
1866 return;
1869 g_string_append_len(pinboard->input_buffer, buf, got);
1871 while (pinboard->from_backdrop_app != -1)
1873 int len;
1874 char *nl, *command;
1876 nl = strchr(pinboard->input_buffer->str, '\n');
1877 if (!nl)
1878 return; /* Haven't got a whole line yet */
1880 len = nl - pinboard->input_buffer->str;
1881 command = g_strndup(pinboard->input_buffer->str, len);
1882 g_string_erase(pinboard->input_buffer, 0, len + 1);
1884 command_from_backdrop_app(pinboard, command);
1886 g_free(command);
1890 static void reload_backdrop(Pinboard *pinboard,
1891 const gchar *backdrop,
1892 BackdropStyle backdrop_style)
1894 GtkStyle *style;
1896 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
1898 const char *argv[] = {NULL, "--backdrop", NULL};
1899 GError *error = NULL;
1901 g_return_if_fail(pinboard->to_backdrop_app == -1);
1902 g_return_if_fail(pinboard->from_backdrop_app == -1);
1903 g_return_if_fail(pinboard->input_tag == -1);
1904 g_return_if_fail(pinboard->input_buffer == NULL);
1906 argv[0] = make_path(backdrop, "AppRun")->str;
1908 /* Run the program. It'll send us a SOAP message and we'll
1909 * get back here with a different style and image.
1912 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
1913 G_SPAWN_DO_NOT_REAP_CHILD |
1914 G_SPAWN_SEARCH_PATH,
1915 NULL, NULL, /* Child setup fn */
1916 NULL, /* Child PID */
1917 &pinboard->to_backdrop_app,
1918 &pinboard->from_backdrop_app,
1919 NULL, /* Standard error */
1920 &error))
1922 pinboard->input_buffer = g_string_new(NULL);
1923 pinboard->input_tag = gtk_input_add_full(
1924 pinboard->from_backdrop_app,
1925 GDK_INPUT_READ,
1926 (GdkInputFunction) backdrop_from_child,
1927 NULL, pinboard, NULL);
1929 else
1931 delayed_error("%s", error ? error->message : "(null)");
1932 g_error_free(error);
1934 return;
1937 /* Note: Copying a style does not ref the pixmaps! */
1939 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
1940 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
1942 if (backdrop)
1943 style->bg_pixmap[GTK_STATE_NORMAL] =
1944 load_backdrop(backdrop, backdrop_style);
1946 gdk_color_parse(o_pinboard_bg_colour.value,
1947 &style->bg[GTK_STATE_NORMAL]);
1949 gtk_widget_set_style(pinboard->window, style);
1951 g_object_unref(style);
1953 gtk_widget_queue_draw(pinboard->window);
1956 /* Set and save (path, style) as the new backdrop.
1957 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
1958 * Otherwise, the image is displayed now.
1960 static void set_backdrop(const gchar *path, BackdropStyle style)
1962 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
1963 (path != NULL && style != BACKDROP_NONE));
1965 if (!current_pinboard)
1967 if (!path)
1968 return;
1969 pinboard_activate("Default");
1970 delayed_error(_("No pinboard was in use... "
1971 "the 'Default' pinboard has been selected. "
1972 "Use 'rox -p=Default' to turn it on in "
1973 "future."));
1974 g_return_if_fail(current_pinboard != NULL);
1977 abandon_backdrop_app(current_pinboard);
1979 g_free(current_pinboard->backdrop);
1980 current_pinboard->backdrop = g_strdup(path);
1981 current_pinboard->backdrop_style = style;
1982 reload_backdrop(current_pinboard,
1983 current_pinboard->backdrop,
1984 current_pinboard->backdrop_style);
1986 pinboard_save();
1989 #define SEARCH_STEP 32
1991 static void search_free(GdkRectangle *rect, GdkRegion *used,
1992 int *outer, int od, int omax,
1993 int *inner, int id, int imax)
1995 *outer = od > 0 ? 0 : omax;
1996 while (*outer >= 0 && *outer <= omax)
1998 *inner = id > 0 ? 0 : imax;
1999 while (*inner >= 0 && *inner <= imax)
2001 if (gdk_region_rect_in(used, rect) ==
2002 GDK_OVERLAP_RECTANGLE_OUT)
2003 return;
2004 *inner += id;
2007 *outer += od;
2010 rect->x = -1;
2011 rect->y = -1;
2014 /* Finds a free area on the pinboard large enough for the width and height
2015 * of the given rectangle, by filling in the x and y fields of 'rect'.
2016 * The search order respects user preferences.
2017 * If no area is free, returns any old area.
2019 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect)
2021 GdkRegion *used;
2022 GList *next;
2023 GdkRectangle used_rect;
2024 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2026 used = gdk_region_new();
2028 panel_mark_used(used);
2030 /* Subtract the used areas... */
2032 next = GTK_FIXED(pinboard->fixed)->children;
2033 for (; next; next = next->next)
2035 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2037 if (!GTK_WIDGET_VISIBLE(fix->widget))
2038 continue;
2040 used_rect.x = fix->x;
2041 used_rect.y = fix->y;
2042 used_rect.width = fix->widget->requisition.width;
2043 used_rect.height = fix->widget->requisition.height;
2045 gdk_region_union_with_rect(used, &used_rect);
2048 /* Find the first free area (yes, this isn't exactly pretty, but
2049 * it works). If you know a better (fast!) algorithm, let me know!
2053 if (o_iconify_start.int_value == CORNER_TOP_RIGHT ||
2054 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2055 dx = -SEARCH_STEP;
2057 if (o_iconify_start.int_value == CORNER_BOTTOM_LEFT ||
2058 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2059 dy = -SEARCH_STEP;
2061 if (o_iconify_dir.int_value == DIR_VERT)
2063 search_free(rect, used,
2064 &rect->x, dx, screen_width - rect->width,
2065 &rect->y, dy, screen_height - rect->height);
2067 else
2069 search_free(rect, used,
2070 &rect->y, dy, screen_height - rect->height,
2071 &rect->x, dx, screen_width - rect->width);
2074 gdk_region_destroy(used);
2076 if (rect->x == -1)
2078 rect->x = 0;
2079 rect->y = 0;