r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / pinboard.c
blobafcc619688d9825ddc87a241586033c1e857bc11
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;
120 PangoFontDescription *pinboard_font = NULL; /* NULL => Gtk default */
122 Pinboard *current_pinboard = NULL;
123 static gint loading_pinboard = 0; /* Non-zero => loading */
125 /* The Icon that was used to start the current drag, if any */
126 Icon *pinboard_drag_in_progress = NULL;
128 /* For selecting groups of icons */
129 static gboolean lasso_in_progress = FALSE;
130 static int lasso_rect_x1, lasso_rect_x2;
131 static int lasso_rect_y1, lasso_rect_y2;
133 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
134 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
135 static Option o_pinboard_tasklist, o_forward_buttons_13;
136 static Option o_iconify_start, o_iconify_dir;
137 static Option o_label_font;
139 /* Static prototypes */
140 static GType pin_icon_get_type(void);
141 static void set_size_and_style(PinIcon *pi);
142 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
143 static gint end_wink(gpointer data);
144 static gboolean button_release_event(GtkWidget *widget,
145 GdkEventButton *event,
146 PinIcon *pi);
147 static gboolean enter_notify(GtkWidget *widget,
148 GdkEventCrossing *event,
149 PinIcon *pi);
150 static gboolean button_press_event(GtkWidget *widget,
151 GdkEventButton *event,
152 PinIcon *pi);
153 static gint icon_motion_notify(GtkWidget *widget,
154 GdkEventMotion *event,
155 PinIcon *pi);
156 static const char *pin_from_file(gchar *line);
157 static void snap_to_grid(int *x, int *y);
158 static void offset_from_centre(PinIcon *pi, int *x, int *y);
159 static gboolean drag_motion(GtkWidget *widget,
160 GdkDragContext *context,
161 gint x,
162 gint y,
163 guint time,
164 PinIcon *pi);
165 static void drag_set_pinicon_dest(PinIcon *pi);
166 static void drag_leave(GtkWidget *widget,
167 GdkDragContext *context,
168 guint32 time,
169 PinIcon *pi);
170 static gboolean bg_drag_motion(GtkWidget *widget,
171 GdkDragContext *context,
172 gint x,
173 gint y,
174 guint time,
175 gpointer data);
176 static gboolean bg_drag_leave(GtkWidget *widget,
177 GdkDragContext *context,
178 guint32 time,
179 gpointer data);
180 static gboolean bg_expose(GtkWidget *window,
181 GdkEventExpose *event, gpointer data);
182 static void drag_end(GtkWidget *widget,
183 GdkDragContext *context,
184 PinIcon *pi);
185 static void reshape_all(void);
186 static void pinboard_check_options(void);
187 static void pinboard_load_from_xml(xmlDocPtr doc);
188 static void pinboard_clear(void);
189 static void pinboard_save(void);
190 static PinIcon *pin_icon_new(const char *pathname, const char *name);
191 static void pin_icon_destroyed(PinIcon *pi);
192 static void pin_icon_set_tip(PinIcon *pi);
193 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
194 static void create_pinboard_window(Pinboard *pinboard);
195 static void reload_backdrop(Pinboard *pinboard,
196 const gchar *backdrop,
197 BackdropStyle backdrop_style);
198 static void set_backdrop(const gchar *path, BackdropStyle style);
199 static void pinboard_reshape_icon(Icon *icon);
200 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
201 static void abandon_backdrop_app(Pinboard *pinboard);
202 static void drag_backdrop_dropped(GtkWidget *frame,
203 GdkDragContext *context,
204 gint x,
205 gint y,
206 GtkSelectionData *selection_data,
207 guint drag_info,
208 guint32 time,
209 GtkWidget *dialog);
210 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
211 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect);
212 static void update_pinboard_font(void);
213 static void draw_lasso(void);
214 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d);
217 /****************************************************************
218 * EXTERNAL INTERFACE *
219 ****************************************************************/
221 void pinboard_init(void)
223 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
224 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
225 option_add_string(&o_label_font, "label_font", "");
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_buttons_13, "pinboard_forward_buttons_13",
232 FALSE);
234 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
235 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
237 option_add_notify(pinboard_check_options);
239 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
240 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
241 update_pinboard_font();
244 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
245 * and make it the current pinboard.
246 * Any existing pinned items are removed. You must call this
247 * at least once before using the pinboard. NULL disables the
248 * pinboard.
250 void pinboard_activate(const gchar *name)
252 Pinboard *old_board = current_pinboard;
253 guchar *path, *slash;
255 /* Treat an empty name the same as NULL */
256 if (name && !*name)
257 name = NULL;
259 if (old_board)
260 pinboard_clear();
262 if (!name)
264 if (number_of_windows < 1 && gtk_main_level() > 0)
265 gtk_main_quit();
267 gdk_property_delete(gdk_get_default_root_window(),
268 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
269 return;
272 number_of_windows++;
274 slash = strchr(name, '/');
275 if (slash)
277 if (access(name, F_OK))
278 path = NULL; /* File does not (yet) exist */
279 else
280 path = g_strdup(name);
282 else
284 guchar *leaf;
286 leaf = g_strconcat("pb_", name, NULL);
287 path = choices_find_path_load(leaf, PROJECT);
288 g_free(leaf);
291 current_pinboard = g_new(Pinboard, 1);
292 current_pinboard->name = g_strdup(name);
293 current_pinboard->icons = NULL;
294 current_pinboard->window = NULL;
295 current_pinboard->backdrop = NULL;
296 current_pinboard->backdrop_style = BACKDROP_NONE;
297 current_pinboard->to_backdrop_app = -1;
298 current_pinboard->from_backdrop_app = -1;
299 current_pinboard->input_tag = -1;
300 current_pinboard->input_buffer = NULL;
302 create_pinboard_window(current_pinboard);
304 loading_pinboard++;
305 if (path)
307 xmlDocPtr doc;
308 doc = xmlParseFile(path);
309 if (doc)
311 pinboard_load_from_xml(doc);
312 xmlFreeDoc(doc);
313 reload_backdrop(current_pinboard,
314 current_pinboard->backdrop,
315 current_pinboard->backdrop_style);
317 else
319 parse_file(path, pin_from_file);
320 info_message(_("Your old pinboard file has been "
321 "converted to the new XML format."));
322 pinboard_save();
324 g_free(path);
326 else
327 pinboard_pin(home_dir, "Home",
328 4 + ICON_WIDTH / 2,
329 4 + ICON_HEIGHT / 2,
330 NULL);
331 loading_pinboard--;
333 if (o_pinboard_tasklist.int_value)
334 tasklist_set_active(TRUE);
337 /* Return the window of the current pinboard, or NULL.
338 * Used to make sure lowering the panels doesn't lose them...
340 GdkWindow *pinboard_get_window(void)
342 if (current_pinboard)
343 return current_pinboard->window->window;
344 return NULL;
347 const char *pinboard_get_name(void)
349 g_return_val_if_fail(current_pinboard != NULL, NULL);
351 return current_pinboard->name;
354 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
355 * being cleared.
357 void pinboard_add_widget(GtkWidget *widget)
359 GtkRequisition req;
360 GdkRectangle rect;
362 g_return_if_fail(current_pinboard != NULL);
364 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
366 gtk_widget_size_request(widget, &req);
368 rect.width = req.width;
369 rect.height = req.height;
370 find_free_rect(current_pinboard, &rect);
372 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
373 widget, rect.x, rect.y);
376 /* Add a new icon to the background.
377 * 'path' should be an absolute pathname.
378 * 'x' and 'y' are the coordinates of the point in the middle of the text.
379 * 'name' is the name to use. If NULL then the leafname of path is used.
381 * name and path are in UTF-8 for Gtk+-2.0 only.
383 void pinboard_pin(const gchar *path, const gchar *name, int x, int y,
384 const gchar *shortcut)
386 GtkWidget *align, *vbox;
387 GdkWindow *events;
388 PinIcon *pi;
389 Icon *icon;
391 g_return_if_fail(path != NULL);
392 g_return_if_fail(current_pinboard != NULL);
394 pi = pin_icon_new(path, name);
395 icon = (Icon *) pi;
396 pi->x = x;
397 pi->y = y;
399 /* This is a bit complicated...
401 * An icon needs to be a NO_WINDOW widget so that the image can
402 * blend with the background (A ParentRelative window also works, but
403 * is slow, causes the xfree86's memory consumption to grow without
404 * bound, and doesn't even get freed when the filer quits!).
406 * However, the icon also needs to have a window, so we get events
407 * delivered correctly. The solution is to float an InputOnly window
408 * over the icon. Since GtkButton works the same way, we just use
409 * that :-)
412 /* Button takes the initial ref of Icon */
413 pi->win = gtk_button_new();
414 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
415 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
416 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
418 vbox = gtk_vbox_new(FALSE, 0);
419 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
421 align = gtk_alignment_new(0.5, 0.5, 0, 0);
422 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
423 gtk_container_add(GTK_CONTAINER(align), pi->widget);
425 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
426 drag_set_pinicon_dest(pi);
427 g_signal_connect(pi->win, "drag_data_get",
428 G_CALLBACK(drag_data_get), NULL);
430 pi->label = gtk_label_new(icon->item->leafname);
431 gtk_label_set_line_wrap(GTK_LABEL(pi->label), TRUE);
432 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
434 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
436 snap_to_grid(&x, &y);
437 pi->x = x;
438 pi->y = y;
439 gtk_widget_show_all(pi->win);
440 pinboard_reshape_icon((Icon *) pi);
442 gtk_widget_realize(pi->win);
443 events = GTK_BUTTON(pi->win)->event_window;
444 gdk_window_set_events(events,
445 GDK_EXPOSURE_MASK |
446 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
447 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
448 GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK |
449 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
450 g_signal_connect(pi->win, "enter-notify-event",
451 G_CALLBACK(enter_notify), pi);
452 g_signal_connect(pi->win, "button-press-event",
453 G_CALLBACK(button_press_event), pi);
454 g_signal_connect(pi->win, "button-release-event",
455 G_CALLBACK(button_release_event), pi);
456 g_signal_connect(pi->win, "motion-notify-event",
457 G_CALLBACK(icon_motion_notify), pi);
458 g_signal_connect(pi->win, "expose-event",
459 G_CALLBACK(draw_icon), pi);
460 g_signal_connect_swapped(pi->win, "style-set",
461 G_CALLBACK(pinboard_reshape_icon), pi);
462 g_signal_connect_swapped(pi->win, "destroy",
463 G_CALLBACK(pin_icon_destroyed), pi);
465 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
466 pin_icon_set_tip(pi);
468 icon_set_shortcut(icon, shortcut);
470 if (!loading_pinboard)
471 pinboard_save();
474 /* Put a border around the icon, briefly.
475 * If icon is NULL then cancel any existing wink.
476 * The icon will automatically unhighlight unless timeout is FALSE,
477 * in which case you must call this function again (with NULL or another
478 * icon) to remove the highlight.
480 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
482 PinIcon *old = current_wink_icon;
484 if (old == pi)
485 return;
487 current_wink_icon = pi;
489 if (old)
491 gtk_widget_queue_draw(old->win);
492 gdk_window_process_updates(old->widget->window, TRUE);
494 if (wink_timeout != -1)
495 gtk_timeout_remove(wink_timeout);
498 if (pi)
500 gtk_widget_queue_draw(pi->win);
501 gdk_window_process_updates(pi->widget->window, TRUE);
503 if (timeout)
504 wink_timeout = gtk_timeout_add(300, end_wink, NULL);
505 else
506 wink_timeout = -1;
510 /* 'app' is saved as the new application to set the backdrop. It will then be
511 * run, and should communicate with the filer as described in the manual.
513 void pinboard_set_backdrop_app(const gchar *app)
515 XMLwrapper *ai;
516 DirItem *item;
517 gboolean can_set;
519 item = diritem_new("");
520 diritem_restat(app, item, NULL);
521 if (!(item->flags & ITEM_FLAG_APPDIR))
523 delayed_error(_("The backdrop handler must be an application "
524 "directory. Drag an application directory "
525 "into the Set Backdrop dialog box, or (for "
526 "programmers) pass it to the SOAP "
527 "SetBackdropApp method."));
528 diritem_free(item);
529 return;
532 ai = appinfo_get(app, item);
533 diritem_free(item);
535 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
536 if (ai)
537 g_object_unref(ai);
539 if (can_set)
540 set_backdrop(app, BACKDROP_PROGRAM);
541 else
542 delayed_error(_("You can only set the backdrop to an image "
543 "or to a program which knows how to "
544 "manage ROX-Filer's backdrop.\n\n"
545 "Programmers: the application's AppInfo.xml "
546 "must contain the CanSetBackdrop element, as "
547 "described in ROX-Filer's manual."));
550 /* Open a dialog box allowing the user to set the backdrop */
551 void pinboard_set_backdrop(void)
553 GtkWidget *dialog, *frame, *label, *radio, *hbox;
554 GtkBox *vbox;
555 GtkTargetEntry targets[] = {
556 {"text/uri-list", 0, TARGET_URI_LIST},
559 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
560 GTK_DIALOG_NO_SEPARATOR,
561 GTK_STOCK_CLEAR, GTK_RESPONSE_NO,
562 GTK_STOCK_OK, GTK_RESPONSE_OK,
563 NULL);
564 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
566 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
568 label = gtk_label_new(_("Display backdrop image:"));
569 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
570 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
571 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
573 /* The Centred, Scaled, Tiled radios... */
574 hbox = gtk_hbox_new(TRUE, 2);
575 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
577 radio = gtk_radio_button_new_with_label(NULL, _("Centred"));
578 g_object_set_data(G_OBJECT(dialog), "radio_centred", radio);
579 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
581 radio = gtk_radio_button_new_with_label(
582 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
583 _("Scaled"));
584 g_object_set_data(G_OBJECT(dialog), "radio_scaled", radio);
585 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
587 radio = gtk_radio_button_new_with_label(
588 gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)),
589 _("Tiled"));
590 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, TRUE, 0);
592 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
594 /* The drop area... */
595 frame = gtk_frame_new(NULL);
596 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
597 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
598 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
600 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
601 targets, sizeof(targets) / sizeof(*targets),
602 GDK_ACTION_COPY);
603 g_signal_connect(frame, "drag_data_received",
604 G_CALLBACK(drag_backdrop_dropped), dialog);
606 label = gtk_label_new(_("Drop an image here"));
607 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
608 gtk_container_add(GTK_CONTAINER(frame), label);
610 g_signal_connect(dialog, "response",
611 G_CALLBACK(backdrop_response), NULL);
612 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
614 gtk_widget_show_all(dialog);
617 /****************************************************************
618 * INTERNAL FUNCTIONS *
619 ****************************************************************/
621 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
623 if (response == GTK_RESPONSE_NO)
624 set_backdrop(NULL, BACKDROP_NONE);
626 gtk_widget_destroy(dialog);
629 static void drag_backdrop_dropped(GtkWidget *frame,
630 GdkDragContext *context,
631 gint x,
632 gint y,
633 GtkSelectionData *selection_data,
634 guint drag_info,
635 guint32 time,
636 GtkWidget *dialog)
638 struct stat info;
639 const gchar *path = NULL;
640 GList *uris;
642 if (!selection_data->data)
643 return; /* Timeout? */
645 uris = uri_list_to_glist(selection_data->data);
647 if (g_list_length(uris) == 1)
648 path = get_local_path((guchar *) uris->data);
649 g_list_free(uris);
651 if (!path)
653 delayed_error(
654 _("You should drop a single (local) image file "
655 "onto the drop box - that image will be "
656 "used for the desktop background. You can also "
657 "drag certain applications onto this box."));
658 return;
661 if (mc_stat(path, &info))
663 delayed_error(
664 _("Can't access '%s':\n%s"), path,
665 g_strerror(errno));
666 return;
669 if (S_ISDIR(info.st_mode))
671 /* Use this program to set the backdrop */
672 pinboard_set_backdrop_app(path);
674 else if (S_ISREG(info.st_mode))
676 GtkWidget *radio;
678 radio = g_object_get_data(G_OBJECT(dialog), "radio_scaled");
679 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
681 set_backdrop(path, BACKDROP_SCALE);
682 return;
685 radio = g_object_get_data(G_OBJECT(dialog), "radio_centred");
686 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio)))
688 set_backdrop(path, BACKDROP_CENTRE);
689 return;
692 set_backdrop(path, BACKDROP_TILE);
694 else
695 delayed_error(_("Only files (and certain applications) can be "
696 "used to set the background image."));
699 /* Do this in the idle loop so that we don't try to put an unmanaged
700 * pinboard behind a managed panel (crashes some WMs).
702 static gboolean recreate_pinboard(gchar *name)
704 pinboard_activate(name);
705 g_free(name);
707 return FALSE;
710 static void pinboard_check_options(void)
712 GdkColor n_fg, n_bg;
714 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
715 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
717 if (o_override_redirect.has_changed && current_pinboard)
719 gchar *name;
720 name = g_strdup(current_pinboard->name);
721 pinboard_activate(NULL);
722 gtk_idle_add((GtkFunction) recreate_pinboard, name);
725 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
727 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
728 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0 ||
729 o_label_font.has_changed)
731 pin_text_fg_col = n_fg;
732 pin_text_bg_col = n_bg;
733 update_pinboard_font();
735 if (current_pinboard)
737 GtkWidget *w = current_pinboard->window;
738 GdkColormap *cm;
740 cm = gtk_widget_get_colormap(w);
742 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
743 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
745 /* Only redraw the background if there is no image */
746 if (!current_pinboard->backdrop)
747 reload_backdrop(current_pinboard,
748 NULL, BACKDROP_NONE);
750 reshape_all();
753 tasklist_style_changed();
757 static gint end_wink(gpointer data)
759 pinboard_wink_item(NULL, FALSE);
760 return FALSE;
763 /* Sets the appearance from the options and updates the size request of
764 * the image.
766 static void set_size_and_style(PinIcon *pi)
768 Icon *icon = (Icon *) pi;
769 MaskedPixmap *image = icon->item->image;
770 int iwidth = image->width;
771 int iheight = image->height;
773 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
774 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
775 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
776 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
777 widget_modify_font(pi->label, pinboard_font);
779 gtk_label_set_text(GTK_LABEL(pi->label), icon->item->leafname);
781 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
784 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
786 static GtkWidgetClass *parent_class = NULL;
787 Icon *icon = (Icon *) pi;
788 DirItem *item = icon->item;
789 MaskedPixmap *image = item->image;
790 int iwidth = image->width;
791 int iheight = image->height;
792 int x, y;
793 GtkStateType state = icon->selected ? GTK_STATE_SELECTED
794 : GTK_STATE_NORMAL;
796 if (!parent_class)
798 gpointer c = ((GTypeInstance *) widget)->g_class;
799 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
802 x = pi->widget->allocation.x;
803 y = pi->widget->allocation.y;
805 gdk_pixbuf_render_to_drawable_alpha(
806 icon->selected ? image->pixbuf_lit : image->pixbuf,
807 pi->widget->window,
808 0, 0, /* src */
809 x, y, /* dest */
810 iwidth, iheight,
811 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
812 GDK_RGB_DITHER_NORMAL, 0, 0);
814 if (item->flags & ITEM_FLAG_SYMLINK)
816 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
817 pi->widget->window,
818 0, 0, /* src */
819 x, y,
820 -1, -1,
821 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
822 GDK_RGB_DITHER_NORMAL, 0, 0);
824 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
826 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
827 ? im_mounted
828 : im_unmounted;
830 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
831 pi->widget->window,
832 0, 0, /* src */
833 x, y,
834 -1, -1,
835 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
836 GDK_RGB_DITHER_NORMAL, 0, 0);
839 if (icon->selected)
841 gtk_paint_flat_box(pi->label->style, pi->label->window,
842 state,
843 GTK_SHADOW_NONE,
844 NULL, pi->label, "text",
845 pi->label->allocation.x,
846 pi->label->allocation.y,
847 pi->label->allocation.width,
848 pi->label->allocation.height);
851 /* Draw children */
852 (parent_class->expose_event)(widget, event);
854 /* Stop the button effect */
855 return TRUE;
858 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
860 gint x, y, width, height;
862 if (current_wink_icon != pi)
863 return FALSE;
865 x = widget->allocation.x;
866 y = widget->allocation.y;
867 width = widget->allocation.width;
868 height = widget->allocation.height;
870 gdk_draw_rectangle(widget->window,
871 pi->widget->style->white_gc,
872 FALSE,
873 x, y, width - 1, height - 1);
874 gdk_draw_rectangle(widget->window,
875 pi->widget->style->black_gc,
876 FALSE,
877 x + 1, y + 1, width - 3, height - 3);
879 return FALSE;
882 static gboolean enter_notify(GtkWidget *widget,
883 GdkEventCrossing *event,
884 PinIcon *pi)
886 icon_may_update((Icon *) pi);
888 return FALSE;
891 static void select_lasso(void)
893 GList *next;
894 int minx, miny, maxx, maxy;
896 g_return_if_fail(lasso_in_progress == TRUE);
898 minx = MIN(lasso_rect_x1, lasso_rect_x2);
899 miny = MIN(lasso_rect_y1, lasso_rect_y2);
900 maxx = MAX(lasso_rect_x1, lasso_rect_x2);
901 maxy = MAX(lasso_rect_y1, lasso_rect_y2);
903 for (next = current_pinboard->icons; next; next = next->next)
905 PinIcon *pi = (PinIcon *) next->data;
906 int cx = pi->x;
907 int cy = pi->y;
909 if (cx > minx && cx < maxx && cy > miny && cy < maxy)
910 icon_set_selected((Icon *) pi, TRUE);
914 static void cancel_lasso(void)
916 draw_lasso();
917 lasso_in_progress = FALSE;
920 static void pinboard_lasso_box(int start_x, int start_y)
922 if (lasso_in_progress)
923 cancel_lasso();
924 lasso_in_progress = TRUE;
925 lasso_rect_x1 = lasso_rect_x2 = start_x;
926 lasso_rect_y1 = lasso_rect_y2 = start_y;
928 draw_lasso();
931 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d)
933 if (!lasso_in_progress)
934 return FALSE;
936 if (lasso_rect_x2 != event->x || lasso_rect_y2 != event->y)
938 draw_lasso();
939 lasso_rect_x2 = event->x;
940 lasso_rect_y2 = event->y;
941 draw_lasso();
944 return FALSE;
947 /* Mark the area of the screen covered by the lasso box for redraw */
948 static void draw_lasso(void)
950 GdkRectangle area;
952 if (!lasso_in_progress)
953 return;
955 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
956 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
957 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
958 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
960 gdk_window_invalidate_rect(current_pinboard->window->window,
961 &area, TRUE);
964 static void perform_action(PinIcon *pi, GdkEventButton *event)
966 BindAction action;
967 Icon *icon = (Icon *) pi;
969 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
970 event);
972 /* Actions that can happen with or without an icon */
973 switch (action)
975 case ACT_LASSO_CLEAR:
976 icon_select_only(NULL);
977 /* (no break) */
978 case ACT_LASSO_MODIFY:
979 pinboard_lasso_box(event->x, event->y);
980 return;
981 case ACT_CLEAR_SELECTION:
982 icon_select_only(NULL);
983 return;
984 case ACT_POPUP_MENU:
985 dnd_motion_ungrab();
986 pinboard_show_menu(event, pi);
987 return;
988 case ACT_IGNORE:
989 return;
990 default:
991 break;
994 g_return_if_fail(pi != NULL);
996 switch (action)
998 case ACT_OPEN_ITEM:
999 dnd_motion_ungrab();
1000 pinboard_wink_item(pi, TRUE);
1001 if (event->type == GDK_2BUTTON_PRESS)
1002 icon_set_selected(icon, FALSE);
1003 run_diritem(icon->path, icon->item, NULL, NULL, FALSE);
1004 break;
1005 case ACT_EDIT_ITEM:
1006 dnd_motion_ungrab();
1007 pinboard_wink_item(pi, TRUE);
1008 if (event->type == GDK_2BUTTON_PRESS)
1009 icon_set_selected(icon, FALSE);
1010 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
1011 break;
1012 case ACT_PRIME_AND_SELECT:
1013 if (!icon->selected)
1014 icon_select_only(icon);
1015 dnd_motion_start(MOTION_READY_FOR_DND);
1016 break;
1017 case ACT_PRIME_AND_TOGGLE:
1018 icon_set_selected(icon, !icon->selected);
1019 dnd_motion_start(MOTION_READY_FOR_DND);
1020 break;
1021 case ACT_PRIME_FOR_DND:
1022 dnd_motion_start(MOTION_READY_FOR_DND);
1023 break;
1024 case ACT_TOGGLE_SELECTED:
1025 icon_set_selected(icon, !icon->selected);
1026 break;
1027 case ACT_SELECT_EXCL:
1028 icon_select_only(icon);
1029 break;
1030 default:
1031 g_warning("Unsupported action : %d\n", action);
1032 break;
1036 static void forward_to_root(GdkEventButton *event)
1038 XButtonEvent xev;
1040 if (event->type == GDK_BUTTON_PRESS)
1042 xev.type = ButtonPress;
1043 XUngrabPointer(gdk_display, event->time);
1045 else
1046 xev.type = ButtonRelease;
1048 xev.window = gdk_x11_get_default_root_xwindow();
1049 xev.root = xev.window;
1050 xev.subwindow = None;
1051 xev.time = event->time;
1052 xev.x = event->x_root; /* Needed for icewm */
1053 xev.y = event->y_root;
1054 xev.x_root = event->x_root;
1055 xev.y_root = event->y_root;
1056 xev.state = event->state;
1057 xev.button = event->button;
1058 xev.same_screen = True;
1060 XSendEvent(gdk_display, xev.window, False,
1061 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
1064 #define FORWARDED_BUTTON(pi, b) ((b) == 2 || \
1065 (((b) == 3 || (b) == 1) && o_forward_buttons_13.int_value && !pi))
1067 /* pi is NULL if this is a root event */
1068 static gboolean button_release_event(GtkWidget *widget,
1069 GdkEventButton *event,
1070 PinIcon *pi)
1072 if (FORWARDED_BUTTON(pi, event->button))
1073 forward_to_root(event);
1074 else if (dnd_motion_release(event))
1076 if (motion_buttons_pressed == 0 && lasso_in_progress)
1078 select_lasso();
1079 cancel_lasso();
1081 return FALSE;
1084 perform_action(pi, event);
1086 return TRUE;
1089 /* pi is NULL if this is a root event */
1090 static gboolean button_press_event(GtkWidget *widget,
1091 GdkEventButton *event,
1092 PinIcon *pi)
1094 /* Just in case we've jumped in front of everything... */
1095 gdk_window_lower(current_pinboard->window->window);
1097 if (FORWARDED_BUTTON(pi, event->button))
1098 forward_to_root(event);
1099 else if (dnd_motion_press(widget, event))
1100 perform_action(pi, event);
1102 return TRUE;
1105 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1107 GtkWidget *widget = pi->win;
1108 Icon *icon = (Icon *) pi;
1110 if (!icon->selected)
1112 tmp_icon_selected = TRUE;
1113 icon_select_only(icon);
1116 g_return_if_fail(icon_selection != NULL);
1118 pinboard_drag_in_progress = icon;
1120 if (icon_selection->next == NULL)
1121 drag_one_item(widget, event, icon->path, icon->item, NULL);
1122 else
1124 guchar *uri_list;
1126 uri_list = icon_create_uri_list();
1127 drag_selection(widget, event, uri_list);
1128 g_free(uri_list);
1132 /* An icon is being dragged around... */
1133 static gint icon_motion_notify(GtkWidget *widget,
1134 GdkEventMotion *event,
1135 PinIcon *pi)
1137 if (motion_state == MOTION_READY_FOR_DND)
1139 if (dnd_motion_moved(event))
1140 start_drag(pi, event);
1141 return TRUE;
1144 return FALSE;
1147 static void backdrop_from_xml(xmlNode *node)
1149 gchar *style;
1151 g_free(current_pinboard->backdrop);
1152 current_pinboard->backdrop = xmlNodeGetContent(node);
1154 style = xmlGetProp(node, "style");
1156 if (style)
1158 current_pinboard->backdrop_style =
1159 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1160 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1161 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1162 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1163 BACKDROP_NONE;
1164 g_free(style);
1166 else
1167 current_pinboard->backdrop_style = BACKDROP_TILE;
1170 /* Create one pinboard icon for each icon in the doc */
1171 static void pinboard_load_from_xml(xmlDocPtr doc)
1173 xmlNodePtr node, root;
1174 char *tmp, *label, *path, *shortcut;
1175 int x, y;
1177 root = xmlDocGetRootElement(doc);
1179 for (node = root->xmlChildrenNode; node; node = node->next)
1181 if (node->type != XML_ELEMENT_NODE)
1182 continue;
1183 if (strcmp(node->name, "backdrop") == 0)
1185 backdrop_from_xml(node);
1186 continue;
1188 if (strcmp(node->name, "icon") != 0)
1189 continue;
1191 tmp = xmlGetProp(node, "x");
1192 if (!tmp)
1193 continue;
1194 x = atoi(tmp);
1195 g_free(tmp);
1197 tmp = xmlGetProp(node, "y");
1198 if (!tmp)
1199 continue;
1200 y = atoi(tmp);
1201 g_free(tmp);
1203 label = xmlGetProp(node, "label");
1204 if (!label)
1205 label = g_strdup("<missing label>");
1206 path = xmlNodeGetContent(node);
1207 if (!path)
1208 path = g_strdup("<missing path>");
1209 shortcut = xmlGetProp(node, "shortcut");
1211 pinboard_pin(path, label, x, y, shortcut);
1213 g_free(path);
1214 g_free(label);
1215 g_free(shortcut);
1219 /* Called for each line in the pinboard file while loading a new board.
1220 * Only used for old-format files when converting to XML.
1222 static const char *pin_from_file(gchar *line)
1224 gchar *leaf = NULL;
1225 int x, y, n;
1227 if (*line == '<')
1229 gchar *end;
1231 end = strchr(line + 1, '>');
1232 if (!end)
1233 return _("Missing '>' in icon label");
1235 leaf = g_strndup(line + 1, end - line - 1);
1237 line = end + 1;
1239 while (isspace(*line))
1240 line++;
1241 if (*line != ',')
1242 return _("Missing ',' after icon label");
1243 line++;
1246 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1247 return NULL; /* Ignore format errors */
1249 pinboard_pin(line + n, leaf, x, y, NULL);
1251 g_free(leaf);
1253 return NULL;
1256 /* Write the current state of the pinboard to the current pinboard file */
1257 static void pinboard_save(void)
1259 guchar *save = NULL;
1260 guchar *save_new = NULL;
1261 GList *next;
1262 xmlDocPtr doc = NULL;
1263 xmlNodePtr root;
1265 g_return_if_fail(current_pinboard != NULL);
1267 if (strchr(current_pinboard->name, '/'))
1268 save = g_strdup(current_pinboard->name);
1269 else
1271 guchar *leaf;
1273 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1274 save = choices_find_path_save(leaf, PROJECT, TRUE);
1275 g_free(leaf);
1278 if (!save)
1279 return;
1281 doc = xmlNewDoc("1.0");
1282 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1284 root = xmlDocGetRootElement(doc);
1286 if (current_pinboard->backdrop)
1288 BackdropStyle style = current_pinboard->backdrop_style;
1289 xmlNodePtr tree;
1291 tree = xmlNewTextChild(root, NULL, "backdrop",
1292 current_pinboard->backdrop);
1293 xmlSetProp(tree, "style",
1294 style == BACKDROP_TILE ? "Tiled" :
1295 style == BACKDROP_CENTRE ? "Centred" :
1296 style == BACKDROP_SCALE ? "Scaled" :
1297 "Program");
1300 for (next = current_pinboard->icons; next; next = next->next)
1302 xmlNodePtr tree;
1303 PinIcon *pi = (PinIcon *) next->data;
1304 Icon *icon = (Icon *) pi;
1305 char *tmp;
1307 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1309 tmp = g_strdup_printf("%d", pi->x);
1310 xmlSetProp(tree, "x", tmp);
1311 g_free(tmp);
1313 tmp = g_strdup_printf("%d", pi->y);
1314 xmlSetProp(tree, "y", tmp);
1315 g_free(tmp);
1317 xmlSetProp(tree, "label", icon->item->leafname);
1318 if (icon->shortcut)
1319 xmlSetProp(tree, "shortcut", icon->shortcut);
1322 save_new = g_strconcat(save, ".new", NULL);
1323 if (save_xml_file(doc, save_new) || rename(save_new, save))
1324 delayed_error(_("Error saving pinboard %s: %s"),
1325 save, g_strerror(errno));
1326 g_free(save_new);
1328 g_free(save);
1329 if (doc)
1330 xmlFreeDoc(doc);
1333 static void snap_to_grid(int *x, int *y)
1335 int step = o_pinboard_grid_step.int_value;
1337 *x = ((*x + step / 2) / step) * step;
1338 *y = ((*y + step / 2) / step) * step;
1341 /* Convert (x,y) from a centre point to a window position */
1342 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1344 gboolean clamp = o_pinboard_clamp_icons.int_value;
1345 GtkRequisition req;
1347 gtk_widget_size_request(pi->win, &req);
1349 *x -= req.width >> 1;
1350 *y -= req.height >> 1;
1351 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1352 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1355 /* Same as drag_set_dest(), but for pinboard icons */
1356 static void drag_set_pinicon_dest(PinIcon *pi)
1358 GtkObject *obj = GTK_OBJECT(pi->win);
1360 make_drop_target(pi->win, 0);
1362 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1363 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1364 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1367 /* Called during the drag when the mouse is in a widget registered
1368 * as a drop target. Returns TRUE if we can accept the drop.
1370 static gboolean drag_motion(GtkWidget *widget,
1371 GdkDragContext *context,
1372 gint x,
1373 gint y,
1374 guint time,
1375 PinIcon *pi)
1377 GdkDragAction action = context->suggested_action;
1378 const char *type = NULL;
1379 Icon *icon = (Icon *) pi;
1380 DirItem *item = icon->item;
1382 if (gtk_drag_get_source_widget(context) == widget)
1383 goto out; /* Can't drag something to itself! */
1385 if (icon->selected)
1386 goto out; /* Can't drag a selection to itself */
1388 type = dnd_motion_item(context, &item);
1390 if (!item)
1391 type = NULL;
1392 out:
1393 /* We actually must pretend to accept the drop, even if the
1394 * directory isn't writeable, so that the spring-opening
1395 * thing works.
1398 /* Don't allow drops to non-writeable directories */
1399 if (o_dnd_spring_open.int_value == FALSE &&
1400 type == drop_dest_dir &&
1401 access(icon->path, W_OK) != 0)
1403 type = NULL;
1406 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
1407 if (type)
1409 gdk_drag_status(context, action, time);
1410 g_dataset_set_data_full(context, "drop_dest_path",
1411 g_strdup(icon->path), g_free);
1412 if (type == drop_dest_dir)
1413 dnd_spring_load(context, NULL);
1415 pinboard_wink_item(pi, FALSE);
1417 else
1418 gdk_drag_status(context, 0, time);
1420 /* Always return TRUE to stop the pinboard getting the events */
1421 return TRUE;
1424 static gboolean pinboard_shadow = FALSE;
1425 static gint shadow_x, shadow_y;
1426 #define SHADOW_SIZE (ICON_WIDTH)
1428 static gboolean bg_expose(GtkWidget *widget,
1429 GdkEventExpose *event, gpointer data)
1431 gpointer gclass = ((GTypeInstance *) widget)->g_class;
1433 /* TODO: Split large regions into smaller chunks... */
1435 gdk_window_begin_paint_region(widget->window, event->region);
1437 if (pinboard_shadow)
1439 gdk_draw_rectangle(widget->window,
1440 widget->style->white_gc, FALSE,
1441 shadow_x, shadow_y,
1442 SHADOW_SIZE, SHADOW_SIZE);
1443 gdk_draw_rectangle(widget->window,
1444 widget->style->black_gc, FALSE,
1445 shadow_x + 1, shadow_y + 1,
1446 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1449 if (lasso_in_progress)
1451 GdkRectangle area;
1453 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1454 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1455 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1456 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1458 if (area.width > 4 && area.height > 4)
1460 gdk_draw_rectangle(widget->window,
1461 widget->style->white_gc, FALSE,
1462 area.x, area.y,
1463 area.width - 1, area.height - 1);
1464 gdk_draw_rectangle(widget->window,
1465 widget->style->black_gc, FALSE,
1466 area.x + 1, area.y + 1,
1467 area.width - 3, area.height - 3);
1471 ((GtkWidgetClass *) gclass)->expose_event(widget, event);
1473 gdk_window_end_paint(widget->window);
1475 return TRUE;
1478 /* Draw a 'shadow' under an icon being dragged, showing where
1479 * it will land.
1481 static void pinboard_set_shadow(gboolean on)
1483 GdkRectangle area;
1485 if (pinboard_shadow)
1487 area.x = shadow_x;
1488 area.y = shadow_y;
1489 area.width = SHADOW_SIZE + 1;
1490 area.height = SHADOW_SIZE + 1;
1492 gdk_window_invalidate_rect(current_pinboard->window->window,
1493 &area, TRUE);
1496 if (on)
1498 int old_x = shadow_x, old_y = shadow_y;
1500 gdk_window_get_pointer(current_pinboard->fixed->window,
1501 &shadow_x, &shadow_y, NULL);
1502 snap_to_grid(&shadow_x, &shadow_y);
1503 shadow_x -= SHADOW_SIZE / 2;
1504 shadow_y -= SHADOW_SIZE / 2;
1507 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1508 return;
1510 area.x = shadow_x;
1511 area.y = shadow_y;
1512 area.width = SHADOW_SIZE + 1;
1513 area.height = SHADOW_SIZE + 1;
1515 gdk_window_invalidate_rect(current_pinboard->window->window,
1516 &area, TRUE);
1519 pinboard_shadow = on;
1522 /* Called when dragging some pinboard icons finishes */
1523 void pinboard_move_icons(void)
1525 int x = shadow_x, y = shadow_y;
1526 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1527 int width, height;
1528 int dx, dy;
1529 GList *next;
1531 g_return_if_fail(pi != NULL);
1533 x += SHADOW_SIZE / 2;
1534 y += SHADOW_SIZE / 2;
1535 snap_to_grid(&x, &y);
1537 if (pi->x == x && pi->y == y)
1538 return;
1540 /* Find out how much the dragged icon moved (after snapping).
1541 * Move all selected icons by the same amount.
1543 dx = x - pi->x;
1544 dy = y - pi->y;
1546 /* Move the other selected icons to keep the same relative
1547 * position.
1549 for (next = icon_selection; next; next = next->next)
1551 PinIcon *pi = (PinIcon *) next->data;
1552 int nx, ny;
1554 g_return_if_fail(IS_PIN_ICON(pi));
1556 pi->x += dx;
1557 pi->y += dy;
1558 nx = pi->x;
1559 ny = pi->y;
1561 gdk_drawable_get_size(pi->win->window, &width, &height);
1562 offset_from_centre(pi, &nx, &ny);
1564 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
1565 pi->win, nx, ny);
1568 pinboard_save();
1571 static void drag_leave(GtkWidget *widget,
1572 GdkDragContext *context,
1573 guint32 time,
1574 PinIcon *pi)
1576 pinboard_wink_item(NULL, FALSE);
1577 dnd_spring_abort();
1580 static gboolean bg_drag_leave(GtkWidget *widget,
1581 GdkDragContext *context,
1582 guint32 time,
1583 gpointer data)
1585 pinboard_set_shadow(FALSE);
1586 return TRUE;
1589 static gboolean bg_drag_motion(GtkWidget *widget,
1590 GdkDragContext *context,
1591 gint x,
1592 gint y,
1593 guint time,
1594 gpointer data)
1596 /* Dragging from the pinboard to the pinboard is not allowed */
1598 if (!provides(context, text_uri_list))
1599 return FALSE;
1601 pinboard_set_shadow(TRUE);
1603 gdk_drag_status(context,
1604 context->suggested_action == GDK_ACTION_ASK
1605 ? GDK_ACTION_LINK : context->suggested_action,
1606 time);
1607 return TRUE;
1610 static void drag_end(GtkWidget *widget,
1611 GdkDragContext *context,
1612 PinIcon *pi)
1614 pinboard_drag_in_progress = NULL;
1615 if (tmp_icon_selected)
1617 icon_select_only(NULL);
1618 tmp_icon_selected = FALSE;
1622 /* Something which affects all the icons has changed - reshape
1623 * and redraw all of them.
1625 static void reshape_all(void)
1627 GList *next;
1629 g_return_if_fail(current_pinboard != NULL);
1631 for (next = current_pinboard->icons; next; next = next->next)
1633 Icon *icon = (Icon *) next->data;
1634 pinboard_reshape_icon(icon);
1638 /* Turns off the pinboard. Does not call gtk_main_quit. */
1639 static void pinboard_clear(void)
1641 GList *next;
1643 g_return_if_fail(current_pinboard != NULL);
1645 tasklist_set_active(FALSE);
1647 next = current_pinboard->icons;
1648 while (next)
1650 PinIcon *pi = (PinIcon *) next->data;
1652 next = next->next;
1654 gtk_widget_destroy(pi->win);
1657 gtk_widget_destroy(current_pinboard->window);
1659 abandon_backdrop_app(current_pinboard);
1661 g_free(current_pinboard->name);
1662 null_g_free(&current_pinboard);
1664 number_of_windows--;
1667 static gpointer parent_class;
1669 static void pin_icon_destroy(Icon *icon)
1671 PinIcon *pi = (PinIcon *) icon;
1673 g_return_if_fail(pi->win != NULL);
1675 gtk_widget_destroy(pi->win);
1678 static void pinboard_remove_items(void)
1680 g_return_if_fail(icon_selection != NULL);
1682 while (icon_selection)
1683 icon_destroy((Icon *) icon_selection->data);
1685 pinboard_save();
1688 static void pin_icon_update(Icon *icon)
1690 pinboard_reshape_icon(icon);
1691 pinboard_save();
1694 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1696 return IS_PIN_ICON(other);
1699 static void pin_wink_icon(Icon *icon)
1701 pinboard_wink_item((PinIcon *) icon, TRUE);
1704 static void pin_icon_class_init(gpointer gclass, gpointer data)
1706 IconClass *icon = (IconClass *) gclass;
1708 parent_class = g_type_class_peek_parent(gclass);
1710 icon->destroy = pin_icon_destroy;
1711 icon->redraw = pinboard_reshape_icon;
1712 icon->update = pin_icon_update;
1713 icon->wink = pin_wink_icon;
1714 icon->remove_items = pinboard_remove_items;
1715 icon->same_group = pin_icon_same_group;
1718 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
1722 static GType pin_icon_get_type(void)
1724 static GType type = 0;
1726 if (!type)
1728 static const GTypeInfo info =
1730 sizeof (PinIconClass),
1731 NULL, /* base_init */
1732 NULL, /* base_finalise */
1733 pin_icon_class_init,
1734 NULL, /* class_finalise */
1735 NULL, /* class_data */
1736 sizeof(PinIcon),
1737 0, /* n_preallocs */
1738 pin_icon_init
1741 type = g_type_register_static(icon_get_type(),
1742 "PinIcon", &info, 0);
1745 return type;
1748 static PinIcon *pin_icon_new(const char *pathname, const char *name)
1750 PinIcon *pi;
1751 Icon *icon;
1753 pi = g_object_new(pin_icon_get_type(), NULL);
1754 icon = (Icon *) pi;
1756 icon_set_path(icon, pathname, name);
1758 return pi;
1761 /* Called when the window widget is somehow destroyed */
1762 static void pin_icon_destroyed(PinIcon *pi)
1764 g_return_if_fail(pi->win != NULL);
1766 pi->win = NULL;
1768 pinboard_wink_item(NULL, FALSE);
1770 if (pinboard_drag_in_progress == (Icon *) pi)
1771 pinboard_drag_in_progress = NULL;
1773 if (current_pinboard)
1774 current_pinboard->icons =
1775 g_list_remove(current_pinboard->icons, pi);
1777 g_object_unref(pi);
1780 /* Set the tooltip */
1781 static void pin_icon_set_tip(PinIcon *pi)
1783 XMLwrapper *ai;
1784 xmlNode *node;
1785 Icon *icon = (Icon *) pi;
1787 g_return_if_fail(pi != NULL);
1789 ai = appinfo_get(icon->path, icon->item);
1791 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
1793 guchar *str;
1794 str = xmlNodeListGetString(node->doc,
1795 node->xmlChildrenNode, 1);
1796 if (str)
1798 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
1799 g_free(str);
1802 else
1803 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
1805 if (ai)
1806 g_object_unref(ai);
1809 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
1811 int pos[3];
1813 pos[0] = event->x_root;
1814 pos[1] = event->y_root;
1815 pos[2] = 1;
1817 icon_prepare_menu((Icon *) pi, TRUE);
1819 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
1820 position_menu,
1821 (gpointer) pos, event->button, event->time);
1824 static void create_pinboard_window(Pinboard *pinboard)
1826 GtkWidget *win;
1828 g_return_if_fail(pinboard->window == NULL);
1830 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1831 gtk_widget_set_style(win, gtk_widget_get_default_style());
1833 gtk_widget_modify_bg(win, GTK_STATE_NORMAL, &pin_text_bg_col);
1835 gtk_widget_set_double_buffered(win, FALSE);
1836 gtk_widget_set_app_paintable(win, TRUE);
1837 gtk_widget_set_name(win, "rox-pinboard");
1838 pinboard->window = win;
1839 pinboard->fixed = gtk_fixed_new();
1840 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
1842 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
1844 gtk_widget_set_size_request(win, screen_width, screen_height);
1845 gtk_widget_realize(win);
1846 gtk_window_move(GTK_WINDOW(win), 0, 0);
1847 make_panel_window(win);
1849 /* TODO: Use gdk function when it supports this type */
1851 GdkAtom desktop_type;
1853 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
1854 FALSE);
1855 gdk_property_change(win->window,
1856 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
1857 gdk_atom_intern("ATOM", FALSE), 32,
1858 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
1861 gtk_widget_add_events(win,
1862 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1863 GDK_EXPOSURE_MASK |
1864 GDK_BUTTON1_MOTION_MASK |
1865 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
1866 g_signal_connect(win, "button-press-event",
1867 G_CALLBACK(button_press_event), NULL);
1868 g_signal_connect(win, "button-release-event",
1869 G_CALLBACK(button_release_event), NULL);
1870 g_signal_connect(win, "motion-notify-event",
1871 G_CALLBACK(lasso_motion), NULL);
1872 g_signal_connect(pinboard->fixed, "expose_event",
1873 G_CALLBACK(bg_expose), NULL);
1875 /* Drag and drop handlers */
1876 drag_set_pinboard_dest(win);
1877 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
1878 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
1880 gtk_widget_show_all(win);
1881 gdk_window_lower(win->window);
1884 /* Load image 'path' and scale according to 'style' */
1885 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
1887 GdkPixmap *pixmap;
1888 GdkPixbuf *pixbuf;
1889 GError *error = NULL;
1891 pixbuf = gdk_pixbuf_new_from_file(path, &error);
1892 if (error)
1894 delayed_error(_("Error loading backdrop image:\n%s\n"
1895 "Backdrop removed."),
1896 error->message);
1897 g_error_free(error);
1898 set_backdrop(NULL, BACKDROP_NONE);
1899 return NULL;
1902 if (style == BACKDROP_SCALE)
1904 GdkPixbuf *old = pixbuf;
1906 pixbuf = gdk_pixbuf_scale_simple(old,
1907 screen_width, screen_height,
1908 GDK_INTERP_HYPER);
1910 g_object_unref(old);
1912 else if (style == BACKDROP_CENTRE)
1914 GdkPixbuf *old = pixbuf;
1915 int x, y, width, height;
1917 width = gdk_pixbuf_get_width(pixbuf);
1918 height = gdk_pixbuf_get_height(pixbuf);
1920 pixbuf = gdk_pixbuf_new(
1921 gdk_pixbuf_get_colorspace(pixbuf), 0,
1922 8, screen_width, screen_height);
1923 gdk_pixbuf_fill(pixbuf, 0);
1925 x = (screen_width - width) / 2;
1926 y = (screen_height - height) / 2;
1927 x = MAX(x, 0);
1928 y = MAX(y, 0);
1930 gdk_pixbuf_composite(old, pixbuf,
1931 x, y,
1932 MIN(screen_width, width),
1933 MIN(screen_height, height),
1934 x, y, 1, 1,
1935 GDK_INTERP_NEAREST, 255);
1936 g_object_unref(old);
1939 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
1940 &pixmap, NULL, 0);
1941 g_object_unref(pixbuf);
1943 return pixmap;
1946 static void abandon_backdrop_app(Pinboard *pinboard)
1948 g_return_if_fail(pinboard != NULL);
1950 if (pinboard->to_backdrop_app != -1)
1952 close(pinboard->to_backdrop_app);
1953 close(pinboard->from_backdrop_app);
1954 gtk_input_remove(pinboard->input_tag);
1955 g_string_free(pinboard->input_buffer, TRUE);
1956 pinboard->to_backdrop_app = -1;
1957 pinboard->from_backdrop_app = -1;
1958 pinboard->input_tag = -1;
1959 pinboard->input_buffer = NULL;
1962 g_return_if_fail(pinboard->to_backdrop_app == -1);
1963 g_return_if_fail(pinboard->from_backdrop_app == -1);
1964 g_return_if_fail(pinboard->input_tag == -1);
1965 g_return_if_fail(pinboard->input_buffer == NULL);
1968 /* A single line has been read from the child.
1969 * Processes the command, and replies 'ok' (or abandons the child on error).
1971 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
1973 BackdropStyle style;
1974 const char *ok = "ok\n";
1976 if (strncmp(command, "tile ", 5) == 0)
1978 style = BACKDROP_TILE;
1979 command += 5;
1981 else if (strncmp(command, "scale ", 6) == 0)
1983 style = BACKDROP_SCALE;
1984 command += 6;
1986 else if (strncmp(command, "centre ", 7) == 0)
1988 style = BACKDROP_CENTRE;
1989 command += 7;
1991 else
1993 g_warning("Invalid command '%s' from backdrop app\n",
1994 command);
1995 abandon_backdrop_app(pinboard);
1996 return;
1999 /* Load the backdrop. May abandon the program if loading fails. */
2000 reload_backdrop(pinboard, command, style);
2002 if (pinboard->to_backdrop_app == -1)
2003 return;
2005 while (*ok)
2007 int sent;
2009 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
2010 if (sent <= 0)
2012 /* Remote app quit? Not an error. */
2013 abandon_backdrop_app(pinboard);
2014 break;
2016 ok += sent;
2020 static void backdrop_from_child(Pinboard *pinboard,
2021 int src, GdkInputCondition cond)
2023 char buf[256];
2024 int got;
2026 got = read(src, buf, sizeof(buf));
2028 if (got <= 0)
2030 if (got < 0)
2031 g_warning("backdrop_from_child: %s\n",
2032 g_strerror(errno));
2033 abandon_backdrop_app(pinboard);
2034 return;
2037 g_string_append_len(pinboard->input_buffer, buf, got);
2039 while (pinboard->from_backdrop_app != -1)
2041 int len;
2042 char *nl, *command;
2044 nl = strchr(pinboard->input_buffer->str, '\n');
2045 if (!nl)
2046 return; /* Haven't got a whole line yet */
2048 len = nl - pinboard->input_buffer->str;
2049 command = g_strndup(pinboard->input_buffer->str, len);
2050 g_string_erase(pinboard->input_buffer, 0, len + 1);
2052 command_from_backdrop_app(pinboard, command);
2054 g_free(command);
2058 static void reload_backdrop(Pinboard *pinboard,
2059 const gchar *backdrop,
2060 BackdropStyle backdrop_style)
2062 GtkStyle *style;
2064 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
2066 const char *argv[] = {NULL, "--backdrop", NULL};
2067 GError *error = NULL;
2069 g_return_if_fail(pinboard->to_backdrop_app == -1);
2070 g_return_if_fail(pinboard->from_backdrop_app == -1);
2071 g_return_if_fail(pinboard->input_tag == -1);
2072 g_return_if_fail(pinboard->input_buffer == NULL);
2074 argv[0] = make_path(backdrop, "AppRun")->str;
2076 /* Run the program. It'll send us a SOAP message and we'll
2077 * get back here with a different style and image.
2080 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
2081 G_SPAWN_DO_NOT_REAP_CHILD |
2082 G_SPAWN_SEARCH_PATH,
2083 NULL, NULL, /* Child setup fn */
2084 NULL, /* Child PID */
2085 &pinboard->to_backdrop_app,
2086 &pinboard->from_backdrop_app,
2087 NULL, /* Standard error */
2088 &error))
2090 pinboard->input_buffer = g_string_new(NULL);
2091 pinboard->input_tag = gtk_input_add_full(
2092 pinboard->from_backdrop_app,
2093 GDK_INPUT_READ,
2094 (GdkInputFunction) backdrop_from_child,
2095 NULL, pinboard, NULL);
2097 else
2099 delayed_error("%s", error ? error->message : "(null)");
2100 g_error_free(error);
2102 return;
2105 /* Note: Copying a style does not ref the pixmaps! */
2107 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
2108 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
2110 if (backdrop)
2111 style->bg_pixmap[GTK_STATE_NORMAL] =
2112 load_backdrop(backdrop, backdrop_style);
2114 gdk_color_parse(o_pinboard_bg_colour.value,
2115 &style->bg[GTK_STATE_NORMAL]);
2117 gtk_widget_set_style(pinboard->window, style);
2119 g_object_unref(style);
2121 gtk_widget_queue_draw(pinboard->window);
2123 /* Also update root window property (for transparent xterms, etc) */
2124 if (style->bg_pixmap[GTK_STATE_NORMAL])
2126 XID id = GDK_DRAWABLE_XID(style->bg_pixmap[GTK_STATE_NORMAL]);
2127 gdk_property_change(gdk_get_default_root_window(),
2128 gdk_atom_intern("_XROOTPMAP_ID", FALSE),
2129 gdk_atom_intern("PIXMAP", FALSE),
2130 32, GDK_PROP_MODE_REPLACE,
2131 (guchar *) &id, 1);
2133 else
2135 gdk_property_delete(gdk_get_default_root_window(),
2136 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
2140 /* Set and save (path, style) as the new backdrop.
2141 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
2142 * Otherwise, the image is displayed now.
2144 static void set_backdrop(const gchar *path, BackdropStyle style)
2146 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
2147 (path != NULL && style != BACKDROP_NONE));
2149 if (!current_pinboard)
2151 if (!path)
2152 return;
2153 pinboard_activate("Default");
2154 delayed_error(_("No pinboard was in use... "
2155 "the 'Default' pinboard has been selected. "
2156 "Use 'rox -p=Default' to turn it on in "
2157 "future."));
2158 g_return_if_fail(current_pinboard != NULL);
2161 /* We might have just run the old backdrop program and now
2162 * we're going to set a new one! Seems a bit mean...
2165 abandon_backdrop_app(current_pinboard);
2167 g_free(current_pinboard->backdrop);
2168 current_pinboard->backdrop = g_strdup(path);
2169 current_pinboard->backdrop_style = style;
2170 reload_backdrop(current_pinboard,
2171 current_pinboard->backdrop,
2172 current_pinboard->backdrop_style);
2174 pinboard_save();
2177 #define SEARCH_STEP 32
2179 static void search_free(GdkRectangle *rect, GdkRegion *used,
2180 int *outer, int od, int omax,
2181 int *inner, int id, int imax)
2183 *outer = od > 0 ? 0 : omax;
2184 while (*outer >= 0 && *outer <= omax)
2186 *inner = id > 0 ? 0 : imax;
2187 while (*inner >= 0 && *inner <= imax)
2189 if (gdk_region_rect_in(used, rect) ==
2190 GDK_OVERLAP_RECTANGLE_OUT)
2191 return;
2192 *inner += id;
2195 *outer += od;
2198 rect->x = -1;
2199 rect->y = -1;
2202 /* Finds a free area on the pinboard large enough for the width and height
2203 * of the given rectangle, by filling in the x and y fields of 'rect'.
2204 * The search order respects user preferences.
2205 * If no area is free, returns any old area.
2207 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect)
2209 GdkRegion *used;
2210 GList *next;
2211 GdkRectangle used_rect;
2212 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2214 used = gdk_region_new();
2216 panel_mark_used(used);
2218 /* Subtract the used areas... */
2220 next = GTK_FIXED(pinboard->fixed)->children;
2221 for (; next; next = next->next)
2223 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2225 if (!GTK_WIDGET_VISIBLE(fix->widget))
2226 continue;
2228 used_rect.x = fix->x;
2229 used_rect.y = fix->y;
2230 used_rect.width = fix->widget->requisition.width;
2231 used_rect.height = fix->widget->requisition.height;
2233 gdk_region_union_with_rect(used, &used_rect);
2236 /* Find the first free area (yes, this isn't exactly pretty, but
2237 * it works). If you know a better (fast!) algorithm, let me know!
2241 if (o_iconify_start.int_value == CORNER_TOP_RIGHT ||
2242 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2243 dx = -SEARCH_STEP;
2245 if (o_iconify_start.int_value == CORNER_BOTTOM_LEFT ||
2246 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2247 dy = -SEARCH_STEP;
2249 if (o_iconify_dir.int_value == DIR_VERT)
2251 search_free(rect, used,
2252 &rect->x, dx, screen_width - rect->width,
2253 &rect->y, dy, screen_height - rect->height);
2255 else
2257 search_free(rect, used,
2258 &rect->y, dy, screen_height - rect->height,
2259 &rect->x, dx, screen_width - rect->width);
2262 gdk_region_destroy(used);
2264 if (rect->x == -1)
2266 rect->x = 0;
2267 rect->y = 0;
2271 /* Icon's size, shape or appearance has changed - update the display */
2272 static void pinboard_reshape_icon(Icon *icon)
2274 PinIcon *pi = (PinIcon *) icon;
2275 int x = pi->x, y = pi->y;
2277 set_size_and_style(pi);
2278 offset_from_centre(pi, &x, &y);
2280 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
2282 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
2283 pi->win, x, y);
2287 /* Sets the pinboard_font global from the option. Doesn't do anything else. */
2288 static void update_pinboard_font(void)
2290 if (pinboard_font)
2291 pango_font_description_free(pinboard_font);
2292 pinboard_font = o_label_font.value[0] != '\0'
2293 ? pango_font_description_from_string(o_label_font.value)
2294 : NULL;