Don't confirm overwrites when copying in quiet mode
[rox-filer.git] / ROX-Filer / src / pinboard.c
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20 /* pinboard.c - icons on the desktop background */
21
22 #include "config.h"
23
24 #include <ctype.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkx.h>
30 #include <stdlib.h>
31 #include <math.h>
32 #include <libxml/parser.h>
33 #include <signal.h>
34
35 #include "global.h"
36
37 #include "pinboard.h"
38 #include "main.h"
39 #include "dnd.h"
40 #include "pixmaps.h"
41 #include "type.h"
42 #include "choices.h"
43 #include "support.h"
44 #include "gui_support.h"
45 #include "options.h"
46 #include "diritem.h"
47 #include "bind.h"
48 #include "icon.h"
49 #include "run.h"
50 #include "appinfo.h"
51 #include "menu.h"
52 #include "xml.h"
53 #include "tasklist.h"
54 #include "panel.h" /* For panel_mark_used() */
55 #include "wrapped.h"
56 #include "dropbox.h"
57
58 static gboolean tmp_icon_selected = FALSE; /* When dragging */
59
60 static GtkWidget *set_backdrop_dialog = NULL;
61
62 struct _Pinboard {
63 guchar *name; /* Leaf name */
64 GList *icons;
65 GtkStyle *style;
66
67 gchar *backdrop; /* Pathname */
68 BackdropStyle backdrop_style;
69 gint to_backdrop_app; /* pipe FD, or -1 */
70 gint from_backdrop_app; /* pipe FD, or -1 */
71 gint input_tag;
72 GString *input_buffer;
73
74 GtkWidget *window; /* Screen-sized window */
75 GtkWidget *fixed;
76 GdkGC *shadow_gc;
77 };
78
79 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
80
81 typedef struct _PinIconClass PinIconClass;
82 typedef struct _PinIcon PinIcon;
83
84 struct _PinIconClass {
85 IconClass parent;
86 };
87
88 struct _PinIcon {
89 Icon icon;
90
91 int x, y;
92 GtkWidget *win;
93 GtkWidget *widget; /* The drawing area for the icon */
94 GtkWidget *label;
95 };
96
97 /* The number of pixels between the bottom of the image and the top
98 * of the text.
99 */
100 #define GAP 4
101
102 /* The size of the border around the icon which is used when winking */
103 #define WINK_FRAME 2
104
105 /* Grid sizes */
106 #define GRID_STEP_FINE 2
107 #define GRID_STEP_MED 16
108 #define GRID_STEP_COARSE 32
109
110 /* Used in options */
111 #define CORNER_TOP_LEFT 0
112 #define CORNER_TOP_RIGHT 1
113 #define CORNER_BOTTOM_LEFT 2
114 #define CORNER_BOTTOM_RIGHT 3
115
116 #define DIR_HORZ 0
117 #define DIR_VERT 1
118
119 static PinIcon *current_wink_icon = NULL;
120 static gint wink_timeout;
121
122 /* Used for the text colours (only) in the icons */
123 GdkColor pin_text_fg_col, pin_text_bg_col;
124 PangoFontDescription *pinboard_font = NULL; /* NULL => Gtk default */
125
126 static GdkColor pin_text_shadow_col;
127
128 Pinboard *current_pinboard = NULL;
129 static gint loading_pinboard = 0; /* Non-zero => loading */
130
131 /* The Icon that was used to start the current drag, if any */
132 Icon *pinboard_drag_in_progress = NULL;
133
134 /* For selecting groups of icons */
135 static gboolean lasso_in_progress = FALSE;
136 static int lasso_rect_x1, lasso_rect_x2;
137 static int lasso_rect_y1, lasso_rect_y2;
138
139 /* Tracking icon positions */
140 static GHashTable *placed_icons=NULL;
141
142 Option o_pinboard_tasklist_per_workspace;
143 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
144 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
145 static Option o_pinboard_tasklist, o_forward_buttons_13;
146 static Option o_iconify_start, o_iconify_dir;
147 static Option o_label_font, o_pinboard_shadow_colour;
148 static Option o_pinboard_shadow_labels;
149 static Option o_blackbox_hack;
150
151 static Option o_top_margin, o_bottom_margin, o_left_margin, o_right_margin;
152 static Option o_pinboard_image_scaling;
153
154 /* Static prototypes */
155 static GType pin_icon_get_type(void);
156 static void set_size_and_style(PinIcon *pi);
157 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
158 static gint end_wink(gpointer data);
159 static gboolean button_release_event(GtkWidget *widget,
160 GdkEventButton *event,
161 PinIcon *pi);
162 static gboolean enter_notify(GtkWidget *widget,
163 GdkEventCrossing *event,
164 PinIcon *pi);
165 static gint leave_notify(GtkWidget *widget,
166 GdkEventCrossing *event,
167 PinIcon *pi);
168 static gboolean button_press_event(GtkWidget *widget,
169 GdkEventButton *event,
170 PinIcon *pi);
171 static gboolean scroll_event(GtkWidget *widget,
172 GdkEventScroll *event);
173 static gint icon_motion_notify(GtkWidget *widget,
174 GdkEventMotion *event,
175 PinIcon *pi);
176 static const char *pin_from_file(gchar *line);
177 static void snap_to_grid(int *x, int *y);
178 static void offset_from_centre(PinIcon *pi, int *x, int *y);
179 static gboolean drag_motion(GtkWidget *widget,
180 GdkDragContext *context,
181 gint x,
182 gint y,
183 guint time,
184 PinIcon *pi);
185 static void drag_set_pinicon_dest(PinIcon *pi);
186 static void drag_leave(GtkWidget *widget,
187 GdkDragContext *context,
188 guint32 time,
189 PinIcon *pi);
190 static gboolean bg_drag_motion(GtkWidget *widget,
191 GdkDragContext *context,
192 gint x,
193 gint y,
194 guint time,
195 gpointer data);
196 static gboolean bg_drag_leave(GtkWidget *widget,
197 GdkDragContext *context,
198 guint32 time,
199 gpointer data);
200 static gboolean bg_expose(GtkWidget *window,
201 GdkEventExpose *event, gpointer data);
202 static void drag_end(GtkWidget *widget,
203 GdkDragContext *context,
204 PinIcon *pi);
205 static void reshape_all(void);
206 static void pinboard_check_options(void);
207 static void pinboard_load_from_xml(xmlDocPtr doc);
208 static void pinboard_clear(void);
209 static void pinboard_save(void);
210 static PinIcon *pin_icon_new(const char *pathname, const char *name);
211 static void pin_icon_destroyed(PinIcon *pi);
212 static void pin_icon_set_tip(PinIcon *pi);
213 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
214 static void create_pinboard_window(Pinboard *pinboard);
215 static void reload_backdrop(Pinboard *pinboard,
216 const gchar *backdrop,
217 BackdropStyle backdrop_style);
218 static void pinboard_reshape_icon(Icon *icon);
219 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
220 static void abandon_backdrop_app(Pinboard *pinboard);
221 static void drag_backdrop_dropped(GtkWidget *drop_box,
222 const guchar *path,
223 GtkWidget *dialog);
224 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
225 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
226 gboolean old, int start, int direction);
227 static void update_pinboard_font(void);
228 static void draw_lasso(void);
229 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d);
230 static void clear_backdrop(GtkWidget *drop_box, gpointer data);
231 static void radios_changed(Radios *radios, gpointer data);
232 static void update_radios(GtkWidget *dialog);
233 static void pinboard_set_backdrop_box(void);
234
235 /****************************************************************
236 * EXTERNAL INTERFACE *
237 ****************************************************************/
238
239 void pinboard_init(void)
240 {
241 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
242 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
243 option_add_string(&o_pinboard_shadow_colour, "pinboard_shadow_colour",
244 "#000");
245 option_add_string(&o_label_font, "label_font", "");
246 option_add_int(&o_pinboard_shadow_labels, "pinboard_shadow_labels", 1);
247
248 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
249 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
250 GRID_STEP_COARSE);
251 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
252 option_add_int(&o_pinboard_tasklist_per_workspace, "pinboard_tasklist_per_workspace", FALSE);
253 option_add_int(&o_forward_buttons_13, "pinboard_forward_buttons_13",
254 FALSE);
255
256 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
257 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
258
259 option_add_int(&o_blackbox_hack, "blackbox_hack", FALSE);
260
261 option_add_int(&o_top_margin, "pinboard_top_margin", 0);
262 option_add_int(&o_bottom_margin, "pinboard_bottom_margin", 0);
263 option_add_int(&o_left_margin, "pinboard_left_margin", 0);
264 option_add_int(&o_right_margin, "pinboard_right_margin", 0);
265
266 option_add_int(&o_pinboard_image_scaling, "pinboard_image_scaling", 0);
267
268 option_add_notify(pinboard_check_options);
269
270 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
271 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
272 gdk_color_parse(o_pinboard_shadow_colour.value, &pin_text_shadow_col);
273 update_pinboard_font();
274
275 placed_icons=g_hash_table_new(g_str_hash, g_str_equal);
276 }
277
278 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
279 * and make it the current pinboard.
280 * Any existing pinned items are removed. You must call this
281 * at least once before using the pinboard. NULL disables the
282 * pinboard.
283 */
284 void pinboard_activate(const gchar *name)
285 {
286 Pinboard *old_board = current_pinboard;
287 guchar *path, *slash;
288
289 /* Treat an empty name the same as NULL */
290 if (name && !*name)
291 name = NULL;
292
293 if (old_board)
294 pinboard_clear();
295
296 if (!name)
297 {
298 if (number_of_windows < 1 && gtk_main_level() > 0)
299 gtk_main_quit();
300
301 gdk_property_delete(gdk_get_default_root_window(),
302 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
303 return;
304 }
305
306 number_of_windows++;
307
308 slash = strchr(name, '/');
309 if (slash)
310 {
311 if (access(name, F_OK))
312 path = NULL; /* File does not (yet) exist */
313 else
314 path = g_strdup(name);
315 }
316 else
317 {
318 guchar *leaf;
319
320 leaf = g_strconcat("pb_", name, NULL);
321 path = choices_find_xdg_path_load(leaf, PROJECT, SITE);
322 g_free(leaf);
323 }
324
325 current_pinboard = g_new(Pinboard, 1);
326 current_pinboard->name = g_strdup(name);
327 current_pinboard->icons = NULL;
328 current_pinboard->window = NULL;
329 current_pinboard->backdrop = NULL;
330 current_pinboard->backdrop_style = BACKDROP_NONE;
331 current_pinboard->to_backdrop_app = -1;
332 current_pinboard->from_backdrop_app = -1;
333 current_pinboard->input_tag = -1;
334 current_pinboard->input_buffer = NULL;
335
336 create_pinboard_window(current_pinboard);
337
338 loading_pinboard++;
339 if (path)
340 {
341 xmlDocPtr doc;
342 doc = xmlParseFile(path);
343 if (doc)
344 {
345 pinboard_load_from_xml(doc);
346 xmlFreeDoc(doc);
347 reload_backdrop(current_pinboard,
348 current_pinboard->backdrop,
349 current_pinboard->backdrop_style);
350 }
351 else
352 {
353 parse_file(path, pin_from_file);
354 info_message(_("Your old pinboard file has been "
355 "converted to the new XML format."));
356 pinboard_save();
357 }
358 g_free(path);
359 }
360 else
361 pinboard_pin(home_dir, "Home",
362 4 + ICON_WIDTH / 2,
363 4 + ICON_HEIGHT / 2,
364 NULL);
365 loading_pinboard--;
366
367 if (o_pinboard_tasklist.int_value)
368 tasklist_set_active(TRUE);
369 }
370
371 /* Return the window of the current pinboard, or NULL.
372 * Used to make sure lowering the panels doesn't lose them...
373 */
374 GdkWindow *pinboard_get_window(void)
375 {
376 if (current_pinboard)
377 return current_pinboard->window->window;
378 return NULL;
379 }
380
381 const char *pinboard_get_name(void)
382 {
383 g_return_val_if_fail(current_pinboard != NULL, NULL);
384
385 return current_pinboard->name;
386 }
387
388 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
389 * being cleared.
390 */
391 void pinboard_add_widget(GtkWidget *widget, const gchar *name)
392 {
393 GtkRequisition req;
394 GdkRectangle *rect=NULL;
395 gboolean found=FALSE;
396
397 g_return_if_fail(current_pinboard != NULL);
398
399 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
400
401 gtk_widget_size_request(widget, &req);
402
403 if(name) {
404 rect=g_hash_table_lookup(placed_icons, name);
405 if(rect)
406 found=TRUE;
407 }
408 /*printf("%s at %p %d\n", name? name: "(nil)", rect, found);*/
409
410 if(rect) {
411 if(rect->width<req.width || rect->height<req.height) {
412 found=FALSE;
413 }
414 } else {
415 rect=g_new(GdkRectangle, 1);
416 rect->width = req.width;
417 rect->height = req.height;
418 }
419 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
420 rect->y, found);*/
421 find_free_rect(current_pinboard, rect, found,
422 o_iconify_start.int_value, o_iconify_dir.int_value);
423 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
424 rect->y, found);*/
425
426 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
427 widget, rect->x, rect->y);
428
429 /* Store the new position (key and value are never freed) */
430 if(name)
431 g_hash_table_insert(placed_icons, g_strdup(name),
432 rect);
433 }
434
435 void pinboard_moved_widget(GtkWidget *widget, const gchar *name,
436 int x, int y)
437 {
438 GdkRectangle *rect;
439
440 if(!name)
441 return;
442 rect=g_hash_table_lookup(placed_icons, name);
443 if(!rect)
444 return;
445
446 rect->x=x;
447 rect->y=y;
448 }
449
450 /* Add a new icon to the background.
451 * 'path' should be an absolute pathname.
452 * 'x' and 'y' are the coordinates of the point in the middle of the text.
453 * If they are negative, the icon is placed automatically.
454 * The values then indicate where they should be added.
455 * x: -1 means left, -2 means right
456 * y: -1 means top, -2 means bottom
457 * 'name' is the name to use. If NULL then the leafname of path is used.
458 * If update is TRUE and there's already an icon for that path, it is updated.
459 */
460 void pinboard_pin_with_args(const gchar *path, const gchar *name,
461 int x, int y, const gchar *shortcut,
462 const gchar *args, gboolean locked, gboolean update)
463 {
464 GtkWidget *align, *vbox;
465 GdkWindow *events;
466 PinIcon *pi;
467 Icon *icon;
468
469 g_return_if_fail(path != NULL);
470 g_return_if_fail(current_pinboard != NULL);
471
472 pi = NULL;
473
474 if (update)
475 {
476 GList *iter;
477
478 for (iter = current_pinboard->icons; iter; iter = iter->next)
479 {
480 icon = (Icon *) iter->data;
481 if (strcmp(icon->path, path) == 0)
482 {
483 pi = (PinIcon *) icon;
484 break;
485 }
486 }
487 if (pi)
488 {
489 if (x < 0)
490 x = pi->x;
491 if (y < 0)
492 y = pi->y;
493 icon_set_path(icon, path, name);
494 goto out; /* The icon is already on the pinboard. */
495 }
496 }
497
498 pi = pin_icon_new(path, name);
499 icon = (Icon *) pi;
500
501 /* This is a bit complicated...
502 *
503 * An icon needs to be a NO_WINDOW widget so that the image can
504 * blend with the background (A ParentRelative window also works, but
505 * is slow, causes the xfree86's memory consumption to grow without
506 * bound, and doesn't even get freed when the filer quits!).
507 *
508 * However, the icon also needs to have a window, so we get events
509 * delivered correctly. The solution is to float an InputOnly window
510 * over the icon. Since GtkButton works the same way, we just use
511 * that :-)
512 */
513
514 /* Button takes the initial ref of Icon */
515 pi->win = gtk_button_new();
516 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
517 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
518 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
519
520 vbox = gtk_vbox_new(FALSE, 0);
521 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
522
523 align = gtk_alignment_new(0.5, 0.5, 0, 0);
524 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
525 gtk_container_add(GTK_CONTAINER(align), pi->widget);
526
527 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
528 drag_set_pinicon_dest(pi);
529 g_signal_connect(pi->win, "drag_data_get",
530 G_CALLBACK(drag_data_get), NULL);
531
532 pi->label = wrapped_label_new(icon->item->leafname, 180);
533 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
534
535 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
536
537 /* find free space for item if position is not given */
538 if (x < 0 || y < 0)
539 {
540 GtkRequisition req;
541 GdkRectangle rect;
542 int placement = CORNER_TOP_LEFT;
543
544 switch (x)
545 {
546 case -1:
547 if (y == -2)
548 placement = CORNER_BOTTOM_LEFT;
549 break;
550 case -2:
551 switch (y)
552 {
553 case -1:
554 placement = CORNER_TOP_RIGHT;
555 break;
556 case -2:
557 placement = CORNER_BOTTOM_RIGHT;
558 break;
559 }
560 break;
561 }
562
563 pinboard_reshape_icon((Icon *) pi);
564 gtk_widget_size_request(pi->widget, &req);
565 rect.width = req.width;
566 rect.height = req.height;
567 gtk_widget_size_request(pi->label, &req);
568 rect.width = MAX(rect.width, req.width);
569 rect.height += req.height;
570 find_free_rect(current_pinboard, &rect, FALSE, placement, DIR_VERT);
571 x = rect.x + rect.width/2;
572 y = rect.y + rect.height/2;
573 }
574
575 gtk_widget_show_all(pi->win);
576
577 events = GTK_BUTTON(pi->win)->event_window;
578 gdk_window_set_events(events,
579 GDK_EXPOSURE_MASK |
580 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
581 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
582 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
583 GDK_BUTTON3_MOTION_MASK);
584 g_signal_connect(pi->win, "enter-notify-event",
585 G_CALLBACK(enter_notify), pi);
586 g_signal_connect(pi->win, "leave-notify-event",
587 G_CALLBACK(leave_notify), pi);
588 g_signal_connect(pi->win, "button-press-event",
589 G_CALLBACK(button_press_event), pi);
590 g_signal_connect(pi->win, "button-release-event",
591 G_CALLBACK(button_release_event), pi);
592 g_signal_connect(pi->win, "motion-notify-event",
593 G_CALLBACK(icon_motion_notify), pi);
594 g_signal_connect(pi->win, "expose-event",
595 G_CALLBACK(draw_icon), pi);
596 g_signal_connect_swapped(pi->win, "style-set",
597 G_CALLBACK(pinboard_reshape_icon), pi);
598 g_signal_connect_swapped(pi->win, "destroy",
599 G_CALLBACK(pin_icon_destroyed), pi);
600
601 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
602
603 out:
604 snap_to_grid(&x, &y);
605
606 pi->x = x;
607 pi->y = y;
608
609 pinboard_reshape_icon((Icon *) pi);
610
611 gtk_widget_realize(pi->win);
612
613 pin_icon_set_tip(pi);
614
615 icon_set_shortcut(icon, shortcut);
616 icon_set_arguments(icon, args);
617 icon->locked = locked;
618
619 if (!loading_pinboard)
620 pinboard_save();
621 }
622
623 void pinboard_pin(const gchar *path, const gchar *name, int x, int y,
624 const gchar *shortcut)
625 {
626 pinboard_pin_with_args(path, name, x, y, shortcut, NULL, FALSE, FALSE);
627 }
628
629 /*
630 * Remove an icon from the background. The first icon with a matching
631 * path is removed. If name is not NULL then that also must match
632 */
633 gboolean pinboard_remove(const gchar *path, const gchar *name)
634 {
635 GList *item;
636 PinIcon *pi;
637 Icon *icon;
638
639 g_return_val_if_fail(current_pinboard != NULL, FALSE);
640
641 for(item=current_pinboard->icons; item; item=g_list_next(item)) {
642 pi=(PinIcon *) item->data;
643 icon=(Icon *) pi;
644
645 if(strcmp(icon->path, path)!=0)
646 continue;
647
648 if(name && strcmp(name, icon->item->leafname)!=0)
649 continue;
650
651 icon->locked = FALSE;
652 icon_destroy(icon);
653
654 pinboard_save();
655
656 return TRUE;
657 }
658
659 return FALSE;
660 }
661
662 /* Put a border around the icon, briefly.
663 * If icon is NULL then cancel any existing wink.
664 * The icon will automatically unhighlight unless timeout is FALSE,
665 * in which case you must call this function again (with NULL or another
666 * icon) to remove the highlight.
667 */
668 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
669 {
670 PinIcon *old = current_wink_icon;
671
672 if (old == pi)
673 return;
674
675 current_wink_icon = pi;
676
677 if (old)
678 {
679 gtk_widget_queue_draw(old->win);
680 gdk_window_process_updates(old->widget->window, TRUE);
681
682 if (wink_timeout != -1)
683 g_source_remove(wink_timeout);
684 }
685
686 if (pi)
687 {
688 gtk_widget_queue_draw(pi->win);
689 gdk_window_process_updates(pi->widget->window, TRUE);
690
691 if (timeout)
692 wink_timeout = g_timeout_add(300, end_wink, NULL);
693 else
694 wink_timeout = -1;
695 }
696 }
697
698 /* 'app' is saved as the new application to set the backdrop. It will then be
699 * run, and should communicate with the filer as described in the manual.
700 */
701 void pinboard_set_backdrop_app(const gchar *app)
702 {
703 XMLwrapper *ai;
704 DirItem *item;
705 gboolean can_set;
706
707 item = diritem_new("");
708 diritem_restat(app, item, NULL);
709 if (!(item->flags & ITEM_FLAG_APPDIR))
710 {
711 delayed_error(_("The backdrop handler must be an application "
712 "directory. Drag an application directory "
713 "into the Set Backdrop dialog box, or (for "
714 "programmers) pass it to the SOAP "
715 "SetBackdropApp method."));
716 diritem_free(item);
717 return;
718 }
719
720 ai = appinfo_get(app, item);
721 diritem_free(item);
722
723 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
724 if (ai)
725 g_object_unref(ai);
726
727 if (can_set)
728 pinboard_set_backdrop(app, BACKDROP_PROGRAM);
729 else
730 delayed_error(_("You can only set the backdrop to an image "
731 "or to a program which knows how to "
732 "manage ROX-Filer's backdrop.\n\n"
733 "Programmers: the application's AppInfo.xml "
734 "must contain the CanSetBackdrop element, as "
735 "described in ROX-Filer's manual."));
736 }
737
738 /* Open a dialog box allowing the user to set the backdrop */
739 static void pinboard_set_backdrop_box(void)
740 {
741 GtkWidget *dialog, *frame, *label, *hbox;
742 GtkBox *vbox;
743 Radios *radios;
744
745 g_return_if_fail(current_pinboard != NULL);
746
747 if (set_backdrop_dialog)
748 gtk_widget_destroy(set_backdrop_dialog);
749
750 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
751 GTK_DIALOG_NO_SEPARATOR,
752 GTK_STOCK_CLOSE, GTK_RESPONSE_OK,
753 NULL);
754 set_backdrop_dialog = dialog;
755 g_signal_connect(dialog, "destroy",
756 G_CALLBACK(gtk_widget_destroyed), &set_backdrop_dialog);
757 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
758
759 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
760
761 label = gtk_label_new(_("Choose a style and drag an image in:"));
762 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
763 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
764 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
765
766 /* The Centred, Scaled, Fit, Tiled radios... */
767 hbox = gtk_hbox_new(TRUE, 2);
768 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
769
770 radios = radios_new(radios_changed, dialog);
771 g_object_set_data(G_OBJECT(dialog), "rox-radios", radios);
772 g_object_set_data(G_OBJECT(dialog), "rox-radios-hbox", hbox);
773
774 radios_add(radios, _("Centre the image without scaling it"),
775 BACKDROP_CENTRE, _("Centre"));
776 radios_add(radios, _("Scale the image to fit the backdrop area, "
777 "without distorting it"),
778 BACKDROP_SCALE, _("Scale"));
779 radios_add(radios, _("Scale the image to fit the backdrop area, "
780 "regardless of image dimensions - overscale"),
781 BACKDROP_FIT, _("Fit"));
782 radios_add(radios, _("Stretch the image to fill the backdrop area"),
783 BACKDROP_STRETCH, _("Stretch"));
784 radios_add(radios, _("Tile the image over the backdrop area"),
785 BACKDROP_TILE, _("Tile"));
786
787 update_radios(dialog);
788
789 /* The drop area... */
790 frame = drop_box_new(_("Drop an image here"));
791 g_object_set_data(G_OBJECT(dialog), "rox-dropbox", frame);
792
793 radios_pack(radios, GTK_BOX(hbox));
794 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
795
796 drop_box_set_path(DROP_BOX(frame), current_pinboard->backdrop);
797
798 g_signal_connect(frame, "path_dropped",
799 G_CALLBACK(drag_backdrop_dropped), dialog);
800 g_signal_connect(frame, "clear",
801 G_CALLBACK(clear_backdrop), dialog);
802
803 g_signal_connect(dialog, "response",
804 G_CALLBACK(backdrop_response), NULL);
805 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
806
807 gtk_widget_show_all(dialog);
808 }
809
810 /* Also used by tasklist.c */
811 void draw_label_shadow(WrappedLabel *wl, GdkRegion *region)
812 {
813 GtkWidget *widget;
814 gint x, y;
815
816 if (!o_pinboard_shadow_labels.int_value)
817 return;
818
819 gdk_gc_set_clip_region(current_pinboard->shadow_gc, region);
820
821 widget = GTK_WIDGET(wl);
822
823 y = widget->allocation.y - wl->y_off;
824 x = widget->allocation.x - wl->x_off -
825 ((wl->text_width - widget->allocation.width) >> 1);
826
827 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
828 x + 1, y + 1, wl->layout);
829
830 if (o_pinboard_shadow_labels.int_value > 1)
831 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
832 x + 2, y + 2, wl->layout);
833
834 gdk_gc_set_clip_region(current_pinboard->shadow_gc, NULL);
835 }
836
837 /* Set and save (path, style) as the new backdrop.
838 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
839 * Otherwise, the image is displayed now.
840 */
841 void pinboard_set_backdrop(const gchar *path, BackdropStyle style)
842 {
843 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
844 (path != NULL && style != BACKDROP_NONE));
845
846 if (!current_pinboard)
847 {
848 if (!path)
849 return;
850 pinboard_activate("Default");
851 delayed_error(_("No pinboard was in use... "
852 "the 'Default' pinboard has been selected. "
853 "Use 'rox -p=Default' to turn it on in "
854 "future."));
855 g_return_if_fail(current_pinboard != NULL);
856 }
857
858 /* We might have just run the old backdrop program and now
859 * we're going to set a new one! Seems a bit mean...
860 */
861
862 abandon_backdrop_app(current_pinboard);
863
864 g_free(current_pinboard->backdrop);
865 current_pinboard->backdrop = g_strdup(path);
866 current_pinboard->backdrop_style = style;
867 reload_backdrop(current_pinboard,
868 current_pinboard->backdrop,
869 current_pinboard->backdrop_style);
870
871 pinboard_save();
872
873 if (set_backdrop_dialog)
874 {
875 DropBox *box = g_object_get_data(G_OBJECT(set_backdrop_dialog),
876 "rox-dropbox");
877 g_return_if_fail(box != NULL);
878 drop_box_set_path(box, current_pinboard->backdrop);
879 update_radios(set_backdrop_dialog);
880 }
881 }
882
883 /* Called on xrandr screen resizes */
884 void pinboard_update_size(void)
885 {
886 int width, height;
887
888 if (!current_pinboard)
889 return;
890
891 gtk_window_get_size(GTK_WINDOW(current_pinboard->window),
892 &width, &height);
893
894 /* Only update the pinboard's size if the screen gets bigger,
895 * not smaller. Not sure what to do about icons that end up
896 * offscreen if the screen shrinks, but perhaps a good policy
897 * is to leave them there until the screen size is increased
898 * again rather than mess them around. */
899 width = MAX(width, screen_width);
900 height = MAX(height, screen_height);
901
902 gtk_widget_set_size_request(current_pinboard->window, width, height);
903 }
904
905 /****************************************************************
906 * INTERNAL FUNCTIONS *
907 ****************************************************************/
908
909 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
910 {
911 gtk_widget_destroy(dialog);
912 }
913
914 static void clear_backdrop(GtkWidget *drop_box, gpointer data)
915 {
916 pinboard_set_backdrop(NULL, BACKDROP_NONE);
917 }
918
919 static void drag_backdrop_dropped(GtkWidget *drop_box,
920 const guchar *path,
921 GtkWidget *dialog)
922 {
923 struct stat info;
924 Radios *radios;
925
926 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
927 g_return_if_fail(radios != NULL);
928
929 if (mc_stat(path, &info))
930 {
931 delayed_error(
932 _("Can't access '%s':\n%s"), path,
933 g_strerror(errno));
934 return;
935 }
936
937 if (S_ISDIR(info.st_mode))
938 {
939 /* Use this program to set the backdrop */
940 pinboard_set_backdrop_app(path);
941 }
942 else if (S_ISREG(info.st_mode))
943 pinboard_set_backdrop(path, radios_get_value(radios));
944 else
945 delayed_error(_("Only files (and certain applications) can be "
946 "used to set the background image."));
947 }
948
949 /* Do this in the idle loop so that we don't try to put an unmanaged
950 * pinboard behind a managed panel (crashes some WMs).
951 */
952 static gboolean recreate_pinboard(gchar *name)
953 {
954 pinboard_activate(name);
955 g_free(name);
956
957 return FALSE;
958 }
959
960 static void pinboard_check_options(void)
961 {
962 GdkColor n_fg, n_bg, n_shadow;
963
964 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
965 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
966 gdk_color_parse(o_pinboard_shadow_colour.value, &n_shadow);
967
968 if (o_override_redirect.has_changed && current_pinboard)
969 {
970 gchar *name;
971 name = g_strdup(current_pinboard->name);
972 pinboard_activate(NULL);
973 g_idle_add((GtkFunction) recreate_pinboard, name);
974 }
975
976 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
977
978 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
979 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0 ||
980 gdk_color_equal(&n_shadow, &pin_text_shadow_col) == 0 ||
981 o_pinboard_shadow_labels.has_changed ||
982 o_label_font.has_changed)
983 {
984 pin_text_fg_col = n_fg;
985 pin_text_bg_col = n_bg;
986 pin_text_shadow_col = n_shadow;
987 update_pinboard_font();
988
989 if (current_pinboard)
990 {
991 GtkWidget *w = current_pinboard->window;
992 GdkColormap *cm;
993
994 cm = gtk_widget_get_colormap(w);
995
996 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
997 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
998
999 gdk_gc_set_rgb_fg_color(current_pinboard->shadow_gc,
1000 &n_shadow);
1001
1002 abandon_backdrop_app(current_pinboard);
1003 reload_backdrop(current_pinboard,
1004 current_pinboard->backdrop,
1005 current_pinboard->backdrop_style);
1006
1007 reshape_all();
1008 }
1009
1010 tasklist_style_changed();
1011 }
1012 }
1013
1014 static gint end_wink(gpointer data)
1015 {
1016 pinboard_wink_item(NULL, FALSE);
1017 return FALSE;
1018 }
1019
1020 /* Sets the appearance from the options and updates the size request of
1021 * the image.
1022 */
1023 static void set_size_and_style(PinIcon *pi)
1024 {
1025 Icon *icon = (Icon *) pi;
1026 MaskedPixmap *image = di_image(icon->item);
1027 int iwidth = image->width;
1028 int iheight = image->height;
1029
1030 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
1031 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
1032 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
1033 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
1034 widget_modify_font(pi->label, pinboard_font);
1035
1036 wrapped_label_set_text(WRAPPED_LABEL(pi->label), icon->item->leafname);
1037
1038 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
1039 }
1040
1041 static GdkPixbuf *get_stock_icon(GtkWidget *widget,
1042 const char *stock_id)
1043 {
1044 GtkIconSet *icon_set;
1045 GdkPixbuf *pixbuf;
1046
1047 icon_set = gtk_style_lookup_icon_set(widget->style,
1048 stock_id);
1049 if (icon_set)
1050 {
1051 pixbuf = gtk_icon_set_render_icon(icon_set,
1052 widget->style,
1053 GTK_TEXT_DIR_LTR,
1054 GTK_STATE_NORMAL,
1055 mount_icon_size,
1056 NULL,
1057 NULL);
1058 }
1059 else
1060 {
1061 pixbuf=im_unknown->pixbuf;
1062 g_object_ref(pixbuf);
1063 }
1064
1065 return pixbuf;
1066 }
1067
1068 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
1069 {
1070 static GtkWidgetClass *parent_class = NULL;
1071 Icon *icon = (Icon *) pi;
1072 DirItem *item = icon->item;
1073 MaskedPixmap *image = di_image(item);
1074 int iwidth = image->width;
1075 int iheight = image->height;
1076 int x, y;
1077 GdkPixbuf *pixbuf;
1078
1079 if (!parent_class)
1080 {
1081 gpointer c = ((GTypeInstance *) widget)->g_class;
1082 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
1083 }
1084
1085 x = pi->widget->allocation.x;
1086 y = pi->widget->allocation.y;
1087
1088 gdk_gc_set_clip_region(pi->widget->style->black_gc, event->region);
1089
1090 pixbuf = icon->selected
1091 ? create_spotlight_pixbuf(image->pixbuf,
1092 &pi->widget->style->base[GTK_STATE_SELECTED])
1093 : image->pixbuf;
1094
1095 render_pixbuf(pixbuf,
1096 pi->widget->window,
1097 pi->widget->style->black_gc,
1098 x, y, iwidth, iheight);
1099
1100 if (icon->selected)
1101 g_object_unref(pixbuf);
1102
1103 if (item->flags & ITEM_FLAG_SYMLINK)
1104 {
1105 GdkPixbuf *emblem = get_stock_icon(pi->widget,
1106 ROX_STOCK_SYMLINK);
1107 render_pixbuf(emblem, pi->widget->window,
1108 pi->widget->style->black_gc,
1109 x, y, -1, -1);
1110 g_object_unref(emblem);
1111 }
1112 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
1113 {
1114 GdkPixbuf *emblem = get_stock_icon(pi->widget,
1115 item->flags & ITEM_FLAG_MOUNTED
1116 ? ROX_STOCK_MOUNTED
1117 : ROX_STOCK_MOUNT);
1118
1119 render_pixbuf(emblem, pi->widget->window,
1120 pi->widget->style->black_gc,
1121 x, y, -1, -1);
1122 g_object_unref(emblem);
1123 }
1124
1125 gdk_gc_set_clip_region(pi->widget->style->black_gc, NULL);
1126
1127 if (icon->selected)
1128 {
1129 GtkStyle *style = pi->label->style;
1130 GdkGC *gc = style->bg_gc[GTK_STATE_SELECTED];
1131
1132 gdk_gc_set_clip_region(gc, event->region);
1133 gdk_draw_rectangle(pi->label->window, gc, TRUE,
1134 pi->label->allocation.x,
1135 pi->label->allocation.y,
1136 pi->label->allocation.width,
1137 pi->label->allocation.height);
1138 gdk_gc_set_clip_region(gc, NULL);
1139 }
1140 else if (o_pinboard_shadow_labels.int_value)
1141 draw_label_shadow((WrappedLabel *) pi->label, event->region);
1142
1143 /* Draw children */
1144 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL],
1145 event->region);
1146 (parent_class->expose_event)(widget, event);
1147 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL], NULL);
1148
1149 /* Stop the button effect */
1150 return TRUE;
1151 }
1152
1153 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
1154 {
1155 gint x, y, width, height;
1156
1157 if (current_wink_icon != pi)
1158 return FALSE;
1159
1160 x = widget->allocation.x;
1161 y = widget->allocation.y;
1162 width = widget->allocation.width;
1163 height = widget->allocation.height;
1164
1165 gdk_draw_rectangle(widget->window,
1166 pi->widget->style->white_gc,
1167 FALSE,
1168 x, y, width - 1, height - 1);
1169 gdk_draw_rectangle(widget->window,
1170 pi->widget->style->black_gc,
1171 FALSE,
1172 x + 1, y + 1, width - 3, height - 3);
1173
1174 return FALSE;
1175 }
1176
1177 static gboolean enter_notify(GtkWidget *widget,
1178 GdkEventCrossing *event,
1179 PinIcon *pi)
1180 {
1181 icon_may_update((Icon *) pi);
1182 pin_icon_set_tip(pi);
1183 return TRUE;
1184 }
1185
1186 static gint leave_notify(GtkWidget *widget,
1187 GdkEventCrossing *event,
1188 PinIcon *pi)
1189 {
1190 return TRUE;
1191 }
1192
1193 static void select_lasso(void)
1194 {
1195 GList *next;
1196 int minx, miny, maxx, maxy;
1197
1198 g_return_if_fail(lasso_in_progress == TRUE);
1199
1200 minx = MIN(lasso_rect_x1, lasso_rect_x2);
1201 miny = MIN(lasso_rect_y1, lasso_rect_y2);
1202 maxx = MAX(lasso_rect_x1, lasso_rect_x2);
1203 maxy = MAX(lasso_rect_y1, lasso_rect_y2);
1204
1205 for (next = current_pinboard->icons; next; next = next->next)
1206 {
1207 PinIcon *pi = (PinIcon *) next->data;
1208 GtkAllocation *alloc = &pi->win->allocation;
1209 int cx = alloc->x + alloc->width / 2;
1210 int cy = alloc->y + alloc->height / 2;
1211
1212 if (cx > minx && cx < maxx && cy > miny && cy < maxy)
1213 icon_set_selected((Icon *) pi, TRUE);
1214 }
1215 }
1216
1217 static void cancel_lasso(void)
1218 {
1219 draw_lasso();
1220 lasso_in_progress = FALSE;
1221 }
1222
1223 static void pinboard_lasso_box(int start_x, int start_y)
1224 {
1225 if (lasso_in_progress)
1226 cancel_lasso();
1227 lasso_in_progress = TRUE;
1228 lasso_rect_x1 = lasso_rect_x2 = start_x;
1229 lasso_rect_y1 = lasso_rect_y2 = start_y;
1230
1231 draw_lasso();
1232 }
1233
1234 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d)
1235 {
1236 if (!lasso_in_progress)
1237 return FALSE;
1238
1239 if (lasso_rect_x2 != event->x || lasso_rect_y2 != event->y)
1240 {
1241 draw_lasso();
1242 lasso_rect_x2 = event->x;
1243 lasso_rect_y2 = event->y;
1244 draw_lasso();
1245 }
1246
1247 return FALSE;
1248 }
1249
1250 /* Mark the area of the screen covered by the lasso box for redraw */
1251 static void draw_lasso(void)
1252 {
1253 GdkRectangle area, edge;
1254
1255 if (!lasso_in_progress)
1256 return;
1257
1258 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1259 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1260 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1261 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1262
1263 edge.x = area.x;
1264 edge.y = area.y;
1265 edge.width = area.width;
1266
1267 edge.height = 2; /* Top */
1268 gdk_window_invalidate_rect(current_pinboard->window->window,
1269 &edge, TRUE);
1270
1271 edge.y += area.height - 2; /* Bottom */
1272 gdk_window_invalidate_rect(current_pinboard->window->window,
1273 &edge, TRUE);
1274
1275 edge.y = area.y;
1276 edge.height = area.height;
1277 edge.width = 2; /* Left */
1278 gdk_window_invalidate_rect(current_pinboard->window->window,
1279 &edge, TRUE);
1280
1281 edge.x += area.width - 2; /* Right */
1282 gdk_window_invalidate_rect(current_pinboard->window->window,
1283 &edge, TRUE);
1284 }
1285
1286 static void perform_action(PinIcon *pi, GdkEventButton *event)
1287 {
1288 BindAction action;
1289 Icon *icon = (Icon *) pi;
1290
1291 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
1292 event);
1293
1294 /* Actions that can happen with or without an icon */
1295 switch (action)
1296 {
1297 case ACT_LASSO_CLEAR:
1298 icon_select_only(NULL);
1299 /* (no break) */
1300 case ACT_LASSO_MODIFY:
1301 pinboard_lasso_box(event->x, event->y);
1302 return;
1303 case ACT_CLEAR_SELECTION:
1304 icon_select_only(NULL);
1305 return;
1306 case ACT_POPUP_MENU:
1307 dnd_motion_ungrab();
1308 pinboard_show_menu(event, pi);
1309 return;
1310 case ACT_IGNORE:
1311 return;
1312 default:
1313 break;
1314 }
1315
1316 g_return_if_fail(pi != NULL);
1317
1318 switch (action)
1319 {
1320 case ACT_OPEN_ITEM:
1321 dnd_motion_ungrab();
1322 pinboard_wink_item(pi, TRUE);
1323 if (event->type == GDK_2BUTTON_PRESS)
1324 icon_set_selected(icon, FALSE);
1325 icon_run(icon);
1326 break;
1327 case ACT_EDIT_ITEM:
1328 dnd_motion_ungrab();
1329 pinboard_wink_item(pi, TRUE);
1330 if (event->type == GDK_2BUTTON_PRESS)
1331 icon_set_selected(icon, FALSE);
1332 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
1333 break;
1334 case ACT_PRIME_AND_SELECT:
1335 if (!icon->selected)
1336 icon_select_only(icon);
1337 dnd_motion_start(MOTION_READY_FOR_DND);
1338 break;
1339 case ACT_PRIME_AND_TOGGLE:
1340 icon_set_selected(icon, !icon->selected);
1341 dnd_motion_start(MOTION_READY_FOR_DND);
1342 break;
1343 case ACT_PRIME_FOR_DND:
1344 dnd_motion_start(MOTION_READY_FOR_DND);
1345 break;
1346 case ACT_TOGGLE_SELECTED:
1347 icon_set_selected(icon, !icon->selected);
1348 break;
1349 case ACT_SELECT_EXCL:
1350 icon_select_only(icon);
1351 break;
1352 default:
1353 g_warning("Unsupported action : %d\n", action);
1354 break;
1355 }
1356 }
1357
1358 static void forward_to_root(GdkEventButton *event)
1359 {
1360 XButtonEvent xev;
1361
1362 if (event->type == GDK_BUTTON_PRESS)
1363 {
1364 xev.type = ButtonPress;
1365 if (!o_blackbox_hack.int_value)
1366 {
1367 gdk_display_pointer_ungrab(gdk_x11_lookup_xdisplay(gdk_display),
1368 event->time);
1369 }
1370 }
1371 else
1372 xev.type = ButtonRelease;
1373
1374 xev.window = gdk_x11_get_default_root_xwindow();
1375 xev.root = xev.window;
1376 xev.subwindow = None;
1377 xev.time = event->time;
1378 xev.x = event->x_root; /* Needed for icewm */
1379 xev.y = event->y_root;
1380 xev.x_root = event->x_root;
1381 xev.y_root = event->y_root;
1382 xev.state = event->state;
1383 xev.button = event->button;
1384 xev.same_screen = True;
1385
1386 XSendEvent(gdk_display, xev.window, False,
1387 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
1388 }
1389
1390 #define FORWARDED_BUTTON(pi, b) ((b) == 2 || \
1391 (((b) == 3 || (b) == 1) && o_forward_buttons_13.int_value && !pi))
1392
1393 /* pi is NULL if this is a root event */
1394 static gboolean button_release_event(GtkWidget *widget,
1395 GdkEventButton *event,
1396 PinIcon *pi)
1397 {
1398 if (FORWARDED_BUTTON(pi, event->button))
1399 forward_to_root(event);
1400 else if (dnd_motion_release(event))
1401 {
1402 if (motion_buttons_pressed == 0 && lasso_in_progress)
1403 {
1404 select_lasso();
1405 cancel_lasso();
1406 }
1407 return FALSE;
1408 }
1409
1410 perform_action(pi, event);
1411
1412 return TRUE;
1413 }
1414
1415 /* pi is NULL if this is a root event */
1416 static gboolean button_press_event(GtkWidget *widget,
1417 GdkEventButton *event,
1418 PinIcon *pi)
1419 {
1420 /* Just in case we've jumped in front of everything... */
1421 gdk_window_lower(current_pinboard->window->window);
1422
1423 if (FORWARDED_BUTTON(pi, event->button))
1424 forward_to_root(event);
1425 else if (dnd_motion_press(widget, event))
1426 perform_action(pi, event);
1427
1428 return TRUE;
1429 }
1430
1431 /* Forward mouse scroll events as buttons 4 and 5 to the window manager
1432 * (for old window managers that don't catch the buttons themselves)
1433 */
1434 static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event)
1435 {
1436 XButtonEvent xev;
1437
1438 xev.type = ButtonPress;
1439 xev.window = gdk_x11_get_default_root_xwindow();
1440 xev.root = xev.window;
1441 xev.subwindow = None;
1442 xev.time = event->time;
1443 xev.x = event->x_root; /* Needed for icewm */
1444 xev.y = event->y_root;
1445 xev.x_root = event->x_root;
1446 xev.y_root = event->y_root;
1447 xev.state = event->state;
1448 xev.same_screen = True;
1449
1450 if (event->direction == GDK_SCROLL_UP)
1451 xev.button = 4;
1452 else if (event->direction == GDK_SCROLL_DOWN)
1453 xev.button = 5;
1454 else
1455 return FALSE;
1456
1457 XSendEvent(gdk_display, xev.window, False,
1458 ButtonPressMask, (XEvent *) &xev);
1459
1460 return TRUE;
1461 }
1462
1463 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1464 {
1465 GtkWidget *widget = pi->win;
1466 Icon *icon = (Icon *) pi;
1467
1468 if (!icon->selected)
1469 {
1470 tmp_icon_selected = TRUE;
1471 icon_select_only(icon);
1472 }
1473
1474 g_return_if_fail(icon_selection != NULL);
1475
1476 pinboard_drag_in_progress = icon;
1477
1478 if (icon_selection->next == NULL)
1479 drag_one_item(widget, event, icon->path, icon->item, NULL);
1480 else
1481 {
1482 guchar *uri_list;
1483
1484 uri_list = icon_create_uri_list();
1485 drag_selection(widget, event, uri_list);
1486 g_free(uri_list);
1487 }
1488 }
1489
1490 /* An icon is being dragged around... */
1491 static gint icon_motion_notify(GtkWidget *widget,
1492 GdkEventMotion *event,
1493 PinIcon *pi)
1494 {
1495 if (motion_state == MOTION_READY_FOR_DND)
1496 {
1497 if (dnd_motion_moved(event))
1498 start_drag(pi, event);
1499 return TRUE;
1500 }
1501
1502 return FALSE;
1503 }
1504
1505 static void backdrop_from_xml(xmlNode *node)
1506 {
1507 gchar *style;
1508
1509 g_free(current_pinboard->backdrop);
1510 current_pinboard->backdrop = xmlNodeGetContent(node);
1511
1512 style = xmlGetProp(node, "style");
1513
1514 if (style)
1515 {
1516 current_pinboard->backdrop_style =
1517 g_ascii_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1518 g_ascii_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1519 g_ascii_strcasecmp(style, "Fit") == 0 ? BACKDROP_FIT :
1520 g_ascii_strcasecmp(style, "Stretched") == 0 ? BACKDROP_STRETCH :
1521 g_ascii_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1522 g_ascii_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1523 BACKDROP_NONE;
1524 g_free(style);
1525 }
1526 else
1527 current_pinboard->backdrop_style = BACKDROP_TILE;
1528 }
1529
1530 /* Create one pinboard icon for each icon in the doc */
1531 static void pinboard_load_from_xml(xmlDocPtr doc)
1532 {
1533 xmlNodePtr node, root;
1534 char *tmp, *label, *path, *shortcut, *args;
1535 int x, y;
1536 gboolean locked;
1537
1538 root = xmlDocGetRootElement(doc);
1539
1540 for (node = root->xmlChildrenNode; node; node = node->next)
1541 {
1542 if (node->type != XML_ELEMENT_NODE)
1543 continue;
1544 if (strcmp(node->name, "backdrop") == 0)
1545 {
1546 backdrop_from_xml(node);
1547 continue;
1548 }
1549 if (strcmp(node->name, "icon") != 0)
1550 continue;
1551
1552 tmp = xmlGetProp(node, "x");
1553 if (!tmp)
1554 continue;
1555 x = atoi(tmp);
1556 g_free(tmp);
1557
1558 tmp = xmlGetProp(node, "y");
1559 if (!tmp)
1560 continue;
1561 y = atoi(tmp);
1562 g_free(tmp);
1563
1564 label = xmlGetProp(node, "label");
1565 if (!label)
1566 label = g_strdup("<missing label>");
1567 path = xmlNodeGetContent(node);
1568 if (!path)
1569 path = g_strdup("<missing path>");
1570 shortcut = xmlGetProp(node, "shortcut");
1571 args = xmlGetProp(node, "args");
1572
1573 tmp = xmlGetProp(node, "locked");
1574 if (tmp)
1575 {
1576 locked = text_to_boolean(tmp, FALSE);
1577 g_free(tmp);
1578 }
1579 else
1580 locked = FALSE;
1581
1582 pinboard_pin_with_args(path, label, x, y, shortcut, args, locked, FALSE);
1583
1584 g_free(path);
1585 g_free(label);
1586 g_free(shortcut);
1587 g_free(args);
1588 }
1589 }
1590
1591 /* Called for each line in the pinboard file while loading a new board.
1592 * Only used for old-format files when converting to XML.
1593 */
1594 static const char *pin_from_file(gchar *line)
1595 {
1596 gchar *leaf = NULL;
1597 int x, y, n;
1598
1599 if (*line == '<')
1600 {
1601 gchar *end;
1602
1603 end = strchr(line + 1, '>');
1604 if (!end)
1605 return _("Missing '>' in icon label");
1606
1607 leaf = g_strndup(line + 1, end - line - 1);
1608
1609 line = end + 1;
1610
1611 while (g_ascii_isspace(*line))
1612 line++;
1613 if (*line != ',')
1614 return _("Missing ',' after icon label");
1615 line++;
1616 }
1617
1618 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1619 return NULL; /* Ignore format errors */
1620
1621 pinboard_pin(line + n, leaf, x, y, NULL);
1622
1623 g_free(leaf);
1624
1625 return NULL;
1626 }
1627
1628 /* Write the current state of the pinboard to the current pinboard file */
1629 static void pinboard_save(void)
1630 {
1631 guchar *save = NULL;
1632 guchar *save_new = NULL;
1633 GList *next;
1634 xmlDocPtr doc = NULL;
1635 xmlNodePtr root;
1636
1637 g_return_if_fail(current_pinboard != NULL);
1638
1639 if (strchr(current_pinboard->name, '/'))
1640 save = g_strdup(current_pinboard->name);
1641 else
1642 {
1643 guchar *leaf;
1644
1645 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1646 save = choices_find_xdg_path_save(leaf, PROJECT, SITE, TRUE);
1647 g_free(leaf);
1648 }
1649
1650 if (!save)
1651 return;
1652
1653 doc = xmlNewDoc("1.0");
1654 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1655
1656 root = xmlDocGetRootElement(doc);
1657
1658 if (current_pinboard->backdrop)
1659 {
1660 BackdropStyle style = current_pinboard->backdrop_style;
1661 xmlNodePtr tree;
1662
1663 tree = xmlNewTextChild(root, NULL, "backdrop",
1664 current_pinboard->backdrop);
1665 xmlSetProp(tree, "style",
1666 style == BACKDROP_TILE ? "Tiled" :
1667 style == BACKDROP_CENTRE ? "Centred" :
1668 style == BACKDROP_SCALE ? "Scaled" :
1669 style == BACKDROP_FIT ? "Fit" :
1670 style == BACKDROP_STRETCH ? "Stretched" :
1671 "Program");
1672 }
1673
1674 for (next = current_pinboard->icons; next; next = next->next)
1675 {
1676 xmlNodePtr tree;
1677 PinIcon *pi = (PinIcon *) next->data;
1678 Icon *icon = (Icon *) pi;
1679 char *tmp;
1680
1681 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1682
1683 tmp = g_strdup_printf("%d", pi->x);
1684 xmlSetProp(tree, "x", tmp);
1685 g_free(tmp);
1686
1687 tmp = g_strdup_printf("%d", pi->y);
1688 xmlSetProp(tree, "y", tmp);
1689 g_free(tmp);
1690
1691 xmlSetProp(tree, "label", icon->item->leafname);
1692 if (icon->shortcut)
1693 xmlSetProp(tree, "shortcut", icon->shortcut);
1694 if (icon->args)
1695 xmlSetProp(tree, "args", icon->args);
1696 if (icon->locked)
1697 xmlSetProp(tree, "locked", "true");
1698 }
1699
1700 save_new = g_strconcat(save, ".new", NULL);
1701 if (save_xml_file(doc, save_new) || rename(save_new, save))
1702 delayed_error(_("Error saving pinboard %s: %s"),
1703 save, g_strerror(errno));
1704 g_free(save_new);
1705
1706 g_free(save);
1707 if (doc)
1708 xmlFreeDoc(doc);
1709 }
1710
1711 static void snap_to_grid(int *x, int *y)
1712 {
1713 int step = o_pinboard_grid_step.int_value;
1714
1715 *x = ((*x + step / 2) / step) * step;
1716 *y = ((*y + step / 2) / step) * step;
1717 }
1718
1719 /* Convert (x,y) from a centre point to a window position */
1720 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1721 {
1722 gboolean clamp = o_pinboard_clamp_icons.int_value;
1723 GtkRequisition req;
1724
1725 gtk_widget_size_request(pi->win, &req);
1726
1727 *x -= req.width >> 1;
1728 *y -= req.height >> 1;
1729 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1730 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1731 }
1732
1733 /* Same as drag_set_dest(), but for pinboard icons */
1734 static void drag_set_pinicon_dest(PinIcon *pi)
1735 {
1736 GtkObject *obj = GTK_OBJECT(pi->win);
1737
1738 make_drop_target(pi->win, 0);
1739
1740 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1741 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1742 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1743 }
1744
1745 /* Called during the drag when the mouse is in a widget registered
1746 * as a drop target. Returns TRUE if we can accept the drop.
1747 */
1748 static gboolean drag_motion(GtkWidget *widget,
1749 GdkDragContext *context,
1750 gint x,
1751 gint y,
1752 guint time,
1753 PinIcon *pi)
1754 {
1755 GdkDragAction action = context->suggested_action;
1756 const char *type = NULL;
1757 Icon *icon = (Icon *) pi;
1758 DirItem *item = icon->item;
1759
1760 if (gtk_drag_get_source_widget(context) == widget)
1761 {
1762 g_dataset_set_data(context, "drop_dest_type",
1763 (gpointer) drop_dest_pass_through);
1764 return FALSE; /* Can't drag something to itself! */
1765 }
1766
1767 if (icon->selected)
1768 goto out; /* Can't drag a selection to itself */
1769
1770 type = dnd_motion_item(context, &item);
1771
1772 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value
1773 && type != drop_dest_prog)
1774 {
1775 guint state;
1776 gdk_window_get_pointer(NULL, NULL, NULL, &state);
1777 if (state & GDK_BUTTON1_MASK)
1778 action = GDK_ACTION_ASK;
1779 }
1780
1781 if (!item)
1782 type = NULL;
1783 out:
1784 /* We actually must pretend to accept the drop, even if the
1785 * directory isn't writeable, so that the spring-opening
1786 * thing works.
1787 */
1788
1789 /* Don't allow drops to non-writeable directories */
1790 if (o_dnd_spring_open.int_value == FALSE &&
1791 type == drop_dest_dir &&
1792 access(icon->path, W_OK) != 0)
1793 {
1794 type = NULL;
1795 }
1796
1797 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
1798 if (type)
1799 {
1800 gdk_drag_status(context, action, time);
1801 g_dataset_set_data_full(context, "drop_dest_path",
1802 g_strdup(icon->path), g_free);
1803 if (type == drop_dest_dir)
1804 dnd_spring_load(context, NULL);
1805
1806 pinboard_wink_item(pi, FALSE);
1807 }
1808 else
1809 gdk_drag_status(context, 0, time);
1810
1811 /* Always return TRUE to stop the pinboard getting the events */
1812 return TRUE;
1813 }
1814
1815 static gboolean pinboard_shadow = FALSE;
1816 static gint shadow_x, shadow_y;
1817 #define SHADOW_SIZE (ICON_WIDTH)
1818
1819 static gboolean bg_expose(GtkWidget *widget,
1820 GdkEventExpose *event, gpointer data)
1821 {
1822 GdkRectangle clipbox;
1823 gpointer gclass = ((GTypeInstance *) widget)->g_class;
1824 gboolean double_buffer;
1825
1826 gdk_gc_set_clip_region(widget->style->white_gc, event->region);
1827 gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1828
1829 gdk_region_get_clipbox(event->region, &clipbox);
1830
1831 double_buffer = (clipbox.width * clipbox.height) < 20000;
1832 if (double_buffer)
1833 gdk_window_begin_paint_region(widget->window, event->region);
1834
1835 /* Clear the area to the background image */
1836 {
1837 GtkStyle *style = current_pinboard->window->style;
1838 GdkGC *gc = style->bg_gc[GTK_STATE_NORMAL];
1839
1840 gdk_gc_set_clip_region(gc, event->region);
1841 if (style->bg_pixmap[GTK_STATE_NORMAL])
1842 {
1843 gdk_gc_set_ts_origin(gc, 0, 0);
1844 gdk_gc_set_fill(gc, GDK_TILED);
1845 gdk_gc_set_tile(gc, style->bg_pixmap[GTK_STATE_NORMAL]);
1846 }
1847
1848 gdk_draw_rectangle(current_pinboard->window->window, gc, TRUE,
1849 clipbox.x, clipbox.y,
1850 clipbox.width, clipbox.height);
1851 if (style->bg_pixmap[GTK_STATE_NORMAL])
1852 gdk_gc_set_fill(gc, GDK_SOLID);
1853
1854 gdk_gc_set_clip_region(gc, NULL);
1855 }
1856
1857 if (pinboard_shadow)
1858 {
1859 gdk_draw_rectangle(widget->window,
1860 widget->style->white_gc, FALSE,
1861 shadow_x, shadow_y,
1862 SHADOW_SIZE, SHADOW_SIZE);
1863 gdk_draw_rectangle(widget->window,
1864 widget->style->black_gc, FALSE,
1865 shadow_x + 1, shadow_y + 1,
1866 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1867 }
1868
1869 if (lasso_in_progress)
1870 {
1871 GdkRectangle area;
1872
1873 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1874 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1875 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1876 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1877
1878 if (area.width > 4 && area.height > 4)
1879 {
1880 gdk_draw_rectangle(widget->window,
1881 widget->style->white_gc, FALSE,
1882 area.x, area.y,
1883 area.width - 1, area.height - 1);
1884 gdk_draw_rectangle(widget->window,
1885 widget->style->black_gc, FALSE,
1886 area.x + 1, area.y + 1,
1887 area.width - 3, area.height - 3);
1888 }
1889 }
1890
1891 gdk_gc_set_clip_region(widget->style->white_gc, NULL);
1892 gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1893
1894 ((GtkWidgetClass *) gclass)->expose_event(widget, event);
1895
1896 if (double_buffer)
1897 gdk_window_end_paint(widget->window);
1898
1899 return TRUE;
1900 }
1901
1902 /* Draw a 'shadow' under an icon being dragged, showing where
1903 * it will land.
1904 */
1905 static void pinboard_set_shadow(gboolean on)
1906 {
1907 GdkRectangle area;
1908
1909 if (pinboard_shadow)
1910 {
1911 area.x = shadow_x;
1912 area.y = shadow_y;
1913 area.width = SHADOW_SIZE + 1;
1914 area.height = SHADOW_SIZE + 1;
1915
1916 gdk_window_invalidate_rect(current_pinboard->window->window,
1917 &area, TRUE);
1918 }
1919
1920 if (on)
1921 {
1922 int old_x = shadow_x, old_y = shadow_y;
1923
1924 gdk_window_get_pointer(current_pinboard->fixed->window,
1925 &shadow_x, &shadow_y, NULL);
1926 snap_to_grid(&shadow_x, &shadow_y);
1927 shadow_x -= SHADOW_SIZE / 2;
1928 shadow_y -= SHADOW_SIZE / 2;
1929
1930
1931 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1932 return;
1933
1934 area.x = shadow_x;
1935 area.y = shadow_y;
1936 area.width = SHADOW_SIZE + 1;
1937 area.height = SHADOW_SIZE + 1;
1938
1939 gdk_window_invalidate_rect(current_pinboard->window->window,
1940 &area, TRUE);
1941 }
1942
1943 pinboard_shadow = on;
1944 }
1945
1946 /* Called when dragging some pinboard icons finishes */
1947 void pinboard_move_icons(void)
1948 {
1949 int x = shadow_x, y = shadow_y;
1950 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1951 int width, height;
1952 int dx, dy;
1953 GList *next;
1954
1955 g_return_if_fail(pi != NULL);
1956
1957 x += SHADOW_SIZE / 2;
1958 y += SHADOW_SIZE / 2;
1959 snap_to_grid(&x, &y);
1960
1961 if (pi->x == x && pi->y == y)
1962 return;
1963
1964 /* Find out how much the dragged icon moved (after snapping).
1965 * Move all selected icons by the same amount.
1966 */
1967 dx = x - pi->x;
1968 dy = y - pi->y;
1969
1970 /* Move the other selected icons to keep the same relative
1971 * position.
1972 */
1973 for (next = icon_selection; next; next = next->next)
1974 {
1975 PinIcon *pi = (PinIcon *) next->data;
1976 int nx, ny;
1977
1978 g_return_if_fail(IS_PIN_ICON(pi));
1979
1980 pi->x += dx;
1981 pi->y += dy;
1982 nx = pi->x;
1983 ny = pi->y;
1984
1985 gdk_drawable_get_size(pi->win->window, &width, &height);
1986 offset_from_centre(pi, &nx, &ny);
1987
1988 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
1989 pi->win, nx, ny);
1990 }
1991
1992 pinboard_save();
1993 }
1994
1995 static void drag_leave(GtkWidget *widget,
1996 GdkDragContext *context,
1997 guint32 time,
1998 PinIcon *pi)
1999 {
2000 pinboard_wink_item(NULL, FALSE);
2001 dnd_spring_abort();
2002 }
2003
2004 static gboolean bg_drag_leave(GtkWidget *widget,
2005 GdkDragContext *context,
2006 guint32 time,
2007 gpointer data)
2008 {
2009 pinboard_set_shadow(FALSE);
2010 return TRUE;
2011 }
2012
2013 static gboolean bg_drag_motion(GtkWidget *widget,
2014 GdkDragContext *context,
2015 gint x,
2016 gint y,
2017 guint time,
2018 gpointer data)
2019 {
2020 /* Dragging from the pinboard to the pinboard is not allowed */
2021
2022 if (!provides(context, text_uri_list))
2023 return FALSE;
2024
2025 pinboard_set_shadow(TRUE);
2026
2027 gdk_drag_status(context,
2028 context->suggested_action == GDK_ACTION_ASK
2029 ? GDK_ACTION_LINK : context->suggested_action,
2030 time);
2031 return TRUE;
2032 }
2033
2034 static void drag_end(GtkWidget *widget,
2035 GdkDragContext *context,
2036 PinIcon *pi)
2037 {
2038 pinboard_drag_in_progress = NULL;
2039 if (tmp_icon_selected)
2040 {
2041 icon_select_only(NULL);
2042 tmp_icon_selected = FALSE;
2043 }
2044 }
2045
2046 /* Something which affects all the icons has changed - reshape
2047 * and redraw all of them.
2048 */
2049 static void reshape_all(void)
2050 {
2051 GList *next;
2052
2053 g_return_if_fail(current_pinboard != NULL);
2054
2055 for (next = current_pinboard->icons; next; next = next->next)
2056 {
2057 Icon *icon = (Icon *) next->data;
2058 pinboard_reshape_icon(icon);
2059 }
2060 }
2061
2062 /* Turns off the pinboard. Does not call gtk_main_quit. */
2063 static void pinboard_clear(void)
2064 {
2065 GList *next;
2066
2067 g_return_if_fail(current_pinboard != NULL);
2068
2069 tasklist_set_active(FALSE);
2070
2071 next = current_pinboard->icons;
2072 while (next)
2073 {
2074 PinIcon *pi = (PinIcon *) next->data;
2075
2076 next = next->next;
2077
2078 gtk_widget_destroy(pi->win);
2079 }
2080
2081 gtk_widget_destroy(current_pinboard->window);
2082
2083 abandon_backdrop_app(current_pinboard);
2084
2085 g_object_unref(current_pinboard->shadow_gc);
2086 current_pinboard->shadow_gc = NULL;
2087
2088 g_free(current_pinboard->name);
2089 null_g_free(&current_pinboard);
2090
2091 number_of_windows--;
2092 }
2093
2094 static gpointer parent_class;
2095
2096 static void pin_icon_destroy(Icon *icon)
2097 {
2098 PinIcon *pi = (PinIcon *) icon;
2099
2100 g_return_if_fail(pi->win != NULL);
2101
2102 gtk_widget_hide(pi->win); /* Stops flicker - stupid GtkFixed! */
2103 gtk_widget_destroy(pi->win);
2104 }
2105
2106 static void pinboard_remove_items(void)
2107 {
2108 g_return_if_fail(icon_selection != NULL);
2109
2110 while (icon_selection)
2111 icon_destroy((Icon *) icon_selection->data);
2112
2113 pinboard_save();
2114 }
2115
2116 static void pin_icon_update(Icon *icon)
2117 {
2118 pinboard_reshape_icon(icon);
2119 pinboard_save();
2120 }
2121
2122 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
2123 {
2124 return IS_PIN_ICON(other);
2125 }
2126
2127 static void pin_wink_icon(Icon *icon)
2128 {
2129 pinboard_wink_item((PinIcon *) icon, TRUE);
2130 }
2131
2132 static void pin_icon_class_init(gpointer gclass, gpointer data)
2133 {
2134 IconClass *icon = (IconClass *) gclass;
2135
2136 parent_class = g_type_class_peek_parent(gclass);
2137
2138 icon->destroy = pin_icon_destroy;
2139 icon->redraw = pinboard_reshape_icon;
2140 icon->update = pin_icon_update;
2141 icon->wink = pin_wink_icon;
2142 icon->remove_items = pinboard_remove_items;
2143 icon->same_group = pin_icon_same_group;
2144 }
2145
2146 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
2147 {
2148 }
2149
2150 static GType pin_icon_get_type(void)
2151 {
2152 static GType type = 0;
2153
2154 if (!type)
2155 {
2156 static const GTypeInfo info =
2157 {
2158 sizeof (PinIconClass),
2159 NULL, /* base_init */
2160 NULL, /* base_finalise */
2161 pin_icon_class_init,
2162 NULL, /* class_finalise */
2163 NULL, /* class_data */
2164 sizeof(PinIcon),
2165 0, /* n_preallocs */
2166 pin_icon_init
2167 };
2168
2169 type = g_type_register_static(icon_get_type(),
2170 "PinIcon", &info, 0);
2171 }
2172
2173 return type;
2174 }
2175
2176 static PinIcon *pin_icon_new(const char *pathname, const char *name)
2177 {
2178 PinIcon *pi;
2179 Icon *icon;
2180
2181 pi = g_object_new(pin_icon_get_type(), NULL);
2182 icon = (Icon *) pi;
2183
2184 icon_set_path(icon, pathname, name);
2185
2186 return pi;
2187 }
2188
2189 /* Called when the window widget is somehow destroyed */
2190 static void pin_icon_destroyed(PinIcon *pi)
2191 {
2192 g_return_if_fail(pi->win != NULL);
2193
2194 pi->win = NULL;
2195
2196 pinboard_wink_item(NULL, FALSE);
2197
2198 if (pinboard_drag_in_progress == (Icon *) pi)
2199 pinboard_drag_in_progress = NULL;
2200
2201 if (current_pinboard)
2202 current_pinboard->icons =
2203 g_list_remove(current_pinboard->icons, pi);
2204
2205 g_object_unref(pi);
2206 }
2207
2208 /* Set the tooltip */
2209 static void pin_icon_set_tip(PinIcon *pi)
2210 {
2211 XMLwrapper *ai;
2212 xmlNode *node;
2213 Icon *icon = (Icon *) pi;
2214
2215 g_return_if_fail(pi != NULL);
2216
2217 ai = appinfo_get(icon->path, icon->item);
2218
2219 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
2220 {
2221 guchar *str;
2222 str = xmlNodeListGetString(node->doc,
2223 node->xmlChildrenNode, 1);
2224 if (str)
2225 {
2226 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
2227 g_free(str);
2228 }
2229 }
2230 else
2231 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
2232
2233 if (ai)
2234 g_object_unref(ai);
2235 }
2236
2237 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
2238 {
2239 GtkWidget *option_item;
2240 GtkWidget *new_panel_item;
2241 int pos[3];
2242 GList *list;
2243
2244 pos[0] = event->x_root;
2245 pos[1] = event->y_root;
2246
2247 option_item = gtk_image_menu_item_new_with_label(_("Backdrop..."));
2248 g_signal_connect(option_item, "activate",
2249 G_CALLBACK(pinboard_set_backdrop_box), NULL);
2250 new_panel_item = gtk_image_menu_item_new_with_label(_("Add Panel"));
2251 add_stock_to_menu_item(new_panel_item, GTK_STOCK_ADD);
2252 gtk_menu_item_set_submenu(GTK_MENU_ITEM(new_panel_item),
2253 panel_new_panel_submenu());
2254 icon_prepare_menu((Icon *) pi, option_item, new_panel_item, NULL);
2255
2256 list = gtk_container_get_children(GTK_CONTAINER(icon_menu));
2257 pos[2] = g_list_length(list) - 6;
2258 g_list_free(list);
2259
2260 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
2261 position_menu,
2262 (gpointer) pos, event->button, event->time);
2263 }
2264
2265 static void create_pinboard_window(Pinboard *pinboard)
2266 {
2267 GtkWidget *win;
2268
2269 g_return_if_fail(pinboard->window == NULL);
2270
2271 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2272 gtk_widget_set_style(win, gtk_widget_get_default_style());
2273
2274 gtk_widget_set_double_buffered(win, FALSE);
2275 gtk_widget_set_app_paintable(win, TRUE);
2276 gtk_widget_set_name(win, "rox-pinboard");
2277 pinboard->window = win;
2278 pinboard->fixed = gtk_fixed_new();
2279 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
2280
2281 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
2282
2283 gtk_widget_set_size_request(win, screen_width, screen_height);
2284 gtk_widget_realize(win);
2285 gtk_window_move(GTK_WINDOW(win), 0, 0);
2286 make_panel_window(win);
2287
2288 /* TODO: Use gdk function when it supports this type */
2289 {
2290 GdkAtom desktop_type;
2291
2292 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
2293 FALSE);
2294 gdk_property_change(win->window,
2295 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
2296 gdk_atom_intern("ATOM", FALSE), 32,
2297 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
2298 }
2299
2300 gtk_widget_add_events(win,
2301 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
2302 GDK_EXPOSURE_MASK |
2303 GDK_BUTTON1_MOTION_MASK |
2304 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
2305 g_signal_connect(win, "button-press-event",
2306 G_CALLBACK(button_press_event), NULL);
2307 g_signal_connect(win, "button-release-event",
2308 G_CALLBACK(button_release_event), NULL);
2309 g_signal_connect(win, "motion-notify-event",
2310 G_CALLBACK(lasso_motion), NULL);
2311 g_signal_connect(pinboard->fixed, "expose_event",
2312 G_CALLBACK(bg_expose), NULL);
2313
2314 /* Some window managers use scroll events on the root to switch
2315 * desktops, but don't cope with our pinboard window, so we forward
2316 * them manually in that case.
2317 */
2318 g_signal_connect(win, "scroll-event", G_CALLBACK(scroll_event), NULL);
2319
2320 /* Drag and drop handlers */
2321 drag_set_pinboard_dest(win);
2322 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
2323 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
2324
2325 pinboard->shadow_gc = gdk_gc_new(win->window);
2326 gdk_gc_set_rgb_fg_color(pinboard->shadow_gc, &pin_text_shadow_col);
2327
2328 reload_backdrop(current_pinboard, NULL, BACKDROP_NONE);
2329
2330 gtk_widget_show_all(win);
2331 gdk_window_lower(win->window);
2332 }
2333
2334 /* Load image 'path' and scale according to 'style' */
2335 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
2336 {
2337 GdkPixmap *pixmap;
2338 GdkPixbuf *pixbuf;
2339 GError *error = NULL;
2340
2341 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2342 if (error)
2343 {
2344 delayed_error(_("Error loading backdrop image:\n%s\n"
2345 "Backdrop removed."),
2346 error->message);
2347 g_error_free(error);
2348 pinboard_set_backdrop(NULL, BACKDROP_NONE);
2349 return NULL;
2350 }
2351
2352 if (style == BACKDROP_STRETCH)
2353 {
2354 GdkPixbuf *old = pixbuf;
2355
2356 pixbuf = gdk_pixbuf_scale_simple(old,
2357 screen_width, screen_height,
2358 GDK_INTERP_HYPER);
2359
2360 g_object_unref(old);
2361 }
2362 else if (style == BACKDROP_CENTRE || style == BACKDROP_SCALE || style == BACKDROP_FIT)
2363 {
2364 GdkPixbuf *old = pixbuf;
2365 int x, y, width, height;
2366 int offset_x, offset_y;
2367 float scale;
2368
2369 width = gdk_pixbuf_get_width(pixbuf);
2370 height = gdk_pixbuf_get_height(pixbuf);
2371
2372 if (style == BACKDROP_SCALE)
2373 {
2374 float scale_x, scale_y;
2375 scale_x = screen_width / ((float) width);
2376 scale_y = screen_height / ((float) height);
2377 scale = MIN(scale_x, scale_y);
2378 }
2379 else if (style == BACKDROP_FIT)
2380 {
2381 float scale_x, scale_y;
2382 scale_x = screen_width / ((float) width);
2383 scale_y = screen_height / ((float) height);
2384 scale = MAX(scale_x, scale_y);
2385 }
2386 else
2387 scale = 1;
2388
2389 pixbuf = gdk_pixbuf_new(
2390 gdk_pixbuf_get_colorspace(pixbuf), FALSE,
2391 8, screen_width, screen_height);
2392 gdk_pixbuf_fill(pixbuf, ((pin_text_bg_col.red & 0xff00) << 16) |
2393 ((pin_text_bg_col.green & 0xff00) << 8) |
2394 ((pin_text_bg_col.blue & 0xff00)));
2395
2396 x = (screen_width - width * scale) / 2;
2397 y = (screen_height - height * scale) / 2;
2398
2399 if (style == BACKDROP_CENTRE)
2400 {
2401 offset_x = x;
2402 offset_y = y;
2403 x = MAX(x, 0);
2404 y = MAX(y, 0);
2405 }
2406 else {
2407 x = MAX(x, 0);
2408 y = MAX(y, 0);
2409 offset_x = x;
2410 offset_y = y;
2411 }
2412
2413
2414 gdk_pixbuf_composite(old, pixbuf,
2415 x, y,
2416 MIN(screen_width, width * scale),
2417 MIN(screen_height, height * scale),
2418 offset_x, offset_y, scale, scale,
2419 o_pinboard_image_scaling.int_value?
2420 GDK_INTERP_BILINEAR: GDK_INTERP_HYPER,
2421 255);
2422 g_object_unref(old);
2423 }
2424
2425 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
2426 &pixmap, NULL, 0);
2427 g_object_unref(pixbuf);
2428
2429 return pixmap;
2430 }
2431
2432 static void abandon_backdrop_app(Pinboard *pinboard)
2433 {
2434 g_return_if_fail(pinboard != NULL);
2435
2436 if (pinboard->to_backdrop_app != -1)
2437 {
2438 close(pinboard->to_backdrop_app);
2439 close(pinboard->from_backdrop_app);
2440 g_source_remove(pinboard->input_tag);
2441 g_string_free(pinboard->input_buffer, TRUE);
2442 pinboard->to_backdrop_app = -1;
2443 pinboard->from_backdrop_app = -1;
2444 pinboard->input_tag = -1;
2445 pinboard->input_buffer = NULL;
2446 }
2447
2448 g_return_if_fail(pinboard->to_backdrop_app == -1);
2449 g_return_if_fail(pinboard->from_backdrop_app == -1);
2450 g_return_if_fail(pinboard->input_tag == -1);
2451 g_return_if_fail(pinboard->input_buffer == NULL);
2452 }
2453
2454 /* A single line has been read from the child.
2455 * Processes the command, and replies 'ok' (or abandons the child on error).
2456 */
2457 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
2458 {
2459 BackdropStyle style;
2460 const char *ok = "ok\n";
2461
2462 if (strncmp(command, "tile ", 5) == 0)
2463 {
2464 style = BACKDROP_TILE;
2465 command += 5;
2466 }
2467 else if (strncmp(command, "scale ", 6) == 0)
2468 {
2469 style = BACKDROP_SCALE;
2470 command += 6;
2471 }
2472 else if (strncmp(command, "fit ", 4) == 0)
2473 {
2474 style = BACKDROP_FIT;
2475 command += 4;
2476 }
2477 else if (strncmp(command, "stretch ", 8) == 0)
2478 {
2479 style = BACKDROP_STRETCH;
2480 command += 8;
2481 }
2482 else if (strncmp(command, "centre ", 7) == 0)
2483 {
2484 style = BACKDROP_CENTRE;
2485 command += 7;
2486 }
2487 else
2488 {
2489 g_warning("Invalid command '%s' from backdrop app\n",
2490 command);
2491 abandon_backdrop_app(pinboard);
2492 return;
2493 }
2494
2495 /* Load the backdrop. May abandon the program if loading fails. */
2496 reload_backdrop(pinboard, command, style);
2497
2498 if (pinboard->to_backdrop_app == -1)
2499 return;
2500
2501 while (*ok)
2502 {
2503 int sent;
2504
2505 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
2506 if (sent <= 0)
2507 {
2508 /* Remote app quit? Not an error. */
2509 abandon_backdrop_app(pinboard);
2510 break;
2511 }
2512 ok += sent;
2513 }
2514 }
2515
2516 static void backdrop_from_child(Pinboard *pinboard,
2517 int src, GdkInputCondition cond)
2518 {
2519 char buf[256];
2520 int got;
2521
2522 got = read(src, buf, sizeof(buf));
2523
2524 if (got <= 0)
2525 {
2526 if (got < 0)
2527 g_warning("backdrop_from_child: %s\n",
2528 g_strerror(errno));
2529 abandon_backdrop_app(pinboard);
2530 return;
2531 }
2532
2533 g_string_append_len(pinboard->input_buffer, buf, got);
2534
2535 while (pinboard->from_backdrop_app != -1)
2536 {
2537 int len;
2538 char *nl, *command;
2539
2540 nl = strchr(pinboard->input_buffer->str, '\n');
2541 if (!nl)
2542 return; /* Haven't got a whole line yet */
2543
2544 len = nl - pinboard->input_buffer->str;
2545 command = g_strndup(pinboard->input_buffer->str, len);
2546 g_string_erase(pinboard->input_buffer, 0, len + 1);
2547
2548 command_from_backdrop_app(pinboard, command);
2549
2550 g_free(command);
2551 }
2552 }
2553
2554 static void reload_backdrop(Pinboard *pinboard,
2555 const gchar *backdrop,
2556 BackdropStyle backdrop_style)
2557 {
2558 GtkStyle *style;
2559
2560 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
2561 {
2562 const char *argv[] = {NULL, "--backdrop", NULL};
2563 GError *error = NULL;
2564
2565 g_return_if_fail(pinboard->to_backdrop_app == -1);
2566 g_return_if_fail(pinboard->from_backdrop_app == -1);
2567 g_return_if_fail(pinboard->input_tag == -1);
2568 g_return_if_fail(pinboard->input_buffer == NULL);
2569
2570 argv[0] = make_path(backdrop, "AppRun");
2571
2572 /* Run the program. It'll send us a SOAP message and we'll
2573 * get back here with a different style and image.
2574 */
2575
2576 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
2577 G_SPAWN_DO_NOT_REAP_CHILD |
2578 G_SPAWN_SEARCH_PATH,
2579 NULL, NULL, /* Child setup fn */
2580 NULL, /* Child PID */
2581 &pinboard->to_backdrop_app,
2582 &pinboard->from_backdrop_app,
2583 NULL, /* Standard error */
2584 &error))
2585 {
2586 pinboard->input_buffer = g_string_new(NULL);
2587 pinboard->input_tag = gdk_input_add_full(
2588 pinboard->from_backdrop_app,
2589 GDK_INPUT_READ,
2590 (GdkInputFunction) backdrop_from_child,
2591 pinboard, NULL);
2592 }
2593 else
2594 {
2595 delayed_error("%s", error ? error->message : "(null)");
2596 g_error_free(error);
2597 }
2598 return;
2599 }
2600
2601 /* Note: Copying a style does not ref the pixmaps! */
2602
2603 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
2604 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
2605
2606 if (backdrop)
2607 style->bg_pixmap[GTK_STATE_NORMAL] =
2608 load_backdrop(backdrop, backdrop_style);
2609
2610 gdk_color_parse(o_pinboard_bg_colour.value,
2611 &style->bg[GTK_STATE_NORMAL]);
2612
2613 gtk_widget_set_style(pinboard->window, style);
2614
2615 g_object_unref(style);
2616
2617 gtk_widget_queue_draw(pinboard->window);
2618
2619 /* Also update root window property (for transparent xterms, etc) */
2620 if (style->bg_pixmap[GTK_STATE_NORMAL])
2621 {
2622 XID id = GDK_DRAWABLE_XID(style->bg_pixmap[GTK_STATE_NORMAL]);
2623 gdk_property_change(gdk_get_default_root_window(),
2624 gdk_atom_intern("_XROOTPMAP_ID", FALSE),
2625 gdk_atom_intern("PIXMAP", FALSE),
2626 32, GDK_PROP_MODE_REPLACE,
2627 (guchar *) &id, 1);
2628 }
2629 else
2630 {
2631 gdk_property_delete(gdk_get_default_root_window(),
2632 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
2633 }
2634 }
2635
2636 #define SEARCH_STEP 32
2637
2638 /* Search the area (omin, imin) to (omax, imax) for a free region the size of
2639 * 'rect' that doesn't overlap 'used'. Which of inner and outer is the
2640 * vertical axis depends on the configuration.
2641 *
2642 * id and od give the direction of the search (step size).
2643 *
2644 * Returns the start of the found region in inner/outer, or -1 if there is no
2645 * free space.
2646 */
2647 static void search_free(GdkRectangle *rect, GdkRegion *used,
2648 int *outer, int od, int omin, int omax,
2649 int *inner, int id, int imin, int imax)
2650 {
2651 *outer = od > 0 ? omin : omax;
2652 while (*outer >= omin && *outer <= omax)
2653 {
2654 *inner = id > 0 ? imin : imax;
2655 while (*inner >= imin && *inner <= imax)
2656 {
2657 if (gdk_region_rect_in(used, rect) ==
2658 GDK_OVERLAP_RECTANGLE_OUT)
2659 return;
2660 *inner += id;
2661 }
2662
2663 *outer += od;
2664 }
2665
2666 rect->x = -1;
2667 rect->y = -1;
2668 }
2669
2670 /* Search the width x height area from (x0, y0) for a free region of size
2671 * 'rect'. direction indicates whether to search rows or columns. dx, dy gives
2672 * the direction of the search.
2673 */
2674 static void search_free_area(GdkRectangle *rect, GdkRegion *used,
2675 int direction, int dx, int dy, int x0, int y0, int width, int height)
2676 {
2677 if (direction == DIR_VERT)
2678 {
2679 search_free(rect, used,
2680 &rect->x, dx, x0, width,
2681 &rect->y, dy, y0, height);
2682 }
2683 else
2684 {
2685 search_free(rect, used,
2686 &rect->y, dy, x0, height,
2687 &rect->x, dx, y0, width);
2688 }
2689 }
2690
2691 static gboolean search_free_xinerama(GdkRectangle *rect, GdkRegion *used,
2692 int direction, int dx, int dy, int rwidth, int rheight)
2693 {
2694 GdkRectangle *geom = &monitor_geom[get_monitor_under_pointer()];
2695
2696 search_free_area(rect, used, direction, dx, dy,
2697 geom->x, geom->y, geom->width - rwidth, geom->height - rheight);
2698 return rect->x != -1;
2699 }
2700
2701 /* Finds a free area on the pinboard large enough for the width and height
2702 * of the given rectangle, by filling in the x and y fields of 'rect'.
2703 * If 'old' is true, 'rect' has a previous position and we first check
2704 * if it is viable.
2705 * The search order respects user preferences.
2706 * If no area is free, returns any old area.
2707 */
2708 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
2709 gboolean old, int start, int direction)
2710 {
2711 GdkRegion *used;
2712 GList *next;
2713 GdkRectangle used_rect;
2714 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2715
2716 used = gdk_region_new();
2717
2718 panel_mark_used(used);
2719
2720 /* Subtract the no-go areas... */
2721
2722 if (o_top_margin.int_value > 0)
2723 {
2724 used_rect.x = 0;
2725 used_rect.y = 0;
2726 used_rect.width = gdk_screen_width();
2727 used_rect.height = o_top_margin.int_value;
2728 gdk_region_union_with_rect(used, &used_rect);
2729 }
2730
2731 if (o_bottom_margin.int_value > 0)
2732 {
2733 used_rect.x = 0;
2734 used_rect.y = gdk_screen_height() - o_bottom_margin.int_value;
2735 used_rect.width = gdk_screen_width();
2736 used_rect.height = o_bottom_margin.int_value;
2737 gdk_region_union_with_rect(used, &used_rect);
2738 }
2739
2740 if (o_left_margin.int_value > 0)
2741 {
2742 used_rect.x = 0;
2743 used_rect.y = 0;
2744 used_rect.width = o_left_margin.int_value;
2745 used_rect.height = gdk_screen_height();
2746 gdk_region_union_with_rect(used, &used_rect);
2747 }
2748
2749 if (o_right_margin.int_value > 0)
2750 {
2751 used_rect.x = gdk_screen_width() - o_right_margin.int_value;
2752 used_rect.y = 0;
2753 used_rect.width = o_right_margin.int_value;
2754 used_rect.height = gdk_screen_height();
2755 gdk_region_union_with_rect(used, &used_rect);
2756 }
2757
2758 /* Subtract the used areas... */
2759
2760 next = GTK_FIXED(pinboard->fixed)->children;
2761 for (; next; next = next->next)
2762 {
2763 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2764
2765 if (!GTK_WIDGET_VISIBLE(fix->widget))
2766 continue;
2767
2768 used_rect.x = fix->x;
2769 used_rect.y = fix->y;
2770 used_rect.width = fix->widget->requisition.width;
2771 used_rect.height = fix->widget->requisition.height;
2772
2773 gdk_region_union_with_rect(used, &used_rect);
2774 }
2775
2776 /* Check the previous area */
2777 if(old) {
2778 if(gdk_region_rect_in(used, rect)==GDK_OVERLAP_RECTANGLE_OUT) {
2779 gdk_region_destroy(used);
2780 return;
2781 }
2782 }
2783
2784 /* Find the first free area (yes, this isn't exactly pretty, but
2785 * it works). If you know a better (fast!) algorithm, let me know!
2786 */
2787
2788 if (start == CORNER_TOP_RIGHT ||
2789 start == CORNER_BOTTOM_RIGHT)
2790 dx = -SEARCH_STEP;
2791
2792 if (start == CORNER_BOTTOM_LEFT ||
2793 start == CORNER_BOTTOM_RIGHT)
2794 dy = -SEARCH_STEP;
2795
2796 /* If pinboard covers more than one monitor, try to find free space on
2797 * monitor under pointer first, then whole screen if that fails */
2798 if (n_monitors == 1 || !search_free_xinerama(rect, used,
2799 direction, dx, dy, rect->width, rect->height))
2800 {
2801 search_free_area(rect, used, direction, dx, dy,
2802 0, 0, screen_width - rect->width, screen_height - rect->height);
2803 }
2804
2805 gdk_region_destroy(used);
2806
2807 if (rect->x == -1)
2808 {
2809 rect->x = 0;
2810 rect->y = 0;
2811 }
2812 }
2813
2814 /* Icon's size, shape or appearance has changed - update the display */
2815 static void pinboard_reshape_icon(Icon *icon)
2816 {
2817 PinIcon *pi = (PinIcon *) icon;
2818 int x = pi->x, y = pi->y;
2819
2820 set_size_and_style(pi);
2821 offset_from_centre(pi, &x, &y);
2822
2823 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
2824 {
2825 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
2826 pi->win, x, y);
2827 }
2828
2829 /* Newer versions of GTK seem to need this, or the icon doesn't
2830 * get redrawn.
2831 */
2832 gtk_widget_queue_draw(pi->win);
2833 }
2834
2835 /* Sets the pinboard_font global from the option. Doesn't do anything else. */
2836 static void update_pinboard_font(void)
2837 {
2838 if (pinboard_font)
2839 pango_font_description_free(pinboard_font);
2840 pinboard_font = o_label_font.value[0] != '\0'
2841 ? pango_font_description_from_string(o_label_font.value)
2842 : NULL;
2843 }
2844
2845 static void radios_changed(Radios *radios, gpointer data)
2846 {
2847 GObject *dialog = G_OBJECT(data);
2848 DropBox *drop_box;
2849 const guchar *path;
2850
2851 g_return_if_fail(dialog != NULL);
2852
2853 drop_box = g_object_get_data(G_OBJECT(dialog), "rox-dropbox");
2854
2855 g_return_if_fail(radios != NULL);
2856 g_return_if_fail(drop_box != NULL);
2857 g_return_if_fail(current_pinboard != NULL);
2858
2859 if (current_pinboard->backdrop_style != BACKDROP_PROGRAM)
2860 {
2861 path = drop_box_get_path(drop_box);
2862 if (path)
2863 pinboard_set_backdrop(path, radios_get_value(radios));
2864 }
2865 }
2866
2867 static void update_radios(GtkWidget *dialog)
2868 {
2869 Radios *radios;
2870 GtkWidget *hbox;
2871
2872 g_return_if_fail(dialog != NULL);
2873
2874 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
2875 hbox = g_object_get_data(G_OBJECT(dialog), "rox-radios-hbox");
2876
2877 g_return_if_fail(current_pinboard != NULL);
2878 g_return_if_fail(radios != NULL);
2879 g_return_if_fail(hbox != NULL);
2880
2881 switch (current_pinboard->backdrop_style)
2882 {
2883 case BACKDROP_TILE:
2884 case BACKDROP_STRETCH:
2885 case BACKDROP_SCALE:
2886 case BACKDROP_CENTRE:
2887 case BACKDROP_FIT:
2888 radios_set_value(radios,
2889 current_pinboard->backdrop_style);
2890 gtk_widget_set_sensitive(hbox, TRUE);
2891 break;
2892 default:
2893 gtk_widget_set_sensitive(hbox, FALSE);
2894 radios_set_value(radios, BACKDROP_TILE);
2895 break;
2896 }
2897 }