4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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)
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
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 background */
31 #include <gtk/gtkinvisible.h>
38 #include "gui_support.h"
41 /* The number of pixels between the bottom of the image and the top
46 /* The size of the border around the icon which is used when winking */
50 GtkWidget
*win
, *paper
;
58 guchar
*name
; /* Leaf name */
62 extern int collection_menu_button
;
64 static Pinboard
*current_pinboard
= NULL
;
65 static gint loading_pinboard
= 0; /* Non-zero => loading */
67 static PinIcon
*current_wink_icon
= NULL
;
68 static gint wink_timeout
;
70 static GdkColor mask_solid
= {1, 1, 1, 1};
71 static GdkColor mask_transp
= {0, 0, 0, 0};
72 static GdkGC
*mask_gc
= NULL
;
74 /* Proxy window for DnD and clicks on the desktop */
75 static GtkWidget
*proxy_invisible
;
77 static gint drag_start_x
, drag_start_y
;
79 /* Static prototypes */
80 static void set_size_and_shape(PinIcon
*icon
, int *rwidth
, int *rheight
);
81 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*icon
);
82 static void mask_wink_border(PinIcon
*icon
, GdkColor
*alpha
);
83 static gint
end_wink(gpointer data
);
84 static gboolean
button_release_event(GtkWidget
*widget
,
85 GdkEventButton
*event
,
87 static gboolean
button_press_event(GtkWidget
*widget
,
88 GdkEventButton
*event
,
90 static gint
icon_motion_notify(GtkWidget
*widget
,
91 GdkEventMotion
*event
,
93 static char *pin_from_file(guchar
*line
);
94 static gboolean
add_root_handlers(void);
95 static void pinboard_save(void);
96 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
99 static void icon_destroyed(GtkWidget
*widget
, PinIcon
*icon
);
100 static void snap_to_grid(int *x
, int *y
);
101 static void offset_from_centre(PinIcon
*icon
,
102 int width
, int height
,
105 /****************************************************************
106 * EXTERNAL INTERFACE *
107 ****************************************************************/
109 /* Load 'pb_<pinboard>' config file from Choices (if it exists)
110 * and make it the current pinboard.
111 * Any existing pinned items are removed. You must call this
112 * at least once before using the pinboard. NULL disables the
115 void pinboard_activate(guchar
*name
)
117 Pinboard
*old_board
= current_pinboard
;
118 guchar
*path
, *slash
;
128 if (number_of_windows
< 1)
135 if (!proxy_invisible
)
137 proxy_invisible
= gtk_invisible_new();
138 gtk_widget_show(proxy_invisible
);
139 gdk_window_add_filter(proxy_invisible
->window
,
141 gdk_window_add_filter(GDK_ROOT_PARENT(),
145 if (!add_root_handlers())
147 delayed_error(PROJECT
, _("Another application is already "
148 "managing the pinboard!"));
152 slash
= strchr(name
, '/');
154 path
= g_strdup(name
);
159 leaf
= g_strconcat("pb_", name
, NULL
);
160 path
= choices_find_path_load(leaf
, "ROX-Filer");
164 current_pinboard
= g_new(Pinboard
, 1);
165 current_pinboard
->name
= g_strdup(name
);
166 current_pinboard
->icons
= NULL
;
171 parse_file(path
, pin_from_file
);
177 /* Add a new icon to the background.
178 * 'path' should be an absolute pathname.
179 * 'x' and 'y' are the coordinates of the point in the middle of the text.
180 * 'name' is the name to use. If NULL then the leafname of path is used.
182 void pinboard_pin(guchar
*path
, guchar
*name
, int x
, int y
)
188 g_return_if_fail(path
!= NULL
);
189 g_return_if_fail(current_pinboard
!= NULL
);
191 path_len
= strlen(path
);
192 while (path_len
> 0 && path
[path_len
- 1] == '/')
195 icon
= g_new(PinIcon
, 1);
196 icon
->path
= g_strndup(path
, path_len
);
198 snap_to_grid(&x
, &y
);
202 dir_stat(icon
->path
, &icon
->item
);
206 name
= strrchr(icon
->path
, '/');
210 name
= icon
->path
; /* Shouldn't happen */
213 icon
->item
.leafname
= g_strdup(name
);
215 icon
->win
= gtk_window_new(GTK_WINDOW_DIALOG
);
216 gtk_window_set_wmclass(GTK_WINDOW(icon
->win
), "ROX-Pinboard", PROJECT
);
218 icon
->paper
= gtk_drawing_area_new();
219 gtk_container_add(GTK_CONTAINER(icon
->win
), icon
->paper
);
221 gtk_widget_realize(icon
->win
);
222 gdk_window_set_decorations(icon
->win
->window
, 0);
223 gdk_window_set_functions(icon
->win
->window
, 0);
225 set_size_and_shape(icon
, &width
, &height
);
226 offset_from_centre(icon
, width
, height
, &x
, &y
);
227 gtk_widget_set_uposition(icon
->win
, x
, y
);
229 gtk_widget_add_events(icon
->paper
,
230 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
231 GDK_BUTTON2_MOTION_MASK
| GDK_BUTTON3_MOTION_MASK
);
232 gtk_signal_connect(GTK_OBJECT(icon
->paper
), "button-press-event",
233 GTK_SIGNAL_FUNC(button_press_event
), icon
);
234 gtk_signal_connect(GTK_OBJECT(icon
->paper
), "button-release-event",
235 GTK_SIGNAL_FUNC(button_release_event
), icon
);
236 gtk_signal_connect(GTK_OBJECT(icon
->paper
), "motion-notify-event",
237 GTK_SIGNAL_FUNC(icon_motion_notify
), icon
);
238 gtk_signal_connect(GTK_OBJECT(icon
->paper
), "expose-event",
239 GTK_SIGNAL_FUNC(draw_icon
), icon
);
240 gtk_signal_connect(GTK_OBJECT(icon
->win
), "destroy",
241 GTK_SIGNAL_FUNC(icon_destroyed
), icon
);
243 current_pinboard
->icons
= g_list_prepend(current_pinboard
->icons
,
245 gtk_widget_show_all(icon
->win
);
247 if (!loading_pinboard
)
251 /* Remove an icon from the pinboard */
252 void pinboard_unpin(PinIcon
*icon
)
254 g_return_if_fail(icon
!= NULL
);
256 gtk_widget_destroy(icon
->win
);
260 /* Put a border around the icon, briefly.
261 * If icon is NULL then cancel any existing wink.
263 void pinboard_wink_item(PinIcon
*icon
)
265 if (current_wink_icon
)
267 mask_wink_border(current_wink_icon
, &mask_transp
);
268 gtk_timeout_remove(wink_timeout
);
271 current_wink_icon
= icon
;
273 if (current_wink_icon
)
275 mask_wink_border(current_wink_icon
, &mask_solid
);
276 wink_timeout
= gtk_timeout_add(300, end_wink
, NULL
);
280 /* Remove everything on the current pinboard and disables the pinboard.
281 * Does not change any files. Does not change number_of_windows.
283 void pinboard_clear(void)
285 g_return_if_fail(current_pinboard
!= NULL
);
286 g_return_if_fail(current_pinboard
->icons
= NULL
); /* XXX */
288 g_free(current_pinboard
->name
);
289 g_free(current_pinboard
);
290 current_pinboard
= NULL
;
291 /* TODO: Remove handlers */
294 /****************************************************************
295 * INTERNAL FUNCTIONS *
296 ****************************************************************/
298 static gint
end_wink(gpointer data
)
300 pinboard_wink_item(NULL
);
304 /* Make the wink border solid or transparent */
305 static void mask_wink_border(PinIcon
*icon
, GdkColor
*alpha
)
309 gdk_window_get_size(icon
->paper
->window
, &width
, &height
);
311 gdk_gc_set_foreground(mask_gc
, alpha
);
312 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
313 0, 0, width
- 1, height
- 1);
314 gdk_draw_rectangle(icon
->mask
, mask_gc
, FALSE
,
315 1, 1, width
- 3, height
- 3);
317 gtk_widget_shape_combine_mask(icon
->win
, icon
->mask
, 0, 0);
319 gtk_widget_draw(icon
->paper
, NULL
);
322 /* Updates the name_width field and resizes and masks the window.
323 * Returns the new width and height.
325 static void set_size_and_shape(PinIcon
*icon
, int *rwidth
, int *rheight
)
328 GdkFont
*font
= icon
->win
->style
->font
;
331 MaskedPixmap
*image
= icon
->item
.image
;
333 font_height
= font
->ascent
+ font
->descent
;
334 icon
->item
.name_width
= gdk_string_width(font
, icon
->item
.leafname
);
336 width
= MAX(image
->width
, icon
->item
.name_width
+ 2) +
338 height
= image
->height
+ GAP
+ (font_height
+ 2) + 2 * WINK_FRAME
;
339 gtk_widget_set_usize(icon
->win
, width
, height
);
341 mask
= gdk_pixmap_new(icon
->win
->window
, width
, height
, 1);
343 mask_gc
= gdk_gc_new(mask
);
345 /* Clear the mask to transparent */
346 gdk_gc_set_foreground(mask_gc
, &mask_transp
);
347 gdk_draw_rectangle(mask
, mask_gc
, TRUE
, 0, 0, width
, height
);
349 gdk_gc_set_foreground(mask_gc
, &mask_solid
);
350 gdk_draw_pixmap(mask
, mask_gc
, image
->mask
,
352 (width
- image
->width
) >> 1,
357 /* Mask off an area for the text */
358 gdk_draw_rectangle(mask
, mask_gc
, TRUE
,
359 (width
- (icon
->item
.name_width
+ 2)) >> 1,
360 WINK_FRAME
+ image
->height
+ GAP
,
361 icon
->item
.name_width
+ 2, font_height
+ 2);
363 gtk_widget_shape_combine_mask(icon
->win
, mask
, 0, 0);
366 gdk_pixmap_unref(icon
->mask
);
373 static gint
draw_icon(GtkWidget
*widget
, GdkEventExpose
*event
, PinIcon
*icon
)
375 GdkFont
*font
= icon
->win
->style
->font
;
379 MaskedPixmap
*image
= icon
->item
.image
;
381 font_height
= font
->ascent
+ font
->descent
;
383 gdk_window_get_size(widget
->window
, &width
, &height
);
385 /* TODO: If the shape extension is missing we might need to set
386 * the clip mask here...
388 gdk_draw_pixmap(widget
->window
, widget
->style
->black_gc
,
391 (width
- image
->width
) >> 1,
396 text_x
= (width
- icon
->item
.name_width
) >> 1;
397 text_y
= WINK_FRAME
+ image
->height
+ GAP
+ 1;
399 gtk_paint_flat_box(widget
->style
, widget
->window
,
400 GTK_STATE_NORMAL
, GTK_SHADOW_NONE
,
401 NULL
, widget
, "text",
404 icon
->item
.name_width
+ 2,
407 gtk_paint_string(widget
->style
, widget
->window
,
409 NULL
, widget
, "text",
411 text_y
+ font
->ascent
,
412 icon
->item
.leafname
);
414 if (current_wink_icon
== icon
)
416 gdk_draw_rectangle(icon
->paper
->window
,
417 icon
->paper
->style
->white_gc
,
419 0, 0, width
- 1, height
- 1);
420 gdk_draw_rectangle(icon
->paper
->window
,
421 icon
->paper
->style
->black_gc
,
423 1, 1, width
- 3, height
- 3);
429 static gboolean
button_release_event(GtkWidget
*widget
,
430 GdkEventButton
*event
,
433 int b
= event
->button
;
435 if (b
== 1 || b
> 3 || b
== collection_menu_button
)
438 g_print("[ stop drag ]\n");
440 gtk_grab_remove(widget
);
447 static gboolean
button_press_event(GtkWidget
*widget
,
448 GdkEventButton
*event
,
451 int b
= event
->button
;
453 if (b
== collection_menu_button
)
454 pinboard_unpin(icon
);
457 pinboard_wink_item(icon
);
459 run_diritem(icon
->path
, &icon
->item
, NULL
,
460 (event
->state
& GDK_SHIFT_MASK
) != 0);
464 drag_start_x
= event
->x_root
;
465 drag_start_y
= event
->y_root
;
466 g_print("[ start drag ]\n");
467 gtk_grab_add(widget
);
473 /* An icon is being dragged around... */
474 static gint
icon_motion_notify(GtkWidget
*widget
,
475 GdkEventMotion
*event
,
478 int x
= event
->x_root
;
479 int y
= event
->y_root
;
482 snap_to_grid(&x
, &y
);
485 gdk_window_get_size(icon
->win
->window
, &width
, &height
);
486 offset_from_centre(icon
, width
, height
, &x
, &y
);
488 gdk_window_move(icon
->win
->window
, x
, y
);
493 /* Called for each line in the pinboard file while loading a new board */
494 static char *pin_from_file(guchar
*line
)
498 if (sscanf(line
, " %d , %d , %n", &x
, &y
, &n
) < 2)
499 return NULL
; /* Ignore format errors */
501 pinboard_pin(line
+ n
, NULL
, x
, y
);
506 /* Make sure that clicks and drops on the root window come to us...
507 * False if an error occurred (ie, someone else is using it).
509 static gboolean
add_root_handlers(void)
513 root
= gdk_window_lookup(GDK_ROOT_WINDOW());
515 root
= gdk_window_foreign_new(GDK_ROOT_WINDOW());
517 if (!setup_xdnd_proxy(GDK_ROOT_WINDOW(), proxy_invisible
->window
))
520 /* Forward events from the root window to our proxy window */
521 gdk_window_set_user_data(GDK_ROOT_PARENT(), proxy_invisible
);
523 drag_set_pinboard_dest(proxy_invisible
);
528 /* Write the current state of the pinboard to the current pinboard file */
529 static void pinboard_save(void)
536 guchar
*save_new
= NULL
;
538 g_return_if_fail(current_pinboard
!= NULL
);
540 leaf
= g_strconcat("pb_", current_pinboard
->name
, NULL
);
541 save
= choices_find_path_save(leaf
, "ROX-Filer", TRUE
);
547 save_new
= g_strconcat(save
, ".new", NULL
);
548 file
= fopen(save_new
, "wb");
552 tmp
= g_string_new(NULL
);
553 for (next
= current_pinboard
->icons
; next
; next
= next
->next
)
555 PinIcon
*icon
= (PinIcon
*) next
->data
;
557 g_string_sprintf(tmp
, "%d, %d, %s\n",
558 icon
->x
, icon
->y
, icon
->path
);
559 if (fwrite(tmp
->str
, 1, tmp
->len
, file
) < tmp
->len
)
563 g_string_free(tmp
, TRUE
);
573 if (rename(save_new
, save
))
578 delayed_error(_("Error saving pinboard"), g_strerror(errno
));
586 * Filter that translates proxied events from virtual root windows into normal
587 * Gdk events for the proxy_invisible widget. Stolen from gmc.
589 static GdkFilterReturn
proxy_filter(GdkXEvent
*xevent
,
594 GdkWindow
*proxy
= proxy_invisible
->window
;
601 /* Translate button events into events that come from
602 * the proxy window, so that we can catch them as a
603 * signal from the invisible widget.
605 if (xev
->type
== ButtonPress
)
606 event
->button
.type
= GDK_BUTTON_PRESS
;
608 event
->button
.type
= GDK_BUTTON_RELEASE
;
610 gdk_window_ref(proxy
);
612 event
->button
.window
= proxy
;
613 event
->button
.send_event
= xev
->xbutton
.send_event
;
614 event
->button
.time
= xev
->xbutton
.time
;
615 event
->button
.x
= xev
->xbutton
.x
;
616 event
->button
.y
= xev
->xbutton
.y
;
617 event
->button
.state
= xev
->xbutton
.state
;
618 event
->button
.button
= xev
->xbutton
.button
;
620 return GDK_FILTER_TRANSLATE
;
623 /* XXX: I have no idea why this helps, but it does! */
624 /* The proxy window was destroyed (i.e. the window
625 * manager died), so we have to cope with it
627 if (((GdkEventAny
*) event
)->window
== proxy
)
628 gdk_window_destroy_notify(proxy
);
630 return GDK_FILTER_REMOVE
;
636 return GDK_FILTER_CONTINUE
;
639 /* Does not save the new state */
640 static void icon_destroyed(GtkWidget
*widget
, PinIcon
*icon
)
642 g_print("[ icon '%s' destroyed! ]\n", icon
->item
.leafname
);
644 gdk_pixmap_unref(icon
->mask
);
645 dir_item_clear(&icon
->item
);
649 if (current_pinboard
)
650 current_pinboard
->icons
=
651 g_list_remove(current_pinboard
->icons
, icon
);
656 static void snap_to_grid(int *x
, int *y
)
658 *x
= ((*x
+ GRID_STEP
/ 2) / GRID_STEP
) * GRID_STEP
;
659 *y
= ((*y
+ GRID_STEP
/ 2) / GRID_STEP
) * GRID_STEP
;
662 /* Convert (x,y) from a centre point to a window position */
663 static void offset_from_centre(PinIcon
*icon
,
664 int width
, int height
,
668 *y
-= height
- (icon
->paper
->style
->font
->descent
>> 1);
669 *x
= CLAMP(*x
, 0, screen_width
- width
);
670 *y
= CLAMP(*y
, 0, screen_height
- height
);