4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
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 /* usericons.c - handle user-defined icons. Diego Zamboni, Feb 7, 2001. */
32 #include <libxml/parser.h>
40 #include "gui_support.h"
47 #include "usericons.h"
55 static GHashTable
*glob_icons
= NULL
; /* Pathname -> Icon pathname */
57 /* Static prototypes */
58 static const char *process_globicons_line(gchar
*line
);
59 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
);
60 static void get_path_set_icon(GtkWidget
*dialog
);
61 static void show_icon_help(gpointer data
);
62 static void write_globicons(void);
63 static void show_current_dirs_menu(GtkWidget
*button
, gpointer data
);
64 static void add_globicon(const gchar
*path
, const gchar
*icon
);
65 static void drag_icon_dropped(GtkWidget
*frame
,
66 GdkDragContext
*context
,
69 GtkSelectionData
*selection_data
,
73 static void remove_icon(GtkWidget
*dialog
);
74 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
77 /****************************************************************
78 * EXTERNAL INTERFACE *
79 ****************************************************************/
81 /* Read glob-pattern -> icon mappings from "globicons" in Choices */
84 static time_t last_read
= (time_t) 0;
90 glob_icons
= g_hash_table_new(g_str_hash
, g_str_equal
);
92 path
= choices_find_path_load("globicons", PROJECT
);
94 return; /* Nothing to load */
96 if (mc_stat(path
, &info
) == -1)
99 if (info
.st_mtime
<= last_read
)
100 goto out
; /* File hasn't been modified since we last read it */
102 g_hash_table_foreach_remove(glob_icons
, free_globicon
, NULL
);
104 doc
= xmlParseFile(path
);
107 xmlNodePtr node
, icon
, root
;
110 root
= xmlDocGetRootElement(doc
);
112 /* Handle the new XML file format */
113 for (node
= root
->xmlChildrenNode
; node
; node
= node
->next
)
115 gchar
*path
, *icon_path
;
117 if (node
->type
!= XML_ELEMENT_NODE
)
119 if (strcmp(node
->name
, "rule") != 0)
121 icon
= get_subnode(node
, NULL
, "icon");
124 match
= xmlGetProp(node
, "match");
128 icon_path
= xmlNodeGetContent(icon
);
131 gchar
*loc_match
, *loc_icon
;
133 loc_match
= from_utf8(match
);
134 path
= icon_convert_path(loc_match
);
136 loc_icon
= from_utf8(icon_path
);
137 g_hash_table_insert(glob_icons
, path
, loc_icon
);
141 path
= icon_convert_path(match
);
142 g_hash_table_insert(glob_icons
, path
, icon_path
);
151 /* Handle the old non-XML format */
152 parse_file(path
, process_globicons_line
);
153 if (g_hash_table_size(glob_icons
))
154 write_globicons(); /* Upgrade to new format */
157 last_read
= time(NULL
); /* Update time stamp */
162 /* Set an item's image field according to the globicons patterns if
163 * it matches one of them and the file exists.
165 void check_globicon(const guchar
*path
, DirItem
*item
)
169 g_return_if_fail(item
&& !item
->image
);
171 gi
= g_hash_table_lookup(glob_icons
, path
);
173 item
->image
= g_fscache_lookup(pixmap_cache
, gi
);
176 /* Add a globicon mapping for the given file to the given icon path */
177 gboolean
set_icon_path(const guchar
*filepath
, const guchar
*iconpath
)
182 /* Check if file exists */
183 if (!mc_stat(iconpath
, &icon
) == 0) {
184 delayed_error(_("The pathname you gave does not exist. "
185 "The icon has not been changed."));
189 /* Check if we can load the image, warn the user if not. */
190 pic
= g_fscache_lookup(pixmap_cache
, iconpath
);
194 _("Unable to load image file -- maybe it's not in a "
195 "format I understand, or maybe the permissions are "
197 "The icon has not been changed."));
200 g_fscache_data_unref(pixmap_cache
, pic
);
202 /* Add the globicon mapping and update visible icons */
203 add_globicon(filepath
, iconpath
);
208 /* Display a dialog box allowing the user to set the icon for
209 * a file or directory.
211 void icon_set_handler_dialog(DirItem
*item
, const guchar
*path
)
214 GtkWidget
*dialog
, *vbox
, *frame
, *hbox
, *vbox2
;
215 GtkWidget
*entry
, *label
, *button
, *align
, *icon
;
217 GtkTargetEntry targets
[] = {
218 {"text/uri-list", 0, TARGET_URI_LIST
},
222 g_return_if_fail(item
!= NULL
&& path
!= NULL
);
224 gi
= g_hash_table_lookup(glob_icons
, path
);
226 dialog
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
227 gtk_window_set_type_hint(GTK_WINDOW(dialog
),
228 GDK_WINDOW_TYPE_HINT_DIALOG
);
229 gtk_object_set_data_full(GTK_OBJECT(dialog
),
234 gtk_window_set_title(GTK_WINDOW(dialog
), _("Set icon"));
235 gtk_container_set_border_width(GTK_CONTAINER(dialog
), 10);
237 vbox
= gtk_vbox_new(FALSE
, 4);
238 gtk_container_add(GTK_CONTAINER(dialog
), vbox
);
240 radio
= g_new(GtkWidget
*, 3);
242 tmp
= g_strdup_printf(_("Set icon for all `%s/<anything>'"),
243 item
->mime_type
->media_type
);
244 radio
[2] = gtk_radio_button_new_with_label(NULL
, tmp
);
245 gtk_box_pack_start(GTK_BOX(vbox
), radio
[2], FALSE
, TRUE
, 0);
248 tmp
= g_strdup_printf(_("Only for the type `%s/%s'"),
249 item
->mime_type
->media_type
,
250 item
->mime_type
->subtype
);
251 radio
[1] = gtk_radio_button_new_with_label_from_widget(
252 GTK_RADIO_BUTTON(radio
[2]), tmp
);
253 gtk_box_pack_start(GTK_BOX(vbox
), radio
[1], FALSE
, TRUE
, 0);
256 tmp
= g_strdup_printf(_("Only for the file `%s'"), path
);
257 radio
[0] = gtk_radio_button_new_with_label_from_widget(
258 GTK_RADIO_BUTTON(radio
[2]), tmp
);
259 gtk_box_pack_start(GTK_BOX(vbox
), radio
[0], FALSE
, TRUE
, 0);
262 gtk_object_set_data_full(GTK_OBJECT(dialog
),
263 "radios", radio
, g_free
);
264 gtk_object_set_data(GTK_OBJECT(dialog
),
265 "mime_type", item
->mime_type
);
267 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio
[0]), TRUE
);
269 frame
= gtk_frame_new(NULL
);
270 gtk_box_pack_start(GTK_BOX(vbox
), frame
, TRUE
, TRUE
, 4);
271 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
272 gtk_container_set_border_width(GTK_CONTAINER(frame
), 4);
274 gtk_drag_dest_set(frame
, GTK_DEST_DEFAULT_ALL
,
275 targets
, sizeof(targets
) / sizeof(*targets
),
277 gtk_signal_connect(GTK_OBJECT(frame
), "drag_data_received",
278 GTK_SIGNAL_FUNC(drag_icon_dropped
), dialog
);
280 vbox2
= gtk_vbox_new(FALSE
, 0);
281 gtk_container_add(GTK_CONTAINER(frame
), vbox2
);
283 label
= gtk_label_new(_("Drop an icon file here"));
284 gtk_misc_set_padding(GTK_MISC(label
), 10, 10);
285 gtk_box_pack_start(GTK_BOX(vbox2
), label
, TRUE
, TRUE
, 0);
286 align
= gtk_alignment_new(1, 1, 0, 0);
287 gtk_box_pack_start(GTK_BOX(vbox2
), align
, FALSE
, TRUE
, 0);
288 button
= gtk_button_new();
289 gtk_container_add(GTK_CONTAINER(align
), button
);
290 icon
= gtk_pixmap_new(im_dirs
->pixmap
, im_dirs
->mask
);
291 gtk_container_add(GTK_CONTAINER(button
), icon
);
292 gtk_tooltips_set_tip(tooltips
, button
,
293 _("Menu of directories previously used for icons"),
295 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
296 GTK_SIGNAL_FUNC(show_current_dirs_menu
), NULL
);
298 hbox
= gtk_hbox_new(FALSE
, 4);
299 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, TRUE
, 4);
300 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
301 gtk_box_pack_start(GTK_BOX(hbox
), gtk_label_new(_("OR")),
303 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
305 hbox
= gtk_hbox_new(FALSE
, 4);
306 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, TRUE
, 0);
308 label
= gtk_label_new(_("Enter the path of an icon file:")),
309 gtk_misc_set_alignment(GTK_MISC(label
), 0, .5);
310 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 4);
312 gtk_box_pack_start(GTK_BOX(hbox
),
313 new_help_button(show_icon_help
, NULL
), FALSE
, TRUE
, 0);
315 entry
= gtk_entry_new();
316 /* Set the current icon as the default text if there is one */
318 gtk_entry_set_text(GTK_ENTRY(entry
), gi
);
320 gtk_box_pack_start(GTK_BOX(vbox
), entry
, FALSE
, TRUE
, 0);
321 gtk_widget_grab_focus(entry
);
322 gtk_object_set_data(GTK_OBJECT(dialog
), "icon_path", entry
);
323 gtk_signal_connect_object(GTK_OBJECT(entry
), "activate",
324 GTK_SIGNAL_FUNC(get_path_set_icon
),
327 hbox
= gtk_hbox_new(FALSE
, 4);
328 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, TRUE
, 4);
329 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
330 gtk_box_pack_start(GTK_BOX(hbox
), gtk_label_new(_("OR")),
332 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
334 hbox
= gtk_hbox_new(TRUE
, 4);
335 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, TRUE
, 0);
337 button
= gtk_button_new_with_label(_("Remove custom icon"));
338 gtk_box_pack_start(GTK_BOX(hbox
), button
, TRUE
, TRUE
, 0);
339 gtk_signal_connect_object(GTK_OBJECT(button
), "clicked",
340 GTK_SIGNAL_FUNC(remove_icon
),
343 hbox
= gtk_hbox_new(TRUE
, 4);
344 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, TRUE
, 0);
346 button
= gtk_button_new_with_label(_("OK"));
347 gtk_box_pack_start(GTK_BOX(hbox
), button
, TRUE
, TRUE
, 0);
348 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
349 gtk_window_set_default(GTK_WINDOW(dialog
), button
);
350 gtk_signal_connect_object(GTK_OBJECT(button
), "clicked",
351 GTK_SIGNAL_FUNC(get_path_set_icon
),
354 button
= gtk_button_new_with_label(_("Cancel"));
355 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
356 gtk_box_pack_start(GTK_BOX(hbox
), button
, TRUE
, TRUE
, 0);
357 gtk_signal_connect_object(GTK_OBJECT(button
), "clicked",
358 GTK_SIGNAL_FUNC(gtk_widget_destroy
),
361 gtk_widget_show_all(dialog
);
365 /****************************************************************
366 * INTERNAL FUNCTIONS *
367 ****************************************************************/
369 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
)
374 return TRUE
; /* For g_hash_table_foreach_remove() */
377 static void write_globicon(gpointer key
, gpointer value
, gpointer data
)
379 xmlNodePtr doc
= (xmlNodePtr
) data
;
382 gchar
*u8_path
, *u8_icon
;
384 u8_path
= to_utf8((gchar
*) key
);
385 u8_icon
= to_utf8((gchar
*) value
);
387 tree
= xmlNewTextChild(doc
, NULL
, "rule", NULL
);
388 xmlSetProp(tree
, "match", u8_path
);
389 xmlNewChild(tree
, NULL
, "icon", u8_icon
);
394 tree
= xmlNewTextChild(doc
, NULL
, "rule", NULL
);
395 xmlSetProp(tree
, "match", key
);
396 xmlNewChild(tree
, NULL
, "icon", value
);
400 /* Write globicons file */
401 static void write_globicons(void)
403 gchar
*save
= NULL
, *save_new
= NULL
;
404 xmlDocPtr doc
= NULL
;
406 save
= choices_find_path_save("globicons", PROJECT
, TRUE
);
409 return; /* Saving is disabled */
411 save_new
= g_strconcat(save
, ".new", NULL
);
413 doc
= xmlNewDoc("1.0");
414 xmlDocSetRootElement(doc
,
415 xmlNewDocNode(doc
, NULL
, "special-files", NULL
));
417 g_hash_table_foreach(glob_icons
, write_globicon
,
418 xmlDocGetRootElement(doc
));
420 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
421 delayed_error(_("Error saving %s: %s"),
422 save
, g_strerror(errno
));
431 /* Process a globicon line. Format:
432 glob-pattern icon-path
434 /home/<*>/Mail /usr/local/icons/mailbox.xpm
435 (<*> represents a single asterisk, enclosed in brackets to not break
438 static const char *process_globicons_line(gchar
*line
)
440 guchar
*pattern
, *iconpath
;
442 pattern
= strtok(line
, " \t");
443 /* We ignore empty lines, but they are no cause for a message */
447 iconpath
= strtok(NULL
, " \t");
449 /* If there is no icon, then we worry */
450 g_return_val_if_fail(iconpath
!= NULL
,
451 "Invalid line in globicons: no icon specified");
453 g_hash_table_insert(glob_icons
, g_strdup(pattern
), g_strdup(iconpath
));
458 /* Add a globicon entry to the list. If another one with the same
459 * path exists, it is replaced. Otherwise, the new entry is
460 * added to the top of the list (so that it takes precedence over
463 static void add_globicon(const gchar
*path
, const gchar
*icon
)
465 g_hash_table_insert(glob_icons
, g_strdup(path
), g_strdup(icon
));
467 /* Rewrite the globicons file */
470 /* Make sure any visible icons for the file are updated */
474 /* Remove the globicon for a certain path */
475 static void delete_globicon(guchar
*path
)
479 if (!g_hash_table_lookup_extended(glob_icons
, path
, &key
, &value
))
482 g_hash_table_remove(glob_icons
, path
);
491 /* Set the icon for this dialog's file to 'icon' */
492 static void do_set_icon(GtkWidget
*dialog
, const gchar
*icon
)
495 GtkToggleButton
**radio
;
497 path
= gtk_object_get_data(GTK_OBJECT(dialog
), "pathname");
498 g_return_if_fail(path
!= NULL
);
500 radio
= gtk_object_get_data(GTK_OBJECT(dialog
), "radios");
501 g_return_if_fail(radio
!= NULL
);
503 if (gtk_toggle_button_get_active(radio
[0]))
505 if (!set_icon_path(path
, icon
))
513 type
= gtk_object_get_data(GTK_OBJECT(dialog
), "mime_type");
514 just_media
= gtk_toggle_button_get_active(radio
[2]);
516 if (!set_icon_for_type(type
, icon
, just_media
))
520 destroy_on_idle(dialog
);
523 /* Called when a URI list is dropped onto the box in the Set Icon
524 * dialog. Make that the default icon.
526 static void drag_icon_dropped(GtkWidget
*frame
,
527 GdkDragContext
*context
,
530 GtkSelectionData
*selection_data
,
538 if (!selection_data
->data
)
541 uris
= uri_list_to_glist(selection_data
->data
);
543 if (g_list_length(uris
) == 1)
544 icon
= g_strdup(get_local_path((guchar
*) uris
->data
));
546 for (next
= uris
; next
; next
= next
->next
)
552 delayed_error(_("You should drop a single local icon file "
553 "onto the drop box - that icon will be "
554 "used for this file from now on."));
558 do_set_icon(dialog
, icon
);
563 /* Called if the user clicks on the OK button on the Set Icon dialog */
564 static void get_path_set_icon(GtkWidget
*dialog
)
569 entry
= gtk_object_get_data(GTK_OBJECT(dialog
), "icon_path");
570 g_return_if_fail(entry
!= NULL
);
572 icon
= gtk_entry_get_text(entry
);
574 do_set_icon(dialog
, icon
);
577 /* Called if the user clicks on the "Remove custom icon" button */
578 static void remove_icon(GtkWidget
*dialog
)
582 g_return_if_fail(dialog
!= NULL
);
584 path
= gtk_object_get_data(GTK_OBJECT(dialog
), "pathname");
585 g_return_if_fail(path
!= NULL
);
587 delete_globicon(path
);
589 destroy_on_idle(dialog
);
592 static void show_icon_help(gpointer data
)
595 _("Enter the full path of a file that contains a valid "
596 "image to be used as the icon for this file or directory."));
599 /* Set the icon for the given MIME type. We copy the file. */
600 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
608 /* XXX: Should convert to XPM format... */
611 leaf
= g_strconcat(type
->media_type
, ".xpm", NULL
);
613 leaf
= g_strconcat(type
->media_type
, "_", type
->subtype
,
616 target
= choices_find_path_save(leaf
, "MIME-icons", TRUE
);
619 delayed_error(_("Setting icon disabled by CHOICESPATH"));
624 dir
= g_dirname(target
);
625 paths
= g_list_append(NULL
, (gchar
*) iconpath
);
627 action_copy(paths
, dir
, leaf
, -1);
637 static void get_dir(gpointer key
, gpointer value
, gpointer data
)
639 GHashTable
*names
= (GHashTable
*) data
;
642 dir
= g_dirname(value
); /* Freed in add_dir_to_menu */
645 g_hash_table_insert(names
, dir
, NULL
);
649 static void open_icon_dir(GtkMenuItem
*item
, gpointer data
)
654 gtk_label_get(GTK_LABEL(GTK_BIN(item
)->child
), &dir
);
655 filer
= filer_opendir(dir
, NULL
);
657 display_set_thumbs(filer
, TRUE
);
660 static void add_dir_to_menu(gpointer key
, gpointer value
, gpointer data
)
662 GtkMenuShell
*menu
= (GtkMenuShell
*) data
;
665 item
= gtk_menu_item_new_with_label(key
);
666 gtk_widget_set_accel_path(item
, NULL
, NULL
); /* XXX */
667 gtk_signal_connect(GTK_OBJECT(item
), "activate",
668 GTK_SIGNAL_FUNC(open_icon_dir
), NULL
);
670 gtk_menu_shell_append(menu
, item
);
673 static void show_current_dirs_menu(GtkWidget
*button
, gpointer data
)
678 names
= g_hash_table_new(g_str_hash
, g_str_equal
);
680 g_hash_table_foreach(glob_icons
, get_dir
, names
);
681 if (g_hash_table_size(glob_icons
) == 0)
683 /* TODO: Include MIME-icons? */
684 delayed_error(_("You have not yet set any special icons; "
685 "therefore, I have no directories to show you"));
689 menu
= gtk_menu_new();
691 g_hash_table_foreach(names
, add_dir_to_menu
, menu
);
693 g_hash_table_destroy(names
);
695 show_popup_menu(menu
, gtk_get_current_event(), 0);