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"
57 /* Used to index the 'radios' array... */
60 #define SET_PATH 0 /* Store in globicons */
61 #define SET_COPY 3 /* Create .DirIcon */
63 static GHashTable
*glob_icons
= NULL
; /* Pathname -> Icon pathname */
65 /* Static prototypes */
66 static const char *process_globicons_line(gchar
*line
);
67 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
);
68 static void get_path_set_icon(GtkWidget
*dialog
);
69 static void show_icon_help(gpointer data
);
70 static void write_globicons(void);
71 static void show_current_dirs_menu(GtkWidget
*button
, gpointer data
);
72 static void add_globicon(const gchar
*path
, const gchar
*icon
);
73 static void drag_icon_dropped(GtkWidget
*frame
,
74 GdkDragContext
*context
,
77 GtkSelectionData
*selection_data
,
81 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
83 static void delete_globicon(const gchar
*path
);
84 static gboolean
convert_to_png(const gchar
*src
, const gchar
*dest
);
86 /****************************************************************
87 * EXTERNAL INTERFACE *
88 ****************************************************************/
90 /* Read glob-pattern -> icon mappings from "globicons" in Choices */
93 static time_t last_read
= (time_t) 0;
99 glob_icons
= g_hash_table_new(g_str_hash
, g_str_equal
);
101 path
= choices_find_path_load("globicons", PROJECT
);
103 return; /* Nothing to load */
105 if (mc_stat(path
, &info
) == -1)
108 if (info
.st_mtime
<= last_read
)
109 goto out
; /* File hasn't been modified since we last read it */
111 g_hash_table_foreach_remove(glob_icons
, free_globicon
, NULL
);
113 doc
= xmlParseFile(path
);
116 xmlNodePtr node
, icon
, root
;
119 root
= xmlDocGetRootElement(doc
);
121 /* Handle the new XML file format */
122 for (node
= root
->xmlChildrenNode
; node
; node
= node
->next
)
124 gchar
*path
, *icon_path
;
126 if (node
->type
!= XML_ELEMENT_NODE
)
128 if (strcmp(node
->name
, "rule") != 0)
130 icon
= get_subnode(node
, NULL
, "icon");
133 match
= xmlGetProp(node
, "match");
137 icon_path
= xmlNodeGetContent(icon
);
138 path
= expand_path(match
);
139 g_hash_table_insert(glob_icons
, path
, icon_path
);
147 /* Handle the old non-XML format */
148 parse_file(path
, process_globicons_line
);
149 if (g_hash_table_size(glob_icons
))
150 write_globicons(); /* Upgrade to new format */
153 last_read
= time(NULL
); /* Update time stamp */
158 /* Set an item's image field according to the globicons patterns if
159 * it matches one of them and the file exists.
161 void check_globicon(const guchar
*path
, DirItem
*item
)
165 g_return_if_fail(item
&& !item
->image
);
167 gi
= g_hash_table_lookup(glob_icons
, path
);
169 item
->image
= g_fscache_lookup(pixmap_cache
, gi
);
172 gboolean
create_diricon(const guchar
*filepath
, const guchar
*iconpath
)
174 if (!convert_to_png(iconpath
, make_path(filepath
, ".DirIcon")->str
))
177 dir_check_this(filepath
);
182 /* Add a globicon mapping for the given file to the given icon path */
183 gboolean
set_icon_path(const guchar
*filepath
, const guchar
*iconpath
)
188 /* Check if file exists */
189 if (mc_stat(iconpath
, &icon
))
191 delayed_error(_("The pathname you gave does not exist. "
192 "The icon has not been changed."));
196 /* Check if we can load the image, warn the user if not. */
197 pic
= g_fscache_lookup(pixmap_cache
, iconpath
);
201 _("Unable to load image file -- maybe it's not in a "
202 "format I understand, or maybe the permissions are "
204 "The icon has not been changed."));
209 /* Add the globicon mapping and update visible icons */
210 add_globicon(filepath
, iconpath
);
215 static void dialog_response(GtkWidget
*dialog
, gint response
, gpointer data
)
217 if (response
== GTK_RESPONSE_OK
)
218 get_path_set_icon(dialog
);
219 else if (response
== GTK_RESPONSE_CANCEL
)
220 gtk_widget_destroy(dialog
);
221 else if (response
== DELETE_ICON
)
226 path
= g_object_get_data(G_OBJECT(dialog
), "pathname");
227 g_return_if_fail(path
!= NULL
);
229 delete_globicon(path
);
231 icon_path
= make_path(path
, ".DirIcon")->str
;
232 if (access(icon_path
, F_OK
) == 0)
236 list
= g_list_prepend(NULL
, icon_path
);
241 gtk_widget_destroy(dialog
);
245 /* Display a dialog box allowing the user to set the icon for
246 * a file or directory.
248 void icon_set_handler_dialog(DirItem
*item
, const guchar
*path
)
253 GtkWidget
*frame
, *hbox
, *vbox2
;
254 GtkWidget
*entry
, *label
, *button
, *align
, *icon
;
256 GtkRadioButton
*group
;
257 GtkTargetEntry targets
[] = {
258 {"text/uri-list", 0, TARGET_URI_LIST
},
262 g_return_if_fail(item
!= NULL
&& path
!= NULL
);
264 gi
= g_hash_table_lookup(glob_icons
, path
);
266 dialog
= GTK_DIALOG(gtk_dialog_new());
267 gtk_dialog_set_has_separator(dialog
, FALSE
);
268 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_MOUSE
);
269 g_object_set_data_full(G_OBJECT(dialog
), "pathname",
270 strdup(path
), g_free
);
272 gtk_window_set_title(GTK_WINDOW(dialog
), _("Set icon"));
274 radio
= g_new(GtkWidget
*, 4);
276 tmp
= g_strdup_printf(_("Set icon for all `%s/<anything>'"),
277 item
->mime_type
->media_type
);
278 radio
[SET_MEDIA
] = gtk_radio_button_new_with_label(NULL
, tmp
);
279 gtk_box_pack_start(GTK_BOX(dialog
->vbox
),
280 radio
[SET_MEDIA
], FALSE
, TRUE
, 0);
282 group
= GTK_RADIO_BUTTON(radio
[SET_MEDIA
]);
284 tmp
= g_strdup_printf(_("Only for the type `%s/%s'"),
285 item
->mime_type
->media_type
,
286 item
->mime_type
->subtype
);
287 radio
[SET_TYPE
] = gtk_radio_button_new_with_label_from_widget(group
,
289 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), radio
[SET_TYPE
],
293 tmp
= g_strdup_printf(_("Only for the file `%s'"), path
);
294 radio
[SET_PATH
] = gtk_radio_button_new_with_label_from_widget(group
,
296 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), radio
[SET_PATH
],
298 gtk_tooltips_set_tip(tooltips
, radio
[SET_PATH
],
299 _("Add the file and image filenames to your "
300 "personal list. The setting will be lost if the image "
301 "or the file is moved."), NULL
);
303 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio
[SET_PATH
]), TRUE
);
305 /* If it's a directory, offer to create a .DirIcon */
306 if (mc_stat(path
, &info
) == 0 && S_ISDIR(info
.st_mode
))
308 radio
[3] = gtk_radio_button_new_with_label_from_widget(
309 group
, _("Copy image into directory"));
310 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), radio
[SET_COPY
],
312 gtk_tooltips_set_tip(tooltips
, radio
[SET_COPY
],
313 _("Copy the image inside the directory, as "
314 "a hidden file called '.DirIcon'. "
315 "All users will then see the "
316 "icon, and you can move the directory around safely. "
317 "This is usually the best option if you can write to "
318 "the directory."), NULL
);
320 if (access(path
, W_OK
) == 0)
321 gtk_toggle_button_set_active(
322 GTK_TOGGLE_BUTTON(radio
[SET_COPY
]),
326 radio
[SET_COPY
] = NULL
;
328 g_object_set_data_full(G_OBJECT(dialog
), "radios", radio
, g_free
);
329 g_object_set_data(G_OBJECT(dialog
), "mime_type", item
->mime_type
);
331 frame
= gtk_frame_new(NULL
);
332 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), frame
, TRUE
, TRUE
, 4);
333 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
334 gtk_container_set_border_width(GTK_CONTAINER(frame
), 4);
336 gtk_drag_dest_set(frame
, GTK_DEST_DEFAULT_ALL
,
337 targets
, sizeof(targets
) / sizeof(*targets
),
339 g_signal_connect(frame
, "drag_data_received",
340 G_CALLBACK(drag_icon_dropped
), dialog
);
342 vbox2
= gtk_vbox_new(FALSE
, 0);
343 gtk_container_add(GTK_CONTAINER(frame
), vbox2
);
345 label
= gtk_label_new(_("Drop an icon file here"));
346 gtk_misc_set_padding(GTK_MISC(label
), 10, 10);
347 gtk_box_pack_start(GTK_BOX(vbox2
), label
, TRUE
, TRUE
, 0);
348 align
= gtk_alignment_new(1, 1, 0, 0);
349 gtk_box_pack_start(GTK_BOX(vbox2
), align
, FALSE
, TRUE
, 0);
350 button
= gtk_button_new();
351 gtk_container_add(GTK_CONTAINER(align
), button
);
352 icon
= gtk_image_new_from_pixbuf(im_dirs
->pixbuf
);
353 gtk_container_add(GTK_CONTAINER(button
), icon
);
354 gtk_tooltips_set_tip(tooltips
, button
,
355 _("Menu of directories previously used for icons"),
357 g_signal_connect(button
, "clicked",
358 G_CALLBACK(show_current_dirs_menu
), NULL
);
360 hbox
= gtk_hbox_new(FALSE
, 4);
361 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), hbox
, FALSE
, TRUE
, 4);
362 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
363 gtk_box_pack_start(GTK_BOX(hbox
), gtk_label_new(_("OR")),
365 gtk_box_pack_start(GTK_BOX(hbox
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
367 hbox
= gtk_hbox_new(FALSE
, 4);
368 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), hbox
, FALSE
, TRUE
, 0);
370 label
= gtk_label_new(_("Enter the path of an icon file:")),
371 gtk_misc_set_alignment(GTK_MISC(label
), 0, .5);
372 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 4);
374 gtk_box_pack_start(GTK_BOX(hbox
),
375 new_help_button(show_icon_help
, NULL
), FALSE
, TRUE
, 0);
377 entry
= gtk_entry_new();
378 /* Set the current icon as the default text if there is one */
380 gtk_entry_set_text(GTK_ENTRY(entry
), gi
);
382 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), entry
, FALSE
, TRUE
, 0);
383 gtk_widget_grab_focus(entry
);
384 g_object_set_data(G_OBJECT(dialog
), "icon_path", entry
);
385 gtk_entry_set_activates_default(GTK_ENTRY(entry
), TRUE
);
387 button
= button_new_mixed(GTK_STOCK_DELETE
, _("_Remove"));
388 GTK_WIDGET_SET_FLAGS(button
, GTK_CAN_DEFAULT
);
389 gtk_dialog_add_action_widget(dialog
, button
, DELETE_ICON
);
390 gtk_dialog_add_buttons(dialog
,
391 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
392 GTK_STOCK_OK
, GTK_RESPONSE_OK
,
394 g_signal_connect(dialog
, "response", G_CALLBACK(dialog_response
), NULL
);
395 gtk_dialog_set_default_response(dialog
, GTK_RESPONSE_OK
);
397 gtk_widget_show_all(GTK_WIDGET(dialog
));
401 /****************************************************************
402 * INTERNAL FUNCTIONS *
403 ****************************************************************/
405 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
)
410 return TRUE
; /* For g_hash_table_foreach_remove() */
413 static void write_globicon(gpointer key
, gpointer value
, gpointer data
)
415 xmlNodePtr doc
= (xmlNodePtr
) data
;
418 tree
= xmlNewTextChild(doc
, NULL
, "rule", NULL
);
419 xmlSetProp(tree
, "match", key
);
420 xmlNewChild(tree
, NULL
, "icon", value
);
423 /* Write globicons file */
424 static void write_globicons(void)
426 gchar
*save
= NULL
, *save_new
= NULL
;
427 xmlDocPtr doc
= NULL
;
429 save
= choices_find_path_save("globicons", PROJECT
, TRUE
);
432 return; /* Saving is disabled */
434 save_new
= g_strconcat(save
, ".new", NULL
);
436 doc
= xmlNewDoc("1.0");
437 xmlDocSetRootElement(doc
,
438 xmlNewDocNode(doc
, NULL
, "special-files", NULL
));
440 g_hash_table_foreach(glob_icons
, write_globicon
,
441 xmlDocGetRootElement(doc
));
443 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
444 delayed_error(_("Error saving %s: %s"),
445 save
, g_strerror(errno
));
454 /* Process a globicon line. Format:
455 glob-pattern icon-path
457 /home/<*>/Mail /usr/local/icons/mailbox.xpm
458 (<*> represents a single asterisk, enclosed in brackets to not break
461 static const char *process_globicons_line(gchar
*line
)
463 guchar
*pattern
, *iconpath
;
465 pattern
= strtok(line
, " \t");
466 /* We ignore empty lines, but they are no cause for a message */
470 iconpath
= strtok(NULL
, " \t");
472 /* If there is no icon, then we worry */
473 g_return_val_if_fail(iconpath
!= NULL
,
474 "Invalid line in globicons: no icon specified");
476 g_hash_table_insert(glob_icons
, g_strdup(pattern
), g_strdup(iconpath
));
481 /* Add a globicon entry to the list. If another one with the same
482 * path exists, it is replaced. Otherwise, the new entry is
483 * added to the top of the list (so that it takes precedence over
486 static void add_globicon(const gchar
*path
, const gchar
*icon
)
488 g_hash_table_insert(glob_icons
, g_strdup(path
), g_strdup(icon
));
490 /* Rewrite the globicons file */
493 /* Make sure any visible icons for the file are updated */
497 /* Remove the globicon for a certain path */
498 static void delete_globicon(const gchar
*path
)
502 if (!g_hash_table_lookup_extended(glob_icons
, path
, &key
, &value
))
505 g_hash_table_remove(glob_icons
, path
);
514 /* Set the icon for this dialog's file to 'icon' */
515 static void do_set_icon(GtkWidget
*dialog
, const gchar
*icon
)
518 GtkToggleButton
**radio
;
520 path
= g_object_get_data(G_OBJECT(dialog
), "pathname");
521 g_return_if_fail(path
!= NULL
);
523 radio
= g_object_get_data(G_OBJECT(dialog
), "radios");
524 g_return_if_fail(radio
!= NULL
);
526 if (gtk_toggle_button_get_active(radio
[0]))
528 if (!set_icon_path(path
, icon
))
531 else if (radio
[SET_COPY
] &&
532 gtk_toggle_button_get_active(radio
[SET_COPY
]))
534 if (!create_diricon(path
, icon
))
542 type
= g_object_get_data(G_OBJECT(dialog
), "mime_type");
543 just_media
= gtk_toggle_button_get_active(radio
[2]);
545 if (!set_icon_for_type(type
, icon
, just_media
))
549 destroy_on_idle(dialog
);
552 /* Called when a URI list is dropped onto the box in the Set Icon
553 * dialog. Make that the default icon.
555 static void drag_icon_dropped(GtkWidget
*frame
,
556 GdkDragContext
*context
,
559 GtkSelectionData
*selection_data
,
567 if (!selection_data
->data
)
570 uris
= uri_list_to_glist(selection_data
->data
);
572 if (g_list_length(uris
) == 1)
573 icon
= g_strdup(get_local_path((guchar
*) uris
->data
));
575 for (next
= uris
; next
; next
= next
->next
)
581 delayed_error(_("You should drop a single local icon file "
582 "onto the drop box - that icon will be "
583 "used for this file from now on."));
587 do_set_icon(dialog
, icon
);
592 /* Called if the user clicks on the OK button on the Set Icon dialog */
593 static void get_path_set_icon(GtkWidget
*dialog
)
598 entry
= g_object_get_data(G_OBJECT(dialog
), "icon_path");
599 g_return_if_fail(entry
!= NULL
);
601 icon
= gtk_entry_get_text(entry
);
603 do_set_icon(dialog
, icon
);
606 static void show_icon_help(gpointer data
)
609 _("Enter the full path of a file that contains a valid "
610 "image to be used as the icon for this file or directory."));
613 /* Set the icon for the given MIME type. We copy the file. */
614 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
621 leaf
= g_strconcat(type
->media_type
, ".png", NULL
);
623 leaf
= g_strconcat(type
->media_type
, "_", type
->subtype
,
626 target
= choices_find_path_save(leaf
, "MIME-icons", TRUE
);
631 delayed_error(_("Setting icon disabled by CHOICESPATH"));
635 if (!convert_to_png(iconpath
, target
))
646 static void get_dir(gpointer key
, gpointer value
, gpointer data
)
648 GHashTable
*names
= (GHashTable
*) data
;
651 dir
= g_dirname(value
); /* Freed in add_dir_to_menu */
654 g_hash_table_insert(names
, dir
, NULL
);
658 static void open_icon_dir(GtkMenuItem
*item
, gpointer data
)
663 dir
= gtk_label_get_text(GTK_LABEL(GTK_BIN(item
)->child
));
664 filer
= filer_opendir(dir
, NULL
);
666 display_set_thumbs(filer
, TRUE
);
669 static void add_dir_to_menu(gpointer key
, gpointer value
, gpointer data
)
671 GtkMenuShell
*menu
= (GtkMenuShell
*) data
;
674 item
= gtk_menu_item_new_with_label(key
);
675 gtk_widget_set_accel_path(item
, NULL
, NULL
); /* XXX */
676 g_signal_connect(item
, "activate",
677 G_CALLBACK(open_icon_dir
), NULL
);
679 gtk_menu_shell_append(menu
, item
);
682 static void show_current_dirs_menu(GtkWidget
*button
, gpointer data
)
687 names
= g_hash_table_new(g_str_hash
, g_str_equal
);
689 g_hash_table_foreach(glob_icons
, get_dir
, names
);
690 if (g_hash_table_size(glob_icons
) == 0)
692 /* TODO: Include MIME-icons? */
693 delayed_error(_("You have not yet set any special icons; "
694 "therefore, I have no directories to show you"));
698 menu
= gtk_menu_new();
700 g_hash_table_foreach(names
, add_dir_to_menu
, menu
);
702 g_hash_table_destroy(names
);
704 show_popup_menu(menu
, gtk_get_current_event(), 0);
707 /* Load image 'src', and save it as an icon-sized image in png format.
708 * TRUE on success, error is already reported on failure.
710 static gboolean
convert_to_png(const gchar
*src
, const gchar
*dest
)
713 GError
*error
= NULL
;
715 pic
= g_fscache_lookup(pixmap_cache
, src
);
719 _("Unable to load image file -- maybe it's not in a "
720 "format I understand, or maybe the permissions are "
722 "The icon has not been changed."));
726 gdk_pixbuf_save(pic
->src_pixbuf
, dest
,
728 "tEXt::Software", PROJECT
,
734 delayed_error(_("Error creating image '%s':\n%s"),
735 dest
, error
->message
);