r4332: When iconfiying a window, try to find space on the pinboard of the current
[rox-filer/translations.git] / ROX-Filer / src / pinboard.c
bloba0fb37d09328abf172d5c08791e60572d7024124
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pinboard.c - icons on the desktop background */
24 #include "config.h"
26 #include <ctype.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkx.h>
32 #include <stdlib.h>
33 #include <math.h>
34 #include <libxml/parser.h>
35 #include <signal.h>
37 #include "global.h"
39 #include "pinboard.h"
40 #include "main.h"
41 #include "dnd.h"
42 #include "pixmaps.h"
43 #include "type.h"
44 #include "choices.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "options.h"
48 #include "diritem.h"
49 #include "bind.h"
50 #include "icon.h"
51 #include "run.h"
52 #include "appinfo.h"
53 #include "menu.h"
54 #include "xml.h"
55 #include "tasklist.h"
56 #include "panel.h" /* For panel_mark_used() */
57 #include "wrapped.h"
58 #include "dropbox.h"
60 static gboolean tmp_icon_selected = FALSE; /* When dragging */
62 static GtkWidget *set_backdrop_dialog = NULL;
64 struct _Pinboard {
65 guchar *name; /* Leaf name */
66 GList *icons;
67 GtkStyle *style;
69 gchar *backdrop; /* Pathname */
70 BackdropStyle backdrop_style;
71 gint to_backdrop_app; /* pipe FD, or -1 */
72 gint from_backdrop_app; /* pipe FD, or -1 */
73 gint input_tag;
74 GString *input_buffer;
76 GtkWidget *window; /* Screen-sized window */
77 GtkWidget *fixed;
78 GdkGC *shadow_gc;
81 #define IS_PIN_ICON(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), pin_icon_get_type())
83 typedef struct _PinIconClass PinIconClass;
84 typedef struct _PinIcon PinIcon;
86 struct _PinIconClass {
87 IconClass parent;
90 struct _PinIcon {
91 Icon icon;
93 int x, y;
94 GtkWidget *win;
95 GtkWidget *widget; /* The drawing area for the icon */
96 GtkWidget *label;
99 /* The number of pixels between the bottom of the image and the top
100 * of the text.
102 #define GAP 4
104 /* The size of the border around the icon which is used when winking */
105 #define WINK_FRAME 2
107 /* Grid sizes */
108 #define GRID_STEP_FINE 2
109 #define GRID_STEP_MED 16
110 #define GRID_STEP_COARSE 32
112 /* Used in options */
113 #define CORNER_TOP_LEFT 0
114 #define CORNER_TOP_RIGHT 1
115 #define CORNER_BOTTOM_LEFT 2
116 #define CORNER_BOTTOM_RIGHT 3
118 #define DIR_HORZ 0
119 #define DIR_VERT 1
121 static PinIcon *current_wink_icon = NULL;
122 static gint wink_timeout;
124 /* Used for the text colours (only) in the icons */
125 GdkColor pin_text_fg_col, pin_text_bg_col;
126 PangoFontDescription *pinboard_font = NULL; /* NULL => Gtk default */
128 static GdkColor pin_text_shadow_col;
130 Pinboard *current_pinboard = NULL;
131 static gint loading_pinboard = 0; /* Non-zero => loading */
133 /* The Icon that was used to start the current drag, if any */
134 Icon *pinboard_drag_in_progress = NULL;
136 /* For selecting groups of icons */
137 static gboolean lasso_in_progress = FALSE;
138 static int lasso_rect_x1, lasso_rect_x2;
139 static int lasso_rect_y1, lasso_rect_y2;
141 /* Tracking icon positions */
142 static GHashTable *placed_icons=NULL;
144 Option o_pinboard_tasklist_per_workspace;
145 static Option o_pinboard_clamp_icons, o_pinboard_grid_step;
146 static Option o_pinboard_fg_colour, o_pinboard_bg_colour;
147 static Option o_pinboard_tasklist, o_forward_buttons_13;
148 static Option o_iconify_start, o_iconify_dir;
149 static Option o_label_font, o_pinboard_shadow_colour;
150 static Option o_pinboard_shadow_labels;
151 static Option o_blackbox_hack;
153 static Option o_top_margin, o_bottom_margin, o_left_margin, o_right_margin;
154 static Option o_pinboard_image_scaling;
156 /* Static prototypes */
157 static GType pin_icon_get_type(void);
158 static void set_size_and_style(PinIcon *pi);
159 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
160 static gint end_wink(gpointer data);
161 static gboolean button_release_event(GtkWidget *widget,
162 GdkEventButton *event,
163 PinIcon *pi);
164 static gboolean enter_notify(GtkWidget *widget,
165 GdkEventCrossing *event,
166 PinIcon *pi);
167 static gint leave_notify(GtkWidget *widget,
168 GdkEventCrossing *event,
169 PinIcon *pi);
170 static gboolean button_press_event(GtkWidget *widget,
171 GdkEventButton *event,
172 PinIcon *pi);
173 static gboolean scroll_event(GtkWidget *widget,
174 GdkEventScroll *event);
175 static gint icon_motion_notify(GtkWidget *widget,
176 GdkEventMotion *event,
177 PinIcon *pi);
178 static const char *pin_from_file(gchar *line);
179 static void snap_to_grid(int *x, int *y);
180 static void offset_from_centre(PinIcon *pi, int *x, int *y);
181 static gboolean drag_motion(GtkWidget *widget,
182 GdkDragContext *context,
183 gint x,
184 gint y,
185 guint time,
186 PinIcon *pi);
187 static void drag_set_pinicon_dest(PinIcon *pi);
188 static void drag_leave(GtkWidget *widget,
189 GdkDragContext *context,
190 guint32 time,
191 PinIcon *pi);
192 static gboolean bg_drag_motion(GtkWidget *widget,
193 GdkDragContext *context,
194 gint x,
195 gint y,
196 guint time,
197 gpointer data);
198 static gboolean bg_drag_leave(GtkWidget *widget,
199 GdkDragContext *context,
200 guint32 time,
201 gpointer data);
202 static gboolean bg_expose(GtkWidget *window,
203 GdkEventExpose *event, gpointer data);
204 static void drag_end(GtkWidget *widget,
205 GdkDragContext *context,
206 PinIcon *pi);
207 static void reshape_all(void);
208 static void pinboard_check_options(void);
209 static void pinboard_load_from_xml(xmlDocPtr doc);
210 static void pinboard_clear(void);
211 static void pinboard_save(void);
212 static PinIcon *pin_icon_new(const char *pathname, const char *name);
213 static void pin_icon_destroyed(PinIcon *pi);
214 static void pin_icon_set_tip(PinIcon *pi);
215 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi);
216 static void create_pinboard_window(Pinboard *pinboard);
217 static void reload_backdrop(Pinboard *pinboard,
218 const gchar *backdrop,
219 BackdropStyle backdrop_style);
220 static void pinboard_reshape_icon(Icon *icon);
221 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi);
222 static void abandon_backdrop_app(Pinboard *pinboard);
223 static void drag_backdrop_dropped(GtkWidget *drop_box,
224 const guchar *path,
225 GtkWidget *dialog);
226 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data);
227 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
228 gboolean old);
229 static void update_pinboard_font(void);
230 static void draw_lasso(void);
231 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d);
232 static void clear_backdrop(GtkWidget *drop_box, gpointer data);
233 static void radios_changed(Radios *radios, gpointer data);
234 static void update_radios(GtkWidget *dialog);
235 static void pinboard_set_backdrop_box(void);
237 /****************************************************************
238 * EXTERNAL INTERFACE *
239 ****************************************************************/
241 void pinboard_init(void)
243 option_add_string(&o_pinboard_fg_colour, "pinboard_fg_colour", "#fff");
244 option_add_string(&o_pinboard_bg_colour, "pinboard_bg_colour", "#888");
245 option_add_string(&o_pinboard_shadow_colour, "pinboard_shadow_colour",
246 "#000");
247 option_add_string(&o_label_font, "label_font", "");
248 option_add_int(&o_pinboard_shadow_labels, "pinboard_shadow_labels", 1);
250 option_add_int(&o_pinboard_clamp_icons, "pinboard_clamp_icons", 1);
251 option_add_int(&o_pinboard_grid_step, "pinboard_grid_step",
252 GRID_STEP_COARSE);
253 option_add_int(&o_pinboard_tasklist, "pinboard_tasklist", TRUE);
254 option_add_int(&o_pinboard_tasklist_per_workspace, "pinboard_tasklist_per_workspace", FALSE);
255 option_add_int(&o_forward_buttons_13, "pinboard_forward_buttons_13",
256 FALSE);
258 option_add_int(&o_iconify_start, "iconify_start", CORNER_TOP_RIGHT);
259 option_add_int(&o_iconify_dir, "iconify_dir", DIR_VERT);
261 option_add_int(&o_blackbox_hack, "blackbox_hack", FALSE);
263 option_add_int(&o_top_margin, "pinboard_top_margin", 0);
264 option_add_int(&o_bottom_margin, "pinboard_bottom_margin", 0);
265 option_add_int(&o_left_margin, "pinboard_left_margin", 0);
266 option_add_int(&o_right_margin, "pinboard_right_margin", 0);
268 option_add_int(&o_pinboard_image_scaling, "pinboard_image_scaling", 0);
270 option_add_notify(pinboard_check_options);
272 gdk_color_parse(o_pinboard_fg_colour.value, &pin_text_fg_col);
273 gdk_color_parse(o_pinboard_bg_colour.value, &pin_text_bg_col);
274 gdk_color_parse(o_pinboard_shadow_colour.value, &pin_text_shadow_col);
275 update_pinboard_font();
277 placed_icons=g_hash_table_new(g_str_hash, g_str_equal);
280 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
281 * and make it the current pinboard.
282 * Any existing pinned items are removed. You must call this
283 * at least once before using the pinboard. NULL disables the
284 * pinboard.
286 void pinboard_activate(const gchar *name)
288 Pinboard *old_board = current_pinboard;
289 guchar *path, *slash;
291 /* Treat an empty name the same as NULL */
292 if (name && !*name)
293 name = NULL;
295 if (old_board)
296 pinboard_clear();
298 if (!name)
300 if (number_of_windows < 1 && gtk_main_level() > 0)
301 gtk_main_quit();
303 gdk_property_delete(gdk_get_default_root_window(),
304 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
305 return;
308 number_of_windows++;
310 slash = strchr(name, '/');
311 if (slash)
313 if (access(name, F_OK))
314 path = NULL; /* File does not (yet) exist */
315 else
316 path = g_strdup(name);
318 else
320 guchar *leaf;
322 leaf = g_strconcat("pb_", name, NULL);
323 path = choices_find_xdg_path_load(leaf, PROJECT, SITE);
324 g_free(leaf);
327 current_pinboard = g_new(Pinboard, 1);
328 current_pinboard->name = g_strdup(name);
329 current_pinboard->icons = NULL;
330 current_pinboard->window = NULL;
331 current_pinboard->backdrop = NULL;
332 current_pinboard->backdrop_style = BACKDROP_NONE;
333 current_pinboard->to_backdrop_app = -1;
334 current_pinboard->from_backdrop_app = -1;
335 current_pinboard->input_tag = -1;
336 current_pinboard->input_buffer = NULL;
338 create_pinboard_window(current_pinboard);
340 loading_pinboard++;
341 if (path)
343 xmlDocPtr doc;
344 doc = xmlParseFile(path);
345 if (doc)
347 pinboard_load_from_xml(doc);
348 xmlFreeDoc(doc);
349 reload_backdrop(current_pinboard,
350 current_pinboard->backdrop,
351 current_pinboard->backdrop_style);
353 else
355 parse_file(path, pin_from_file);
356 info_message(_("Your old pinboard file has been "
357 "converted to the new XML format."));
358 pinboard_save();
360 g_free(path);
362 else
363 pinboard_pin(home_dir, "Home",
364 4 + ICON_WIDTH / 2,
365 4 + ICON_HEIGHT / 2,
366 NULL);
367 loading_pinboard--;
369 if (o_pinboard_tasklist.int_value)
370 tasklist_set_active(TRUE);
373 /* Return the window of the current pinboard, or NULL.
374 * Used to make sure lowering the panels doesn't lose them...
376 GdkWindow *pinboard_get_window(void)
378 if (current_pinboard)
379 return current_pinboard->window->window;
380 return NULL;
383 const char *pinboard_get_name(void)
385 g_return_val_if_fail(current_pinboard != NULL, NULL);
387 return current_pinboard->name;
390 /* Add widget to the pinboard. Caller is responsible for coping with pinboard
391 * being cleared.
393 void pinboard_add_widget(GtkWidget *widget, const gchar *name)
395 GtkRequisition req;
396 GdkRectangle *rect=NULL;
397 gboolean found=FALSE;
399 g_return_if_fail(current_pinboard != NULL);
401 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), widget, 0, 0);
403 gtk_widget_size_request(widget, &req);
405 if(name) {
406 rect=g_hash_table_lookup(placed_icons, name);
407 if(rect)
408 found=TRUE;
410 /*printf("%s at %p %d\n", name? name: "(nil)", rect, found);*/
412 if(rect) {
413 if(rect->width<req.width || rect->height<req.height) {
414 found=FALSE;
416 } else {
417 rect=g_new(GdkRectangle, 1);
418 rect->width = req.width;
419 rect->height = req.height;
421 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
422 rect->y, found);*/
423 find_free_rect(current_pinboard, rect, found);
424 /*printf("%s at %d,%d %d\n", name? name: "(nil)", rect->x,
425 rect->y, found);*/
427 gtk_fixed_move(GTK_FIXED(current_pinboard->fixed),
428 widget, rect->x, rect->y);
430 /* Store the new position (key and value are never freed) */
431 if(name)
432 g_hash_table_insert(placed_icons, g_strdup(name),
433 rect);
436 void pinboard_moved_widget(GtkWidget *widget, const gchar *name,
437 int x, int y)
439 GdkRectangle *rect;
441 if(!name)
442 return;
443 rect=g_hash_table_lookup(placed_icons, name);
444 if(!rect)
445 return;
447 rect->x=x;
448 rect->y=y;
451 /* Add a new icon to the background.
452 * 'path' should be an absolute pathname.
453 * 'x' and 'y' are the coordinates of the point in the middle of the text.
454 * 'name' is the name to use. If NULL then the leafname of path is used.
456 void pinboard_pin_with_args(const gchar *path, const gchar *name,
457 int x, int y, const gchar *shortcut,
458 const gchar *args)
460 GtkWidget *align, *vbox;
461 GdkWindow *events;
462 PinIcon *pi;
463 Icon *icon;
465 g_return_if_fail(path != NULL);
466 g_return_if_fail(current_pinboard != NULL);
468 pi = pin_icon_new(path, name);
469 icon = (Icon *) pi;
470 pi->x = x;
471 pi->y = y;
473 /* This is a bit complicated...
475 * An icon needs to be a NO_WINDOW widget so that the image can
476 * blend with the background (A ParentRelative window also works, but
477 * is slow, causes the xfree86's memory consumption to grow without
478 * bound, and doesn't even get freed when the filer quits!).
480 * However, the icon also needs to have a window, so we get events
481 * delivered correctly. The solution is to float an InputOnly window
482 * over the icon. Since GtkButton works the same way, we just use
483 * that :-)
486 /* Button takes the initial ref of Icon */
487 pi->win = gtk_button_new();
488 gtk_container_set_border_width(GTK_CONTAINER(pi->win), WINK_FRAME);
489 g_signal_connect(pi->win, "expose-event", G_CALLBACK(draw_wink), pi);
490 gtk_button_set_relief(GTK_BUTTON(pi->win), GTK_RELIEF_NONE);
492 vbox = gtk_vbox_new(FALSE, 0);
493 gtk_container_add(GTK_CONTAINER(pi->win), vbox);
495 align = gtk_alignment_new(0.5, 0.5, 0, 0);
496 pi->widget = gtk_hbox_new(FALSE, 0); /* Placeholder */
497 gtk_container_add(GTK_CONTAINER(align), pi->widget);
499 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
500 drag_set_pinicon_dest(pi);
501 g_signal_connect(pi->win, "drag_data_get",
502 G_CALLBACK(drag_data_get), NULL);
504 pi->label = wrapped_label_new(icon->item->leafname, 180);
505 gtk_box_pack_start(GTK_BOX(vbox), pi->label, TRUE, TRUE, 0);
507 gtk_fixed_put(GTK_FIXED(current_pinboard->fixed), pi->win, 0, 0);
509 snap_to_grid(&x, &y);
510 pi->x = x;
511 pi->y = y;
512 gtk_widget_show_all(pi->win);
513 pinboard_reshape_icon((Icon *) pi);
515 gtk_widget_realize(pi->win);
516 events = GTK_BUTTON(pi->win)->event_window;
517 gdk_window_set_events(events,
518 GDK_EXPOSURE_MASK |
519 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
520 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
521 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
522 GDK_BUTTON3_MOTION_MASK);
523 g_signal_connect(pi->win, "enter-notify-event",
524 G_CALLBACK(enter_notify), pi);
525 g_signal_connect(pi->win, "leave-notify-event",
526 G_CALLBACK(leave_notify), pi);
527 g_signal_connect(pi->win, "button-press-event",
528 G_CALLBACK(button_press_event), pi);
529 g_signal_connect(pi->win, "button-release-event",
530 G_CALLBACK(button_release_event), pi);
531 g_signal_connect(pi->win, "motion-notify-event",
532 G_CALLBACK(icon_motion_notify), pi);
533 g_signal_connect(pi->win, "expose-event",
534 G_CALLBACK(draw_icon), pi);
535 g_signal_connect_swapped(pi->win, "style-set",
536 G_CALLBACK(pinboard_reshape_icon), pi);
537 g_signal_connect_swapped(pi->win, "destroy",
538 G_CALLBACK(pin_icon_destroyed), pi);
540 current_pinboard->icons = g_list_prepend(current_pinboard->icons, pi);
541 pin_icon_set_tip(pi);
543 icon_set_shortcut(icon, shortcut);
544 icon_set_arguments(icon, args);
546 if (!loading_pinboard)
547 pinboard_save();
550 void pinboard_pin(const gchar *path, const gchar *name, int x, int y,
551 const gchar *shortcut)
553 pinboard_pin_with_args(path, name, x, y, shortcut, NULL);
557 * Remove an icon from the background. The first icon with a matching
558 * path is removed. If name is not NULL then that also must match
560 gboolean pinboard_remove(const gchar *path, const gchar *name)
562 GList *item;
563 PinIcon *pi;
564 Icon *icon;
566 g_return_val_if_fail(current_pinboard != NULL, FALSE);
568 for(item=current_pinboard->icons; item; item=g_list_next(item)) {
569 pi=(PinIcon *) item->data;
570 icon=(Icon *) pi;
572 if(strcmp(icon->path, path)!=0)
573 continue;
575 if(name && strcmp(name, icon->item->leafname)!=0)
576 continue;
578 icon_destroy(icon);
580 pinboard_save();
582 return TRUE;
585 return FALSE;
588 /* Put a border around the icon, briefly.
589 * If icon is NULL then cancel any existing wink.
590 * The icon will automatically unhighlight unless timeout is FALSE,
591 * in which case you must call this function again (with NULL or another
592 * icon) to remove the highlight.
594 static void pinboard_wink_item(PinIcon *pi, gboolean timeout)
596 PinIcon *old = current_wink_icon;
598 if (old == pi)
599 return;
601 current_wink_icon = pi;
603 if (old)
605 gtk_widget_queue_draw(old->win);
606 gdk_window_process_updates(old->widget->window, TRUE);
608 if (wink_timeout != -1)
609 g_source_remove(wink_timeout);
612 if (pi)
614 gtk_widget_queue_draw(pi->win);
615 gdk_window_process_updates(pi->widget->window, TRUE);
617 if (timeout)
618 wink_timeout = g_timeout_add(300, end_wink, NULL);
619 else
620 wink_timeout = -1;
624 /* 'app' is saved as the new application to set the backdrop. It will then be
625 * run, and should communicate with the filer as described in the manual.
627 void pinboard_set_backdrop_app(const gchar *app)
629 XMLwrapper *ai;
630 DirItem *item;
631 gboolean can_set;
633 item = diritem_new("");
634 diritem_restat(app, item, NULL);
635 if (!(item->flags & ITEM_FLAG_APPDIR))
637 delayed_error(_("The backdrop handler must be an application "
638 "directory. Drag an application directory "
639 "into the Set Backdrop dialog box, or (for "
640 "programmers) pass it to the SOAP "
641 "SetBackdropApp method."));
642 diritem_free(item);
643 return;
646 ai = appinfo_get(app, item);
647 diritem_free(item);
649 can_set = ai && xml_get_section(ai, ROX_NS, "CanSetBackdrop") != NULL;
650 if (ai)
651 g_object_unref(ai);
653 if (can_set)
654 pinboard_set_backdrop(app, BACKDROP_PROGRAM);
655 else
656 delayed_error(_("You can only set the backdrop to an image "
657 "or to a program which knows how to "
658 "manage ROX-Filer's backdrop.\n\n"
659 "Programmers: the application's AppInfo.xml "
660 "must contain the CanSetBackdrop element, as "
661 "described in ROX-Filer's manual."));
664 /* Open a dialog box allowing the user to set the backdrop */
665 static void pinboard_set_backdrop_box(void)
667 GtkWidget *dialog, *frame, *label, *hbox;
668 GtkBox *vbox;
669 Radios *radios;
671 g_return_if_fail(current_pinboard != NULL);
673 if (set_backdrop_dialog)
674 gtk_widget_destroy(set_backdrop_dialog);
676 dialog = gtk_dialog_new_with_buttons(_("Set backdrop"), NULL,
677 GTK_DIALOG_NO_SEPARATOR,
678 GTK_STOCK_CLOSE, GTK_RESPONSE_OK,
679 NULL);
680 set_backdrop_dialog = dialog;
681 g_signal_connect(dialog, "destroy",
682 G_CALLBACK(gtk_widget_destroyed), &set_backdrop_dialog);
683 vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
685 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
687 label = gtk_label_new(_("Choose a style and drag an image in:"));
688 gtk_misc_set_padding(GTK_MISC(label), 4, 0);
689 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
690 gtk_box_pack_start(vbox, label, TRUE, TRUE, 4);
692 /* The Centred, Scaled, Tiled radios... */
693 hbox = gtk_hbox_new(TRUE, 2);
694 gtk_box_pack_start(vbox, hbox, TRUE, TRUE, 4);
696 radios = radios_new(radios_changed, dialog);
697 g_object_set_data(G_OBJECT(dialog), "rox-radios", radios);
698 g_object_set_data(G_OBJECT(dialog), "rox-radios-hbox", hbox);
700 radios_add(radios, _("Centre the image without scaling it"),
701 BACKDROP_CENTRE, _("Centre"));
702 radios_add(radios, _("Scale the image to fit the backdrop area, "
703 "without distorting it"),
704 BACKDROP_SCALE, _("Scale"));
705 radios_add(radios, _("Stretch the image to fill the backdrop area"),
706 BACKDROP_STRETCH, _("Stretch"));
707 radios_add(radios, _("Tile the image over the backdrop area"),
708 BACKDROP_TILE, _("Tile"));
710 update_radios(dialog);
712 /* The drop area... */
713 frame = drop_box_new(_("Drop an image here"));
714 g_object_set_data(G_OBJECT(dialog), "rox-dropbox", frame);
716 radios_pack(radios, GTK_BOX(hbox));
717 gtk_box_pack_start(vbox, frame, TRUE, TRUE, 4);
719 drop_box_set_path(DROP_BOX(frame), current_pinboard->backdrop);
721 g_signal_connect(frame, "path_dropped",
722 G_CALLBACK(drag_backdrop_dropped), dialog);
723 g_signal_connect(frame, "clear",
724 G_CALLBACK(clear_backdrop), dialog);
726 g_signal_connect(dialog, "response",
727 G_CALLBACK(backdrop_response), NULL);
728 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
730 gtk_widget_show_all(dialog);
733 /* Also used by tasklist.c */
734 void draw_label_shadow(WrappedLabel *wl, GdkRegion *region)
736 GtkWidget *widget;
737 gint x, y;
739 if (!o_pinboard_shadow_labels.int_value)
740 return;
742 gdk_gc_set_clip_region(current_pinboard->shadow_gc, region);
744 widget = GTK_WIDGET(wl);
746 y = widget->allocation.y - wl->y_off;
747 x = widget->allocation.x - wl->x_off -
748 ((wl->text_width - widget->allocation.width) >> 1);
750 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
751 x + 1, y + 1, wl->layout);
753 if (o_pinboard_shadow_labels.int_value > 1)
754 gdk_draw_layout(widget->window, current_pinboard->shadow_gc,
755 x + 2, y + 2, wl->layout);
757 gdk_gc_set_clip_region(current_pinboard->shadow_gc, NULL);
760 /* Set and save (path, style) as the new backdrop.
761 * If style is BACKDROP_PROGRAM, the program is run to get the backdrop.
762 * Otherwise, the image is displayed now.
764 void pinboard_set_backdrop(const gchar *path, BackdropStyle style)
766 g_return_if_fail((path == NULL && style == BACKDROP_NONE) ||
767 (path != NULL && style != BACKDROP_NONE));
769 if (!current_pinboard)
771 if (!path)
772 return;
773 pinboard_activate("Default");
774 delayed_error(_("No pinboard was in use... "
775 "the 'Default' pinboard has been selected. "
776 "Use 'rox -p=Default' to turn it on in "
777 "future."));
778 g_return_if_fail(current_pinboard != NULL);
781 /* We might have just run the old backdrop program and now
782 * we're going to set a new one! Seems a bit mean...
785 abandon_backdrop_app(current_pinboard);
787 g_free(current_pinboard->backdrop);
788 current_pinboard->backdrop = g_strdup(path);
789 current_pinboard->backdrop_style = style;
790 reload_backdrop(current_pinboard,
791 current_pinboard->backdrop,
792 current_pinboard->backdrop_style);
794 pinboard_save();
796 if (set_backdrop_dialog)
798 DropBox *box = g_object_get_data(G_OBJECT(set_backdrop_dialog),
799 "rox-dropbox");
800 g_return_if_fail(box != NULL);
801 drop_box_set_path(box, current_pinboard->backdrop);
802 update_radios(set_backdrop_dialog);
806 /* Called on xrandr screen resizes */
807 void pinboard_update_size(void)
809 int width, height;
811 if (!current_pinboard)
812 return;
814 gtk_window_get_size(GTK_WINDOW(current_pinboard->window),
815 &width, &height);
817 /* Only update the pinboard's size if the screen gets bigger,
818 * not smaller. Not sure what to do about icons that end up
819 * offscreen if the screen shrinks, but perhaps a good policy
820 * is to leave them there until the screen size is increased
821 * again rather than mess them around. */
822 width = MAX(width, screen_width);
823 height = MAX(height, screen_height);
825 gtk_widget_set_size_request(current_pinboard->window, width, height);
828 /****************************************************************
829 * INTERNAL FUNCTIONS *
830 ****************************************************************/
832 static void backdrop_response(GtkWidget *dialog, gint response, gpointer data)
834 gtk_widget_destroy(dialog);
837 static void clear_backdrop(GtkWidget *drop_box, gpointer data)
839 pinboard_set_backdrop(NULL, BACKDROP_NONE);
842 static void drag_backdrop_dropped(GtkWidget *drop_box,
843 const guchar *path,
844 GtkWidget *dialog)
846 struct stat info;
847 Radios *radios;
849 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
850 g_return_if_fail(radios != NULL);
852 if (mc_stat(path, &info))
854 delayed_error(
855 _("Can't access '%s':\n%s"), path,
856 g_strerror(errno));
857 return;
860 if (S_ISDIR(info.st_mode))
862 /* Use this program to set the backdrop */
863 pinboard_set_backdrop_app(path);
865 else if (S_ISREG(info.st_mode))
866 pinboard_set_backdrop(path, radios_get_value(radios));
867 else
868 delayed_error(_("Only files (and certain applications) can be "
869 "used to set the background image."));
872 /* Do this in the idle loop so that we don't try to put an unmanaged
873 * pinboard behind a managed panel (crashes some WMs).
875 static gboolean recreate_pinboard(gchar *name)
877 pinboard_activate(name);
878 g_free(name);
880 return FALSE;
883 static void pinboard_check_options(void)
885 GdkColor n_fg, n_bg, n_shadow;
887 gdk_color_parse(o_pinboard_fg_colour.value, &n_fg);
888 gdk_color_parse(o_pinboard_bg_colour.value, &n_bg);
889 gdk_color_parse(o_pinboard_shadow_colour.value, &n_shadow);
891 if (o_override_redirect.has_changed && current_pinboard)
893 gchar *name;
894 name = g_strdup(current_pinboard->name);
895 pinboard_activate(NULL);
896 g_idle_add((GtkFunction) recreate_pinboard, name);
899 tasklist_set_active(o_pinboard_tasklist.int_value && current_pinboard);
901 if (gdk_color_equal(&n_fg, &pin_text_fg_col) == 0 ||
902 gdk_color_equal(&n_bg, &pin_text_bg_col) == 0 ||
903 gdk_color_equal(&n_shadow, &pin_text_shadow_col) == 0 ||
904 o_pinboard_shadow_labels.has_changed ||
905 o_label_font.has_changed)
907 pin_text_fg_col = n_fg;
908 pin_text_bg_col = n_bg;
909 pin_text_shadow_col = n_shadow;
910 update_pinboard_font();
912 if (current_pinboard)
914 GtkWidget *w = current_pinboard->window;
915 GdkColormap *cm;
917 cm = gtk_widget_get_colormap(w);
919 gdk_colormap_alloc_color(cm, &n_bg, FALSE, TRUE);
920 gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &n_bg);
922 gdk_gc_set_rgb_fg_color(current_pinboard->shadow_gc,
923 &n_shadow);
925 abandon_backdrop_app(current_pinboard);
926 reload_backdrop(current_pinboard,
927 current_pinboard->backdrop,
928 current_pinboard->backdrop_style);
930 reshape_all();
933 tasklist_style_changed();
937 static gint end_wink(gpointer data)
939 pinboard_wink_item(NULL, FALSE);
940 return FALSE;
943 /* Sets the appearance from the options and updates the size request of
944 * the image.
946 static void set_size_and_style(PinIcon *pi)
948 Icon *icon = (Icon *) pi;
949 MaskedPixmap *image = di_image(icon->item);
950 int iwidth = image->width;
951 int iheight = image->height;
953 gtk_widget_modify_fg(pi->label, GTK_STATE_PRELIGHT, &pin_text_fg_col);
954 gtk_widget_modify_bg(pi->label, GTK_STATE_PRELIGHT, &pin_text_bg_col);
955 gtk_widget_modify_fg(pi->label, GTK_STATE_NORMAL, &pin_text_fg_col);
956 gtk_widget_modify_bg(pi->label, GTK_STATE_NORMAL, &pin_text_bg_col);
957 widget_modify_font(pi->label, pinboard_font);
959 wrapped_label_set_text(WRAPPED_LABEL(pi->label), icon->item->leafname);
961 gtk_widget_set_size_request(pi->widget, iwidth, iheight);
964 static gint draw_icon(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
966 static GtkWidgetClass *parent_class = NULL;
967 Icon *icon = (Icon *) pi;
968 DirItem *item = icon->item;
969 MaskedPixmap *image = di_image(item);
970 int iwidth = image->width;
971 int iheight = image->height;
972 int x, y;
973 GdkPixbuf *pixbuf;
975 if (!parent_class)
977 gpointer c = ((GTypeInstance *) widget)->g_class;
978 parent_class = (GtkWidgetClass *) g_type_class_peek_parent(c);
981 x = pi->widget->allocation.x;
982 y = pi->widget->allocation.y;
984 gdk_gc_set_clip_region(pi->widget->style->black_gc, event->region);
986 pixbuf = icon->selected
987 ? create_spotlight_pixbuf(image->pixbuf,
988 &pi->widget->style->base[GTK_STATE_SELECTED])
989 : image->pixbuf;
991 render_pixbuf(pixbuf,
992 pi->widget->window,
993 pi->widget->style->black_gc,
994 x, y, iwidth, iheight);
996 if (icon->selected)
997 g_object_unref(pixbuf);
999 if (item->flags & ITEM_FLAG_SYMLINK)
1001 render_pixbuf(im_symlink->pixbuf, pi->widget->window,
1002 pi->widget->style->black_gc,
1003 x, y, -1, -1);
1005 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
1007 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
1008 ? im_mounted
1009 : im_unmounted;
1011 render_pixbuf(mp->pixbuf, pi->widget->window,
1012 pi->widget->style->black_gc,
1013 x, y, -1, -1);
1016 gdk_gc_set_clip_region(pi->widget->style->black_gc, NULL);
1018 if (icon->selected)
1020 GtkStyle *style = pi->label->style;
1021 GdkGC *gc = style->bg_gc[GTK_STATE_SELECTED];
1023 gdk_gc_set_clip_region(gc, event->region);
1024 gdk_draw_rectangle(pi->label->window, gc, TRUE,
1025 pi->label->allocation.x,
1026 pi->label->allocation.y,
1027 pi->label->allocation.width,
1028 pi->label->allocation.height);
1029 gdk_gc_set_clip_region(gc, NULL);
1031 else if (o_pinboard_shadow_labels.int_value)
1032 draw_label_shadow((WrappedLabel *) pi->label, event->region);
1034 /* Draw children */
1035 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL],
1036 event->region);
1037 (parent_class->expose_event)(widget, event);
1038 gdk_gc_set_clip_region(pi->label->style->fg_gc[GTK_STATE_NORMAL], NULL);
1040 /* Stop the button effect */
1041 return TRUE;
1044 static gint draw_wink(GtkWidget *widget, GdkEventExpose *event, PinIcon *pi)
1046 gint x, y, width, height;
1048 if (current_wink_icon != pi)
1049 return FALSE;
1051 x = widget->allocation.x;
1052 y = widget->allocation.y;
1053 width = widget->allocation.width;
1054 height = widget->allocation.height;
1056 gdk_draw_rectangle(widget->window,
1057 pi->widget->style->white_gc,
1058 FALSE,
1059 x, y, width - 1, height - 1);
1060 gdk_draw_rectangle(widget->window,
1061 pi->widget->style->black_gc,
1062 FALSE,
1063 x + 1, y + 1, width - 3, height - 3);
1065 return FALSE;
1068 static gboolean enter_notify(GtkWidget *widget,
1069 GdkEventCrossing *event,
1070 PinIcon *pi)
1072 icon_may_update((Icon *) pi);
1073 pin_icon_set_tip(pi);
1074 return TRUE;
1077 static gint leave_notify(GtkWidget *widget,
1078 GdkEventCrossing *event,
1079 PinIcon *pi)
1081 return TRUE;
1084 static void select_lasso(void)
1086 GList *next;
1087 int minx, miny, maxx, maxy;
1089 g_return_if_fail(lasso_in_progress == TRUE);
1091 minx = MIN(lasso_rect_x1, lasso_rect_x2);
1092 miny = MIN(lasso_rect_y1, lasso_rect_y2);
1093 maxx = MAX(lasso_rect_x1, lasso_rect_x2);
1094 maxy = MAX(lasso_rect_y1, lasso_rect_y2);
1096 for (next = current_pinboard->icons; next; next = next->next)
1098 PinIcon *pi = (PinIcon *) next->data;
1099 GtkAllocation *alloc = &pi->win->allocation;
1100 int cx = alloc->x + alloc->width / 2;
1101 int cy = alloc->y + alloc->height / 2;
1103 if (cx > minx && cx < maxx && cy > miny && cy < maxy)
1104 icon_set_selected((Icon *) pi, TRUE);
1108 static void cancel_lasso(void)
1110 draw_lasso();
1111 lasso_in_progress = FALSE;
1114 static void pinboard_lasso_box(int start_x, int start_y)
1116 if (lasso_in_progress)
1117 cancel_lasso();
1118 lasso_in_progress = TRUE;
1119 lasso_rect_x1 = lasso_rect_x2 = start_x;
1120 lasso_rect_y1 = lasso_rect_y2 = start_y;
1122 draw_lasso();
1125 static gint lasso_motion(GtkWidget *widget, GdkEventMotion *event, gpointer d)
1127 if (!lasso_in_progress)
1128 return FALSE;
1130 if (lasso_rect_x2 != event->x || lasso_rect_y2 != event->y)
1132 draw_lasso();
1133 lasso_rect_x2 = event->x;
1134 lasso_rect_y2 = event->y;
1135 draw_lasso();
1138 return FALSE;
1141 /* Mark the area of the screen covered by the lasso box for redraw */
1142 static void draw_lasso(void)
1144 GdkRectangle area, edge;
1146 if (!lasso_in_progress)
1147 return;
1149 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1150 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1151 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1152 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1154 edge.x = area.x;
1155 edge.y = area.y;
1156 edge.width = area.width;
1158 edge.height = 2; /* Top */
1159 gdk_window_invalidate_rect(current_pinboard->window->window,
1160 &edge, TRUE);
1162 edge.y += area.height - 2; /* Bottom */
1163 gdk_window_invalidate_rect(current_pinboard->window->window,
1164 &edge, TRUE);
1166 edge.y = area.y;
1167 edge.height = area.height;
1168 edge.width = 2; /* Left */
1169 gdk_window_invalidate_rect(current_pinboard->window->window,
1170 &edge, TRUE);
1172 edge.x += area.width - 2; /* Right */
1173 gdk_window_invalidate_rect(current_pinboard->window->window,
1174 &edge, TRUE);
1177 static void perform_action(PinIcon *pi, GdkEventButton *event)
1179 BindAction action;
1180 Icon *icon = (Icon *) pi;
1182 action = bind_lookup_bev(pi ? BIND_PINBOARD_ICON : BIND_PINBOARD,
1183 event);
1185 /* Actions that can happen with or without an icon */
1186 switch (action)
1188 case ACT_LASSO_CLEAR:
1189 icon_select_only(NULL);
1190 /* (no break) */
1191 case ACT_LASSO_MODIFY:
1192 pinboard_lasso_box(event->x, event->y);
1193 return;
1194 case ACT_CLEAR_SELECTION:
1195 icon_select_only(NULL);
1196 return;
1197 case ACT_POPUP_MENU:
1198 dnd_motion_ungrab();
1199 pinboard_show_menu(event, pi);
1200 return;
1201 case ACT_IGNORE:
1202 return;
1203 default:
1204 break;
1207 g_return_if_fail(pi != NULL);
1209 switch (action)
1211 case ACT_OPEN_ITEM:
1212 dnd_motion_ungrab();
1213 pinboard_wink_item(pi, TRUE);
1214 if (event->type == GDK_2BUTTON_PRESS)
1215 icon_set_selected(icon, FALSE);
1216 icon_run(icon);
1217 break;
1218 case ACT_EDIT_ITEM:
1219 dnd_motion_ungrab();
1220 pinboard_wink_item(pi, TRUE);
1221 if (event->type == GDK_2BUTTON_PRESS)
1222 icon_set_selected(icon, FALSE);
1223 run_diritem(icon->path, icon->item, NULL, NULL, TRUE);
1224 break;
1225 case ACT_PRIME_AND_SELECT:
1226 if (!icon->selected)
1227 icon_select_only(icon);
1228 dnd_motion_start(MOTION_READY_FOR_DND);
1229 break;
1230 case ACT_PRIME_AND_TOGGLE:
1231 icon_set_selected(icon, !icon->selected);
1232 dnd_motion_start(MOTION_READY_FOR_DND);
1233 break;
1234 case ACT_PRIME_FOR_DND:
1235 dnd_motion_start(MOTION_READY_FOR_DND);
1236 break;
1237 case ACT_TOGGLE_SELECTED:
1238 icon_set_selected(icon, !icon->selected);
1239 break;
1240 case ACT_SELECT_EXCL:
1241 icon_select_only(icon);
1242 break;
1243 default:
1244 g_warning("Unsupported action : %d\n", action);
1245 break;
1249 static void forward_to_root(GdkEventButton *event)
1251 XButtonEvent xev;
1253 if (event->type == GDK_BUTTON_PRESS)
1255 xev.type = ButtonPress;
1256 if (!o_blackbox_hack.int_value)
1257 XUngrabPointer(gdk_display, event->time);
1259 else
1260 xev.type = ButtonRelease;
1262 xev.window = gdk_x11_get_default_root_xwindow();
1263 xev.root = xev.window;
1264 xev.subwindow = None;
1265 xev.time = event->time;
1266 xev.x = event->x_root; /* Needed for icewm */
1267 xev.y = event->y_root;
1268 xev.x_root = event->x_root;
1269 xev.y_root = event->y_root;
1270 xev.state = event->state;
1271 xev.button = event->button;
1272 xev.same_screen = True;
1274 XSendEvent(gdk_display, xev.window, False,
1275 ButtonPressMask | ButtonReleaseMask, (XEvent *) &xev);
1278 #define FORWARDED_BUTTON(pi, b) ((b) == 2 || \
1279 (((b) == 3 || (b) == 1) && o_forward_buttons_13.int_value && !pi))
1281 /* pi is NULL if this is a root event */
1282 static gboolean button_release_event(GtkWidget *widget,
1283 GdkEventButton *event,
1284 PinIcon *pi)
1286 if (FORWARDED_BUTTON(pi, event->button))
1287 forward_to_root(event);
1288 else if (dnd_motion_release(event))
1290 if (motion_buttons_pressed == 0 && lasso_in_progress)
1292 select_lasso();
1293 cancel_lasso();
1295 return FALSE;
1298 perform_action(pi, event);
1300 return TRUE;
1303 /* pi is NULL if this is a root event */
1304 static gboolean button_press_event(GtkWidget *widget,
1305 GdkEventButton *event,
1306 PinIcon *pi)
1308 /* Just in case we've jumped in front of everything... */
1309 gdk_window_lower(current_pinboard->window->window);
1311 if (FORWARDED_BUTTON(pi, event->button))
1312 forward_to_root(event);
1313 else if (dnd_motion_press(widget, event))
1314 perform_action(pi, event);
1316 return TRUE;
1319 /* Forward mouse scroll events as buttons 4 and 5 to the window manager
1320 * (for old window managers that don't catch the buttons themselves)
1322 static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event)
1324 XButtonEvent xev;
1326 xev.type = ButtonPress;
1327 xev.window = gdk_x11_get_default_root_xwindow();
1328 xev.root = xev.window;
1329 xev.subwindow = None;
1330 xev.time = event->time;
1331 xev.x = event->x_root; /* Needed for icewm */
1332 xev.y = event->y_root;
1333 xev.x_root = event->x_root;
1334 xev.y_root = event->y_root;
1335 xev.state = event->state;
1336 xev.same_screen = True;
1338 if (event->direction == GDK_SCROLL_UP)
1339 xev.button = 4;
1340 else if (event->direction == GDK_SCROLL_DOWN)
1341 xev.button = 5;
1342 else
1343 return FALSE;
1345 XSendEvent(gdk_display, xev.window, False,
1346 ButtonPressMask, (XEvent *) &xev);
1348 return TRUE;
1351 static void start_drag(PinIcon *pi, GdkEventMotion *event)
1353 GtkWidget *widget = pi->win;
1354 Icon *icon = (Icon *) pi;
1356 if (!icon->selected)
1358 tmp_icon_selected = TRUE;
1359 icon_select_only(icon);
1362 g_return_if_fail(icon_selection != NULL);
1364 pinboard_drag_in_progress = icon;
1366 if (icon_selection->next == NULL)
1367 drag_one_item(widget, event, icon->path, icon->item, NULL);
1368 else
1370 guchar *uri_list;
1372 uri_list = icon_create_uri_list();
1373 drag_selection(widget, event, uri_list);
1374 g_free(uri_list);
1378 /* An icon is being dragged around... */
1379 static gint icon_motion_notify(GtkWidget *widget,
1380 GdkEventMotion *event,
1381 PinIcon *pi)
1383 if (motion_state == MOTION_READY_FOR_DND)
1385 if (dnd_motion_moved(event))
1386 start_drag(pi, event);
1387 return TRUE;
1390 return FALSE;
1393 static void backdrop_from_xml(xmlNode *node)
1395 gchar *style;
1397 g_free(current_pinboard->backdrop);
1398 current_pinboard->backdrop = xmlNodeGetContent(node);
1400 style = xmlGetProp(node, "style");
1402 if (style)
1404 current_pinboard->backdrop_style =
1405 g_strcasecmp(style, "Tiled") == 0 ? BACKDROP_TILE :
1406 g_strcasecmp(style, "Scaled") == 0 ? BACKDROP_SCALE :
1407 g_strcasecmp(style, "Stretched") == 0 ? BACKDROP_STRETCH :
1408 g_strcasecmp(style, "Centred") == 0 ? BACKDROP_CENTRE :
1409 g_strcasecmp(style, "Program") == 0 ? BACKDROP_PROGRAM :
1410 BACKDROP_NONE;
1411 g_free(style);
1413 else
1414 current_pinboard->backdrop_style = BACKDROP_TILE;
1417 /* Create one pinboard icon for each icon in the doc */
1418 static void pinboard_load_from_xml(xmlDocPtr doc)
1420 xmlNodePtr node, root;
1421 char *tmp, *label, *path, *shortcut, *args;
1422 int x, y;
1424 root = xmlDocGetRootElement(doc);
1426 for (node = root->xmlChildrenNode; node; node = node->next)
1428 if (node->type != XML_ELEMENT_NODE)
1429 continue;
1430 if (strcmp(node->name, "backdrop") == 0)
1432 backdrop_from_xml(node);
1433 continue;
1435 if (strcmp(node->name, "icon") != 0)
1436 continue;
1438 tmp = xmlGetProp(node, "x");
1439 if (!tmp)
1440 continue;
1441 x = atoi(tmp);
1442 g_free(tmp);
1444 tmp = xmlGetProp(node, "y");
1445 if (!tmp)
1446 continue;
1447 y = atoi(tmp);
1448 g_free(tmp);
1450 label = xmlGetProp(node, "label");
1451 if (!label)
1452 label = g_strdup("<missing label>");
1453 path = xmlNodeGetContent(node);
1454 if (!path)
1455 path = g_strdup("<missing path>");
1456 shortcut = xmlGetProp(node, "shortcut");
1457 args = xmlGetProp(node, "args");
1459 pinboard_pin_with_args(path, label, x, y, shortcut, args);
1461 g_free(path);
1462 g_free(label);
1463 g_free(shortcut);
1464 g_free(args);
1468 /* Called for each line in the pinboard file while loading a new board.
1469 * Only used for old-format files when converting to XML.
1471 static const char *pin_from_file(gchar *line)
1473 gchar *leaf = NULL;
1474 int x, y, n;
1476 if (*line == '<')
1478 gchar *end;
1480 end = strchr(line + 1, '>');
1481 if (!end)
1482 return _("Missing '>' in icon label");
1484 leaf = g_strndup(line + 1, end - line - 1);
1486 line = end + 1;
1488 while (g_ascii_isspace(*line))
1489 line++;
1490 if (*line != ',')
1491 return _("Missing ',' after icon label");
1492 line++;
1495 if (sscanf(line, " %d , %d , %n", &x, &y, &n) < 2)
1496 return NULL; /* Ignore format errors */
1498 pinboard_pin(line + n, leaf, x, y, NULL);
1500 g_free(leaf);
1502 return NULL;
1505 /* Write the current state of the pinboard to the current pinboard file */
1506 static void pinboard_save(void)
1508 guchar *save = NULL;
1509 guchar *save_new = NULL;
1510 GList *next;
1511 xmlDocPtr doc = NULL;
1512 xmlNodePtr root;
1514 g_return_if_fail(current_pinboard != NULL);
1516 if (strchr(current_pinboard->name, '/'))
1517 save = g_strdup(current_pinboard->name);
1518 else
1520 guchar *leaf;
1522 leaf = g_strconcat("pb_", current_pinboard->name, NULL);
1523 save = choices_find_xdg_path_save(leaf, PROJECT, SITE, TRUE);
1524 g_free(leaf);
1527 if (!save)
1528 return;
1530 doc = xmlNewDoc("1.0");
1531 xmlDocSetRootElement(doc, xmlNewDocNode(doc, NULL, "pinboard", NULL));
1533 root = xmlDocGetRootElement(doc);
1535 if (current_pinboard->backdrop)
1537 BackdropStyle style = current_pinboard->backdrop_style;
1538 xmlNodePtr tree;
1540 tree = xmlNewTextChild(root, NULL, "backdrop",
1541 current_pinboard->backdrop);
1542 xmlSetProp(tree, "style",
1543 style == BACKDROP_TILE ? "Tiled" :
1544 style == BACKDROP_CENTRE ? "Centred" :
1545 style == BACKDROP_SCALE ? "Scaled" :
1546 style == BACKDROP_STRETCH ? "Stretched" :
1547 "Program");
1550 for (next = current_pinboard->icons; next; next = next->next)
1552 xmlNodePtr tree;
1553 PinIcon *pi = (PinIcon *) next->data;
1554 Icon *icon = (Icon *) pi;
1555 char *tmp;
1557 tree = xmlNewTextChild(root, NULL, "icon", icon->src_path);
1559 tmp = g_strdup_printf("%d", pi->x);
1560 xmlSetProp(tree, "x", tmp);
1561 g_free(tmp);
1563 tmp = g_strdup_printf("%d", pi->y);
1564 xmlSetProp(tree, "y", tmp);
1565 g_free(tmp);
1567 xmlSetProp(tree, "label", icon->item->leafname);
1568 if (icon->shortcut)
1569 xmlSetProp(tree, "shortcut", icon->shortcut);
1570 if (icon->args)
1571 xmlSetProp(tree, "args", icon->args);
1574 save_new = g_strconcat(save, ".new", NULL);
1575 if (save_xml_file(doc, save_new) || rename(save_new, save))
1576 delayed_error(_("Error saving pinboard %s: %s"),
1577 save, g_strerror(errno));
1578 g_free(save_new);
1580 g_free(save);
1581 if (doc)
1582 xmlFreeDoc(doc);
1585 static void snap_to_grid(int *x, int *y)
1587 int step = o_pinboard_grid_step.int_value;
1589 *x = ((*x + step / 2) / step) * step;
1590 *y = ((*y + step / 2) / step) * step;
1593 /* Convert (x,y) from a centre point to a window position */
1594 static void offset_from_centre(PinIcon *pi, int *x, int *y)
1596 gboolean clamp = o_pinboard_clamp_icons.int_value;
1597 GtkRequisition req;
1599 gtk_widget_size_request(pi->win, &req);
1601 *x -= req.width >> 1;
1602 *y -= req.height >> 1;
1603 *x = CLAMP(*x, 0, screen_width - (clamp ? req.width : 0));
1604 *y = CLAMP(*y, 0, screen_height - (clamp ? req.height : 0));
1607 /* Same as drag_set_dest(), but for pinboard icons */
1608 static void drag_set_pinicon_dest(PinIcon *pi)
1610 GtkObject *obj = GTK_OBJECT(pi->win);
1612 make_drop_target(pi->win, 0);
1614 g_signal_connect(obj, "drag_motion", G_CALLBACK(drag_motion), pi);
1615 g_signal_connect(obj, "drag_leave", G_CALLBACK(drag_leave), pi);
1616 g_signal_connect(obj, "drag_end", G_CALLBACK(drag_end), pi);
1619 /* Called during the drag when the mouse is in a widget registered
1620 * as a drop target. Returns TRUE if we can accept the drop.
1622 static gboolean drag_motion(GtkWidget *widget,
1623 GdkDragContext *context,
1624 gint x,
1625 gint y,
1626 guint time,
1627 PinIcon *pi)
1629 GdkDragAction action = context->suggested_action;
1630 const char *type = NULL;
1631 Icon *icon = (Icon *) pi;
1632 DirItem *item = icon->item;
1634 if (gtk_drag_get_source_widget(context) == widget)
1636 g_dataset_set_data(context, "drop_dest_type",
1637 (gpointer) drop_dest_pass_through);
1638 return FALSE; /* Can't drag something to itself! */
1641 if (icon->selected)
1642 goto out; /* Can't drag a selection to itself */
1644 type = dnd_motion_item(context, &item);
1646 if ((context->actions & GDK_ACTION_ASK) && o_dnd_left_menu.int_value
1647 && type != drop_dest_prog)
1649 guint state;
1650 gdk_window_get_pointer(NULL, NULL, NULL, &state);
1651 if (state & GDK_BUTTON1_MASK)
1652 action = GDK_ACTION_ASK;
1655 if (!item)
1656 type = NULL;
1657 out:
1658 /* We actually must pretend to accept the drop, even if the
1659 * directory isn't writeable, so that the spring-opening
1660 * thing works.
1663 /* Don't allow drops to non-writeable directories */
1664 if (o_dnd_spring_open.int_value == FALSE &&
1665 type == drop_dest_dir &&
1666 access(icon->path, W_OK) != 0)
1668 type = NULL;
1671 g_dataset_set_data(context, "drop_dest_type", (gpointer) type);
1672 if (type)
1674 gdk_drag_status(context, action, time);
1675 g_dataset_set_data_full(context, "drop_dest_path",
1676 g_strdup(icon->path), g_free);
1677 if (type == drop_dest_dir)
1678 dnd_spring_load(context, NULL);
1680 pinboard_wink_item(pi, FALSE);
1682 else
1683 gdk_drag_status(context, 0, time);
1685 /* Always return TRUE to stop the pinboard getting the events */
1686 return TRUE;
1689 static gboolean pinboard_shadow = FALSE;
1690 static gint shadow_x, shadow_y;
1691 #define SHADOW_SIZE (ICON_WIDTH)
1693 static gboolean bg_expose(GtkWidget *widget,
1694 GdkEventExpose *event, gpointer data)
1696 GdkRectangle clipbox;
1697 gpointer gclass = ((GTypeInstance *) widget)->g_class;
1698 gboolean double_buffer;
1700 gdk_gc_set_clip_region(widget->style->white_gc, event->region);
1701 gdk_gc_set_clip_region(widget->style->black_gc, event->region);
1703 gdk_region_get_clipbox(event->region, &clipbox);
1705 double_buffer = (clipbox.width * clipbox.height) < 20000;
1706 if (double_buffer)
1707 gdk_window_begin_paint_region(widget->window, event->region);
1709 /* Clear the area to the background image */
1711 GtkStyle *style = current_pinboard->window->style;
1712 GdkGC *gc = style->bg_gc[GTK_STATE_NORMAL];
1714 gdk_gc_set_clip_region(gc, event->region);
1715 if (style->bg_pixmap[GTK_STATE_NORMAL])
1717 gdk_gc_set_ts_origin(gc, 0, 0);
1718 gdk_gc_set_fill(gc, GDK_TILED);
1719 gdk_gc_set_tile(gc, style->bg_pixmap[GTK_STATE_NORMAL]);
1722 gdk_draw_rectangle(current_pinboard->window->window, gc, TRUE,
1723 clipbox.x, clipbox.y,
1724 clipbox.width, clipbox.height);
1725 if (style->bg_pixmap[GTK_STATE_NORMAL])
1726 gdk_gc_set_fill(gc, GDK_SOLID);
1728 gdk_gc_set_clip_region(gc, NULL);
1731 if (pinboard_shadow)
1733 gdk_draw_rectangle(widget->window,
1734 widget->style->white_gc, FALSE,
1735 shadow_x, shadow_y,
1736 SHADOW_SIZE, SHADOW_SIZE);
1737 gdk_draw_rectangle(widget->window,
1738 widget->style->black_gc, FALSE,
1739 shadow_x + 1, shadow_y + 1,
1740 SHADOW_SIZE - 2, SHADOW_SIZE - 2);
1743 if (lasso_in_progress)
1745 GdkRectangle area;
1747 area.x = MIN(lasso_rect_x1, lasso_rect_x2);
1748 area.y = MIN(lasso_rect_y1, lasso_rect_y2);
1749 area.width = ABS(lasso_rect_x1 - lasso_rect_x2);
1750 area.height = ABS(lasso_rect_y1 - lasso_rect_y2);
1752 if (area.width > 4 && area.height > 4)
1754 gdk_draw_rectangle(widget->window,
1755 widget->style->white_gc, FALSE,
1756 area.x, area.y,
1757 area.width - 1, area.height - 1);
1758 gdk_draw_rectangle(widget->window,
1759 widget->style->black_gc, FALSE,
1760 area.x + 1, area.y + 1,
1761 area.width - 3, area.height - 3);
1765 gdk_gc_set_clip_region(widget->style->white_gc, NULL);
1766 gdk_gc_set_clip_region(widget->style->black_gc, NULL);
1768 ((GtkWidgetClass *) gclass)->expose_event(widget, event);
1770 if (double_buffer)
1771 gdk_window_end_paint(widget->window);
1773 return TRUE;
1776 /* Draw a 'shadow' under an icon being dragged, showing where
1777 * it will land.
1779 static void pinboard_set_shadow(gboolean on)
1781 GdkRectangle area;
1783 if (pinboard_shadow)
1785 area.x = shadow_x;
1786 area.y = shadow_y;
1787 area.width = SHADOW_SIZE + 1;
1788 area.height = SHADOW_SIZE + 1;
1790 gdk_window_invalidate_rect(current_pinboard->window->window,
1791 &area, TRUE);
1794 if (on)
1796 int old_x = shadow_x, old_y = shadow_y;
1798 gdk_window_get_pointer(current_pinboard->fixed->window,
1799 &shadow_x, &shadow_y, NULL);
1800 snap_to_grid(&shadow_x, &shadow_y);
1801 shadow_x -= SHADOW_SIZE / 2;
1802 shadow_y -= SHADOW_SIZE / 2;
1805 if (pinboard_shadow && shadow_x == old_x && shadow_y == old_y)
1806 return;
1808 area.x = shadow_x;
1809 area.y = shadow_y;
1810 area.width = SHADOW_SIZE + 1;
1811 area.height = SHADOW_SIZE + 1;
1813 gdk_window_invalidate_rect(current_pinboard->window->window,
1814 &area, TRUE);
1817 pinboard_shadow = on;
1820 /* Called when dragging some pinboard icons finishes */
1821 void pinboard_move_icons(void)
1823 int x = shadow_x, y = shadow_y;
1824 PinIcon *pi = (PinIcon *) pinboard_drag_in_progress;
1825 int width, height;
1826 int dx, dy;
1827 GList *next;
1829 g_return_if_fail(pi != NULL);
1831 x += SHADOW_SIZE / 2;
1832 y += SHADOW_SIZE / 2;
1833 snap_to_grid(&x, &y);
1835 if (pi->x == x && pi->y == y)
1836 return;
1838 /* Find out how much the dragged icon moved (after snapping).
1839 * Move all selected icons by the same amount.
1841 dx = x - pi->x;
1842 dy = y - pi->y;
1844 /* Move the other selected icons to keep the same relative
1845 * position.
1847 for (next = icon_selection; next; next = next->next)
1849 PinIcon *pi = (PinIcon *) next->data;
1850 int nx, ny;
1852 g_return_if_fail(IS_PIN_ICON(pi));
1854 pi->x += dx;
1855 pi->y += dy;
1856 nx = pi->x;
1857 ny = pi->y;
1859 gdk_drawable_get_size(pi->win->window, &width, &height);
1860 offset_from_centre(pi, &nx, &ny);
1862 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
1863 pi->win, nx, ny);
1866 pinboard_save();
1869 static void drag_leave(GtkWidget *widget,
1870 GdkDragContext *context,
1871 guint32 time,
1872 PinIcon *pi)
1874 pinboard_wink_item(NULL, FALSE);
1875 dnd_spring_abort();
1878 static gboolean bg_drag_leave(GtkWidget *widget,
1879 GdkDragContext *context,
1880 guint32 time,
1881 gpointer data)
1883 pinboard_set_shadow(FALSE);
1884 return TRUE;
1887 static gboolean bg_drag_motion(GtkWidget *widget,
1888 GdkDragContext *context,
1889 gint x,
1890 gint y,
1891 guint time,
1892 gpointer data)
1894 /* Dragging from the pinboard to the pinboard is not allowed */
1896 if (!provides(context, text_uri_list))
1897 return FALSE;
1899 pinboard_set_shadow(TRUE);
1901 gdk_drag_status(context,
1902 context->suggested_action == GDK_ACTION_ASK
1903 ? GDK_ACTION_LINK : context->suggested_action,
1904 time);
1905 return TRUE;
1908 static void drag_end(GtkWidget *widget,
1909 GdkDragContext *context,
1910 PinIcon *pi)
1912 pinboard_drag_in_progress = NULL;
1913 if (tmp_icon_selected)
1915 icon_select_only(NULL);
1916 tmp_icon_selected = FALSE;
1920 /* Something which affects all the icons has changed - reshape
1921 * and redraw all of them.
1923 static void reshape_all(void)
1925 GList *next;
1927 g_return_if_fail(current_pinboard != NULL);
1929 for (next = current_pinboard->icons; next; next = next->next)
1931 Icon *icon = (Icon *) next->data;
1932 pinboard_reshape_icon(icon);
1936 /* Turns off the pinboard. Does not call gtk_main_quit. */
1937 static void pinboard_clear(void)
1939 GList *next;
1941 g_return_if_fail(current_pinboard != NULL);
1943 tasklist_set_active(FALSE);
1945 next = current_pinboard->icons;
1946 while (next)
1948 PinIcon *pi = (PinIcon *) next->data;
1950 next = next->next;
1952 gtk_widget_destroy(pi->win);
1955 gtk_widget_destroy(current_pinboard->window);
1957 abandon_backdrop_app(current_pinboard);
1959 g_object_unref(current_pinboard->shadow_gc);
1960 current_pinboard->shadow_gc = NULL;
1962 g_free(current_pinboard->name);
1963 null_g_free(&current_pinboard);
1965 number_of_windows--;
1968 static gpointer parent_class;
1970 static void pin_icon_destroy(Icon *icon)
1972 PinIcon *pi = (PinIcon *) icon;
1974 g_return_if_fail(pi->win != NULL);
1976 gtk_widget_hide(pi->win); /* Stops flicker - stupid GtkFixed! */
1977 gtk_widget_destroy(pi->win);
1980 static void pinboard_remove_items(void)
1982 g_return_if_fail(icon_selection != NULL);
1984 while (icon_selection)
1985 icon_destroy((Icon *) icon_selection->data);
1987 pinboard_save();
1990 static void pin_icon_update(Icon *icon)
1992 pinboard_reshape_icon(icon);
1993 pinboard_save();
1996 static gboolean pin_icon_same_group(Icon *icon, Icon *other)
1998 return IS_PIN_ICON(other);
2001 static void pin_wink_icon(Icon *icon)
2003 pinboard_wink_item((PinIcon *) icon, TRUE);
2006 static void pin_icon_class_init(gpointer gclass, gpointer data)
2008 IconClass *icon = (IconClass *) gclass;
2010 parent_class = g_type_class_peek_parent(gclass);
2012 icon->destroy = pin_icon_destroy;
2013 icon->redraw = pinboard_reshape_icon;
2014 icon->update = pin_icon_update;
2015 icon->wink = pin_wink_icon;
2016 icon->remove_items = pinboard_remove_items;
2017 icon->same_group = pin_icon_same_group;
2020 static void pin_icon_init(GTypeInstance *object, gpointer gclass)
2024 static GType pin_icon_get_type(void)
2026 static GType type = 0;
2028 if (!type)
2030 static const GTypeInfo info =
2032 sizeof (PinIconClass),
2033 NULL, /* base_init */
2034 NULL, /* base_finalise */
2035 pin_icon_class_init,
2036 NULL, /* class_finalise */
2037 NULL, /* class_data */
2038 sizeof(PinIcon),
2039 0, /* n_preallocs */
2040 pin_icon_init
2043 type = g_type_register_static(icon_get_type(),
2044 "PinIcon", &info, 0);
2047 return type;
2050 static PinIcon *pin_icon_new(const char *pathname, const char *name)
2052 PinIcon *pi;
2053 Icon *icon;
2055 pi = g_object_new(pin_icon_get_type(), NULL);
2056 icon = (Icon *) pi;
2058 icon_set_path(icon, pathname, name);
2060 return pi;
2063 /* Called when the window widget is somehow destroyed */
2064 static void pin_icon_destroyed(PinIcon *pi)
2066 g_return_if_fail(pi->win != NULL);
2068 pi->win = NULL;
2070 pinboard_wink_item(NULL, FALSE);
2072 if (pinboard_drag_in_progress == (Icon *) pi)
2073 pinboard_drag_in_progress = NULL;
2075 if (current_pinboard)
2076 current_pinboard->icons =
2077 g_list_remove(current_pinboard->icons, pi);
2079 g_object_unref(pi);
2082 /* Set the tooltip */
2083 static void pin_icon_set_tip(PinIcon *pi)
2085 XMLwrapper *ai;
2086 xmlNode *node;
2087 Icon *icon = (Icon *) pi;
2089 g_return_if_fail(pi != NULL);
2091 ai = appinfo_get(icon->path, icon->item);
2093 if (ai && ((node = xml_get_section(ai, NULL, "Summary"))))
2095 guchar *str;
2096 str = xmlNodeListGetString(node->doc,
2097 node->xmlChildrenNode, 1);
2098 if (str)
2100 gtk_tooltips_set_tip(tooltips, pi->win, str, NULL);
2101 g_free(str);
2104 else
2105 gtk_tooltips_set_tip(tooltips, pi->widget, NULL, NULL);
2107 if (ai)
2108 g_object_unref(ai);
2111 static void pinboard_show_menu(GdkEventButton *event, PinIcon *pi)
2113 GtkWidget *option_item;
2114 int pos[3];
2115 GList *list;
2117 pos[0] = event->x_root;
2118 pos[1] = event->y_root;
2120 option_item = gtk_image_menu_item_new_with_label(_("Backdrop..."));
2121 g_signal_connect(option_item, "activate",
2122 G_CALLBACK(pinboard_set_backdrop_box), NULL);
2123 icon_prepare_menu((Icon *) pi, option_item);
2125 list = gtk_container_get_children(GTK_CONTAINER(icon_menu));
2126 pos[2] = g_list_length(list) - 6;
2127 g_list_free(list);
2129 gtk_menu_popup(GTK_MENU(icon_menu), NULL, NULL,
2130 position_menu,
2131 (gpointer) pos, event->button, event->time);
2134 static void create_pinboard_window(Pinboard *pinboard)
2136 GtkWidget *win;
2138 g_return_if_fail(pinboard->window == NULL);
2140 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2141 gtk_widget_set_style(win, gtk_widget_get_default_style());
2143 gtk_widget_set_double_buffered(win, FALSE);
2144 gtk_widget_set_app_paintable(win, TRUE);
2145 gtk_widget_set_name(win, "rox-pinboard");
2146 pinboard->window = win;
2147 pinboard->fixed = gtk_fixed_new();
2148 gtk_container_add(GTK_CONTAINER(win), pinboard->fixed);
2150 gtk_window_set_wmclass(GTK_WINDOW(win), "ROX-Pinboard", PROJECT);
2152 gtk_widget_set_size_request(win, screen_width, screen_height);
2153 gtk_widget_realize(win);
2154 gtk_window_move(GTK_WINDOW(win), 0, 0);
2155 make_panel_window(win);
2157 /* TODO: Use gdk function when it supports this type */
2159 GdkAtom desktop_type;
2161 desktop_type = gdk_atom_intern("_NET_WM_WINDOW_TYPE_DESKTOP",
2162 FALSE);
2163 gdk_property_change(win->window,
2164 gdk_atom_intern("_NET_WM_WINDOW_TYPE", FALSE),
2165 gdk_atom_intern("ATOM", FALSE), 32,
2166 GDK_PROP_MODE_REPLACE, (guchar *) &desktop_type, 1);
2169 gtk_widget_add_events(win,
2170 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
2171 GDK_EXPOSURE_MASK |
2172 GDK_BUTTON1_MOTION_MASK |
2173 GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK);
2174 g_signal_connect(win, "button-press-event",
2175 G_CALLBACK(button_press_event), NULL);
2176 g_signal_connect(win, "button-release-event",
2177 G_CALLBACK(button_release_event), NULL);
2178 g_signal_connect(win, "motion-notify-event",
2179 G_CALLBACK(lasso_motion), NULL);
2180 g_signal_connect(pinboard->fixed, "expose_event",
2181 G_CALLBACK(bg_expose), NULL);
2183 /* Some window managers use scroll events on the root to switch
2184 * desktops, but don't cope with our pinboard window, so we forward
2185 * them manually in that case.
2187 g_signal_connect(win, "scroll-event", G_CALLBACK(scroll_event), NULL);
2189 /* Drag and drop handlers */
2190 drag_set_pinboard_dest(win);
2191 g_signal_connect(win, "drag_motion", G_CALLBACK(bg_drag_motion), NULL);
2192 g_signal_connect(win, "drag_leave", G_CALLBACK(bg_drag_leave), NULL);
2194 pinboard->shadow_gc = gdk_gc_new(win->window);
2195 gdk_gc_set_rgb_fg_color(pinboard->shadow_gc, &pin_text_shadow_col);
2197 reload_backdrop(current_pinboard, NULL, BACKDROP_NONE);
2199 gtk_widget_show_all(win);
2200 gdk_window_lower(win->window);
2203 /* Load image 'path' and scale according to 'style' */
2204 static GdkPixmap *load_backdrop(const gchar *path, BackdropStyle style)
2206 GdkPixmap *pixmap;
2207 GdkPixbuf *pixbuf;
2208 GError *error = NULL;
2210 pixbuf = gdk_pixbuf_new_from_file(path, &error);
2211 if (error)
2213 delayed_error(_("Error loading backdrop image:\n%s\n"
2214 "Backdrop removed."),
2215 error->message);
2216 g_error_free(error);
2217 pinboard_set_backdrop(NULL, BACKDROP_NONE);
2218 return NULL;
2221 if (style == BACKDROP_STRETCH)
2223 GdkPixbuf *old = pixbuf;
2225 pixbuf = gdk_pixbuf_scale_simple(old,
2226 screen_width, screen_height,
2227 GDK_INTERP_HYPER);
2229 g_object_unref(old);
2231 else if (style == BACKDROP_CENTRE || style == BACKDROP_SCALE)
2233 GdkPixbuf *old = pixbuf;
2234 int x, y, width, height;
2235 float scale;
2237 width = gdk_pixbuf_get_width(pixbuf);
2238 height = gdk_pixbuf_get_height(pixbuf);
2240 if (style == BACKDROP_SCALE)
2242 float scale_x, scale_y;
2243 scale_x = screen_width / ((float) width);
2244 scale_y = screen_height / ((float) height);
2245 scale = MIN(scale_x, scale_y);
2247 else
2248 scale = 1;
2250 pixbuf = gdk_pixbuf_new(
2251 gdk_pixbuf_get_colorspace(pixbuf), FALSE,
2252 8, screen_width, screen_height);
2253 gdk_pixbuf_fill(pixbuf, ((pin_text_bg_col.red & 0xff00) << 16) |
2254 ((pin_text_bg_col.green & 0xff00) << 8) |
2255 ((pin_text_bg_col.blue & 0xff00)));
2257 x = (screen_width - width * scale) / 2;
2258 y = (screen_height - height * scale) / 2;
2259 x = MAX(x, 0);
2260 y = MAX(y, 0);
2262 gdk_pixbuf_composite(old, pixbuf,
2263 x, y,
2264 MIN(screen_width, width * scale),
2265 MIN(screen_height, height * scale),
2266 x, y, scale, scale,
2267 o_pinboard_image_scaling.int_value?
2268 GDK_INTERP_BILINEAR: GDK_INTERP_HYPER,
2269 255);
2270 g_object_unref(old);
2273 gdk_pixbuf_render_pixmap_and_mask(pixbuf,
2274 &pixmap, NULL, 0);
2275 g_object_unref(pixbuf);
2277 return pixmap;
2280 static void abandon_backdrop_app(Pinboard *pinboard)
2282 g_return_if_fail(pinboard != NULL);
2284 if (pinboard->to_backdrop_app != -1)
2286 close(pinboard->to_backdrop_app);
2287 close(pinboard->from_backdrop_app);
2288 g_source_remove(pinboard->input_tag);
2289 g_string_free(pinboard->input_buffer, TRUE);
2290 pinboard->to_backdrop_app = -1;
2291 pinboard->from_backdrop_app = -1;
2292 pinboard->input_tag = -1;
2293 pinboard->input_buffer = NULL;
2296 g_return_if_fail(pinboard->to_backdrop_app == -1);
2297 g_return_if_fail(pinboard->from_backdrop_app == -1);
2298 g_return_if_fail(pinboard->input_tag == -1);
2299 g_return_if_fail(pinboard->input_buffer == NULL);
2302 /* A single line has been read from the child.
2303 * Processes the command, and replies 'ok' (or abandons the child on error).
2305 static void command_from_backdrop_app(Pinboard *pinboard, const gchar *command)
2307 BackdropStyle style;
2308 const char *ok = "ok\n";
2310 if (strncmp(command, "tile ", 5) == 0)
2312 style = BACKDROP_TILE;
2313 command += 5;
2315 else if (strncmp(command, "scale ", 6) == 0)
2317 style = BACKDROP_SCALE;
2318 command += 6;
2320 else if (strncmp(command, "stretch ", 8) == 0)
2322 style = BACKDROP_STRETCH;
2323 command += 8;
2325 else if (strncmp(command, "centre ", 7) == 0)
2327 style = BACKDROP_CENTRE;
2328 command += 7;
2330 else
2332 g_warning("Invalid command '%s' from backdrop app\n",
2333 command);
2334 abandon_backdrop_app(pinboard);
2335 return;
2338 /* Load the backdrop. May abandon the program if loading fails. */
2339 reload_backdrop(pinboard, command, style);
2341 if (pinboard->to_backdrop_app == -1)
2342 return;
2344 while (*ok)
2346 int sent;
2348 sent = write(pinboard->to_backdrop_app, ok, strlen(ok));
2349 if (sent <= 0)
2351 /* Remote app quit? Not an error. */
2352 abandon_backdrop_app(pinboard);
2353 break;
2355 ok += sent;
2359 static void backdrop_from_child(Pinboard *pinboard,
2360 int src, GdkInputCondition cond)
2362 char buf[256];
2363 int got;
2365 got = read(src, buf, sizeof(buf));
2367 if (got <= 0)
2369 if (got < 0)
2370 g_warning("backdrop_from_child: %s\n",
2371 g_strerror(errno));
2372 abandon_backdrop_app(pinboard);
2373 return;
2376 g_string_append_len(pinboard->input_buffer, buf, got);
2378 while (pinboard->from_backdrop_app != -1)
2380 int len;
2381 char *nl, *command;
2383 nl = strchr(pinboard->input_buffer->str, '\n');
2384 if (!nl)
2385 return; /* Haven't got a whole line yet */
2387 len = nl - pinboard->input_buffer->str;
2388 command = g_strndup(pinboard->input_buffer->str, len);
2389 g_string_erase(pinboard->input_buffer, 0, len + 1);
2391 command_from_backdrop_app(pinboard, command);
2393 g_free(command);
2397 static void reload_backdrop(Pinboard *pinboard,
2398 const gchar *backdrop,
2399 BackdropStyle backdrop_style)
2401 GtkStyle *style;
2403 if (backdrop && backdrop_style == BACKDROP_PROGRAM)
2405 const char *argv[] = {NULL, "--backdrop", NULL};
2406 GError *error = NULL;
2408 g_return_if_fail(pinboard->to_backdrop_app == -1);
2409 g_return_if_fail(pinboard->from_backdrop_app == -1);
2410 g_return_if_fail(pinboard->input_tag == -1);
2411 g_return_if_fail(pinboard->input_buffer == NULL);
2413 argv[0] = make_path(backdrop, "AppRun");
2415 /* Run the program. It'll send us a SOAP message and we'll
2416 * get back here with a different style and image.
2419 if (g_spawn_async_with_pipes(NULL, (gchar **) argv, NULL,
2420 G_SPAWN_DO_NOT_REAP_CHILD |
2421 G_SPAWN_SEARCH_PATH,
2422 NULL, NULL, /* Child setup fn */
2423 NULL, /* Child PID */
2424 &pinboard->to_backdrop_app,
2425 &pinboard->from_backdrop_app,
2426 NULL, /* Standard error */
2427 &error))
2429 pinboard->input_buffer = g_string_new(NULL);
2430 pinboard->input_tag = gdk_input_add_full(
2431 pinboard->from_backdrop_app,
2432 GDK_INPUT_READ,
2433 (GdkInputFunction) backdrop_from_child,
2434 pinboard, NULL);
2436 else
2438 delayed_error("%s", error ? error->message : "(null)");
2439 g_error_free(error);
2441 return;
2444 /* Note: Copying a style does not ref the pixmaps! */
2446 style = gtk_style_copy(gtk_widget_get_style(pinboard->window));
2447 style->bg_pixmap[GTK_STATE_NORMAL] = NULL;
2449 if (backdrop)
2450 style->bg_pixmap[GTK_STATE_NORMAL] =
2451 load_backdrop(backdrop, backdrop_style);
2453 gdk_color_parse(o_pinboard_bg_colour.value,
2454 &style->bg[GTK_STATE_NORMAL]);
2456 gtk_widget_set_style(pinboard->window, style);
2458 g_object_unref(style);
2460 gtk_widget_queue_draw(pinboard->window);
2462 /* Also update root window property (for transparent xterms, etc) */
2463 if (style->bg_pixmap[GTK_STATE_NORMAL])
2465 XID id = GDK_DRAWABLE_XID(style->bg_pixmap[GTK_STATE_NORMAL]);
2466 gdk_property_change(gdk_get_default_root_window(),
2467 gdk_atom_intern("_XROOTPMAP_ID", FALSE),
2468 gdk_atom_intern("PIXMAP", FALSE),
2469 32, GDK_PROP_MODE_REPLACE,
2470 (guchar *) &id, 1);
2472 else
2474 gdk_property_delete(gdk_get_default_root_window(),
2475 gdk_atom_intern("_XROOTPMAP_ID", FALSE));
2479 #define SEARCH_STEP 32
2481 /* Search the area (omin, imin) to (omax, imax) for a free region the size of
2482 * 'rect' that doesn't overlap 'used'. Which of inner and outer is the
2483 * vertical axis depends on the configuration.
2485 * id and od give the direction of the search (step size).
2487 * Returns the start of the found region in inner/outer, or -1 if there is no
2488 * free space.
2490 static void search_free(GdkRectangle *rect, GdkRegion *used,
2491 int *outer, int od, int omin, int omax,
2492 int *inner, int id, int imin, int imax)
2494 *outer = od > 0 ? omin : omax;
2495 while (*outer >= omin && *outer <= omax)
2497 *inner = id > 0 ? imin : imax;
2498 while (*inner >= imin && *inner <= imax)
2500 if (gdk_region_rect_in(used, rect) ==
2501 GDK_OVERLAP_RECTANGLE_OUT)
2502 return;
2503 *inner += id;
2506 *outer += od;
2509 rect->x = -1;
2510 rect->y = -1;
2513 /* Search the width x height area from (x0, y0) for a free region of size
2514 * 'rect'. direction indicates whether to search rows or columns. dx, dy gives
2515 * the direction of the search.
2517 static void search_free_area(GdkRectangle *rect, GdkRegion *used,
2518 int direction, int dx, int dy, int x0, int y0, int width, int height)
2520 if (direction == DIR_VERT)
2522 search_free(rect, used,
2523 &rect->x, dx, x0, width,
2524 &rect->y, dy, y0, height);
2526 else
2528 search_free(rect, used,
2529 &rect->y, dy, x0, height,
2530 &rect->x, dx, y0, width);
2534 static gboolean search_free_xinerama(GdkRectangle *rect, GdkRegion *used,
2535 int direction, int dx, int dy, int rwidth, int rheight)
2537 GdkRectangle *geom = &monitor_geom[get_monitor_under_pointer()];
2539 search_free_area(rect, used, direction, dx, dy,
2540 geom->x, geom->y, geom->width - rwidth, geom->height - rheight);
2541 return rect->x != -1;
2544 /* Finds a free area on the pinboard large enough for the width and height
2545 * of the given rectangle, by filling in the x and y fields of 'rect'.
2546 * If 'old' is true, 'rect' has a previous position and we first check
2547 * if it is viable.
2548 * The search order respects user preferences.
2549 * If no area is free, returns any old area.
2551 static void find_free_rect(Pinboard *pinboard, GdkRectangle *rect,
2552 gboolean old)
2554 GdkRegion *used;
2555 GList *next;
2556 GdkRectangle used_rect;
2557 int dx = SEARCH_STEP, dy = SEARCH_STEP;
2559 used = gdk_region_new();
2561 panel_mark_used(used);
2563 /* Subtract the no-go areas... */
2565 if (o_top_margin.int_value > 0)
2567 used_rect.x = 0;
2568 used_rect.y = 0;
2569 used_rect.width = gdk_screen_width();
2570 used_rect.height = o_top_margin.int_value;
2571 gdk_region_union_with_rect(used, &used_rect);
2574 if (o_bottom_margin.int_value > 0)
2576 used_rect.x = 0;
2577 used_rect.y = gdk_screen_height() - o_bottom_margin.int_value;
2578 used_rect.width = gdk_screen_width();
2579 used_rect.height = o_bottom_margin.int_value;
2580 gdk_region_union_with_rect(used, &used_rect);
2583 if (o_left_margin.int_value > 0)
2585 used_rect.x = 0;
2586 used_rect.y = 0;
2587 used_rect.width = o_left_margin.int_value;
2588 used_rect.height = gdk_screen_height();
2589 gdk_region_union_with_rect(used, &used_rect);
2592 if (o_right_margin.int_value > 0)
2594 used_rect.x = gdk_screen_width() - o_right_margin.int_value;
2595 used_rect.y = 0;
2596 used_rect.width = o_right_margin.int_value;
2597 used_rect.height = gdk_screen_height();
2598 gdk_region_union_with_rect(used, &used_rect);
2601 /* Subtract the used areas... */
2603 next = GTK_FIXED(pinboard->fixed)->children;
2604 for (; next; next = next->next)
2606 GtkFixedChild *fix = (GtkFixedChild *) next->data;
2608 if (!GTK_WIDGET_VISIBLE(fix->widget))
2609 continue;
2611 used_rect.x = fix->x;
2612 used_rect.y = fix->y;
2613 used_rect.width = fix->widget->requisition.width;
2614 used_rect.height = fix->widget->requisition.height;
2616 gdk_region_union_with_rect(used, &used_rect);
2619 /* Check the previous area */
2620 if(old) {
2621 if(gdk_region_rect_in(used, rect)==GDK_OVERLAP_RECTANGLE_OUT) {
2622 gdk_region_destroy(used);
2623 return;
2627 /* Find the first free area (yes, this isn't exactly pretty, but
2628 * it works). If you know a better (fast!) algorithm, let me know!
2631 if (o_iconify_start.int_value == CORNER_TOP_RIGHT ||
2632 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2633 dx = -SEARCH_STEP;
2635 if (o_iconify_start.int_value == CORNER_BOTTOM_LEFT ||
2636 o_iconify_start.int_value == CORNER_BOTTOM_RIGHT)
2637 dy = -SEARCH_STEP;
2639 /* If pinboard covers more than one monitor, try to find free space on
2640 * monitor under pointer first, then whole screen if that fails */
2641 if (n_monitors == 1 || !search_free_xinerama(rect, used,
2642 o_iconify_dir.int_value, dx, dy, rect->width, rect->height))
2644 search_free_area(rect, used, o_iconify_dir.int_value, dx, dy,
2645 0, 0, screen_width - rect->width, screen_height - rect->height);
2648 gdk_region_destroy(used);
2650 if (rect->x == -1)
2652 rect->x = 0;
2653 rect->y = 0;
2657 /* Icon's size, shape or appearance has changed - update the display */
2658 static void pinboard_reshape_icon(Icon *icon)
2660 PinIcon *pi = (PinIcon *) icon;
2661 int x = pi->x, y = pi->y;
2663 set_size_and_style(pi);
2664 offset_from_centre(pi, &x, &y);
2666 if (pi->win->allocation.x != x || pi->win->allocation.y != y)
2668 fixed_move_fast(GTK_FIXED(current_pinboard->fixed),
2669 pi->win, x, y);
2672 /* Newer versions of GTK seem to need this, or the icon doesn't
2673 * get redrawn.
2675 gtk_widget_queue_draw(pi->win);
2678 /* Sets the pinboard_font global from the option. Doesn't do anything else. */
2679 static void update_pinboard_font(void)
2681 if (pinboard_font)
2682 pango_font_description_free(pinboard_font);
2683 pinboard_font = o_label_font.value[0] != '\0'
2684 ? pango_font_description_from_string(o_label_font.value)
2685 : NULL;
2688 static void radios_changed(Radios *radios, gpointer data)
2690 GObject *dialog = G_OBJECT(data);
2691 DropBox *drop_box;
2692 const guchar *path;
2694 g_return_if_fail(dialog != NULL);
2696 drop_box = g_object_get_data(G_OBJECT(dialog), "rox-dropbox");
2698 g_return_if_fail(radios != NULL);
2699 g_return_if_fail(drop_box != NULL);
2700 g_return_if_fail(current_pinboard != NULL);
2702 if (current_pinboard->backdrop_style != BACKDROP_PROGRAM)
2704 path = drop_box_get_path(drop_box);
2705 if (path)
2706 pinboard_set_backdrop(path, radios_get_value(radios));
2710 static void update_radios(GtkWidget *dialog)
2712 Radios *radios;
2713 GtkWidget *hbox;
2715 g_return_if_fail(dialog != NULL);
2717 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
2718 hbox = g_object_get_data(G_OBJECT(dialog), "rox-radios-hbox");
2720 g_return_if_fail(current_pinboard != NULL);
2721 g_return_if_fail(radios != NULL);
2722 g_return_if_fail(hbox != NULL);
2724 switch (current_pinboard->backdrop_style)
2726 case BACKDROP_TILE:
2727 case BACKDROP_STRETCH:
2728 case BACKDROP_SCALE:
2729 case BACKDROP_CENTRE:
2730 radios_set_value(radios,
2731 current_pinboard->backdrop_style);
2732 gtk_widget_set_sensitive(hbox, TRUE);
2733 break;
2734 default:
2735 gtk_widget_set_sensitive(hbox, FALSE);
2736 radios_set_value(radios, BACKDROP_TILE);
2737 break;