4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, 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"
58 #define SET_PATH 0 /* Store in globicons */
59 #define SET_COPY 3 /* Create .DirIcon */
61 static GHashTable
*glob_icons
= NULL
; /* Pathname -> Icon pathname */
63 /* Static prototypes */
64 static const char *process_globicons_line(gchar
*line
);
65 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
);
66 static void write_globicons(void);
67 static void add_globicon(const gchar
*path
, const gchar
*icon
);
68 static void drag_icon_dropped(GtkWidget
*drop_box
,
71 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
73 static void delete_globicon(const gchar
*path
);
74 static gboolean
convert_to_png(const gchar
*src
, const gchar
*dest
);
75 static void radios_changed(gpointer data
);
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
))
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
);
129 path
= expand_path(match
);
130 g_hash_table_insert(glob_icons
, path
, icon_path
);
138 /* Handle the old non-XML format */
139 parse_file(path
, process_globicons_line
);
140 if (g_hash_table_size(glob_icons
))
141 write_globicons(); /* Upgrade to new format */
144 last_read
= time(NULL
); /* Update time stamp */
149 /* Set an item's image field according to the globicons patterns if
150 * it matches one of them and the file exists.
152 void check_globicon(const guchar
*path
, DirItem
*item
)
156 g_return_if_fail(item
&& !item
->image
);
158 gi
= g_hash_table_lookup(glob_icons
, path
);
160 item
->image
= g_fscache_lookup(pixmap_cache
, gi
);
163 static gboolean
create_diricon(const guchar
*filepath
, const guchar
*iconpath
)
165 if (!convert_to_png(iconpath
, make_path(filepath
, ".DirIcon")))
168 dir_check_this(filepath
);
173 /* Add a globicon mapping for the given file to the given icon path */
174 static gboolean
set_icon_path(const guchar
*filepath
, const guchar
*iconpath
)
178 /* Check if file exists */
179 if (!file_exists(iconpath
))
181 delayed_error(_("The pathname you gave does not exist. "
182 "The icon has not been changed."));
186 /* Check if we can load the image, warn the user if not. */
187 pic
= g_fscache_lookup(pixmap_cache
, iconpath
);
191 _("Unable to load image file -- maybe it's not in a "
192 "format I understand, or maybe the permissions are "
194 "The icon has not been changed."));
199 /* Add the globicon mapping and update visible icons */
200 add_globicon(filepath
, iconpath
);
205 static void dialog_response(GtkWidget
*dialog
, gint response
, gpointer data
)
207 gtk_widget_destroy(dialog
);
210 static void clear_icon(DropBox
*drop_box
, GObject
*dialog
)
214 radios
= g_object_get_data(G_OBJECT(dialog
), "radios");
215 g_return_if_fail(radios
!= NULL
);
217 if (radios_get_value(radios
) == SET_PATH
)
221 path
= g_object_get_data(G_OBJECT(dialog
), "pathname");
222 g_return_if_fail(path
!= NULL
);
224 delete_globicon(path
);
232 drop_box
= g_object_get_data(G_OBJECT(dialog
), "rox-dropbox");
233 g_return_if_fail(drop_box
!= NULL
);
235 path
= drop_box_get_path(drop_box
);
236 g_return_if_fail(path
!= NULL
);
238 tmp
= g_strdup_printf(_("Really delete icon '%s'?"), path
);
239 if (confirm(tmp
, GTK_STOCK_DELETE
, NULL
))
242 delayed_error(_("Can't delete '%s':\n%s"),
243 path
, g_strerror(errno
));
249 radios_changed(dialog
);
252 /* Display a dialog box allowing the user to set the icon for
253 * a file or directory.
255 void icon_set_handler_dialog(DirItem
*item
, const guchar
*path
)
262 g_return_if_fail(item
!= NULL
&& path
!= NULL
);
264 dialog
= GTK_DIALOG(gtk_dialog_new());
265 gtk_dialog_set_has_separator(dialog
, FALSE
);
266 gtk_window_set_position(GTK_WINDOW(dialog
), GTK_WIN_POS_MOUSE
);
267 g_object_set_data_full(G_OBJECT(dialog
), "pathname",
268 strdup(path
), g_free
);
270 gtk_window_set_title(GTK_WINDOW(dialog
), _("Set icon"));
272 radios
= radios_new(radios_changed
, dialog
);
274 g_object_set_data(G_OBJECT(dialog
), "radios", radios
);
275 g_object_set_data(G_OBJECT(dialog
), "mime-type", item
->mime_type
);
279 _("Use a copy of the image as the default for all "
280 "files of these MIME types."), SET_MEDIA
,
281 _("Set icon for all `%s/<anything>'"),
282 item
->mime_type
->media_type
);
286 _("Use a copy of the image for all files of this MIME "
288 _("For all files of type `%s' (%s/%s)"),
289 mime_type_comment(item
->mime_type
),
290 item
->mime_type
->media_type
,
291 item
->mime_type
->subtype
);
294 _("Add the file and image filenames to your "
295 "personal list. The setting will be lost if the image "
296 "or the file is moved."), SET_PATH
,
297 _("Only for the file `%s'"), path
);
299 radios_set_value(radios
, SET_PATH
);
301 /* If it's a directory, offer to create a .DirIcon */
302 if (mc_stat(path
, &info
) == 0 && S_ISDIR(info
.st_mode
))
305 _("Copy the image inside the directory, as "
306 "a hidden file called '.DirIcon'. "
307 "All users will then see the "
308 "icon, and you can move the directory around safely. "
309 "This is usually the best option if you can write to "
310 "the directory."), SET_COPY
,
311 _("Copy image into directory"));
312 if (access(path
, W_OK
) == 0)
313 radios_set_value(radios
, SET_COPY
);
317 frame
= drop_box_new(_("Drop an icon file here"));
318 g_object_set_data(G_OBJECT(dialog
), "rox-dropbox", frame
);
320 /* Make sure rox-dropbox is set before packing (calls changed) */
321 radios_pack(radios
, GTK_BOX(dialog
->vbox
));
322 gtk_box_pack_start(GTK_BOX(dialog
->vbox
), frame
, TRUE
, TRUE
, 4);
324 g_signal_connect(frame
, "path_dropped",
325 G_CALLBACK(drag_icon_dropped
), dialog
);
326 g_signal_connect(frame
, "clear",
327 G_CALLBACK(clear_icon
), dialog
);
329 gtk_dialog_add_buttons(dialog
,
330 GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
,
332 g_signal_connect(dialog
, "response", G_CALLBACK(dialog_response
), NULL
);
333 gtk_dialog_set_default_response(dialog
, GTK_RESPONSE_OK
);
335 gtk_widget_show_all(GTK_WIDGET(dialog
));
339 /****************************************************************
340 * INTERNAL FUNCTIONS *
341 ****************************************************************/
343 /* The dropbox shows the path for the currently selected radio setting */
344 static void radios_changed(gpointer data
)
346 GObject
*dialog
= G_OBJECT(data
);
350 MIME_type
*mime_type
;
352 radios
= g_object_get_data(dialog
, "radios");
353 path
= g_object_get_data(dialog
, "pathname");
354 drop_box
= g_object_get_data(dialog
, "rox-dropbox");
355 mime_type
= g_object_get_data(dialog
, "mime-type");
357 g_return_if_fail(radios
!= NULL
);
358 g_return_if_fail(path
!= NULL
);
359 g_return_if_fail(drop_box
!= NULL
);
360 g_return_if_fail(mime_type
!= NULL
);
362 switch (radios_get_value(radios
))
368 type
= g_strconcat(mime_type
->media_type
, ".png", NULL
);
369 path
= choices_find_path_load(type
, "MIME-icons");
371 drop_box_set_path(drop_box
, path
);
379 type
= g_strconcat(mime_type
->media_type
, "_",
380 mime_type
->subtype
, ".png", NULL
);
381 path
= choices_find_path_load(type
, "MIME-icons");
383 drop_box_set_path(drop_box
, path
);
390 gi
= g_hash_table_lookup(glob_icons
, path
);
391 drop_box_set_path(drop_box
, gi
);
397 diricon
= make_path(path
, ".DirIcon");
398 if (file_exists(diricon
))
399 drop_box_set_path(drop_box
, diricon
);
401 drop_box_set_path(drop_box
, NULL
);
405 drop_box_set_path(drop_box
, NULL
);
410 static gboolean
free_globicon(gpointer key
, gpointer value
, gpointer data
)
415 return TRUE
; /* For g_hash_table_foreach_remove() */
418 static void write_globicon(gpointer key
, gpointer value
, gpointer data
)
420 xmlNodePtr doc
= (xmlNodePtr
) data
;
423 tree
= xmlNewTextChild(doc
, NULL
, "rule", NULL
);
424 xmlSetProp(tree
, "match", key
);
425 xmlNewChild(tree
, NULL
, "icon", value
);
428 /* Write globicons file */
429 static void write_globicons(void)
431 gchar
*save
= NULL
, *save_new
= NULL
;
432 xmlDocPtr doc
= NULL
;
434 save
= choices_find_path_save("globicons", PROJECT
, TRUE
);
437 return; /* Saving is disabled */
439 save_new
= g_strconcat(save
, ".new", NULL
);
441 doc
= xmlNewDoc("1.0");
442 xmlDocSetRootElement(doc
,
443 xmlNewDocNode(doc
, NULL
, "special-files", NULL
));
445 g_hash_table_foreach(glob_icons
, write_globicon
,
446 xmlDocGetRootElement(doc
));
448 if (save_xml_file(doc
, save_new
) || rename(save_new
, save
))
449 delayed_error(_("Error saving %s: %s"),
450 save
, g_strerror(errno
));
459 /* Process a globicon line. Format:
460 glob-pattern icon-path
462 /home/<*>/Mail /usr/local/icons/mailbox.xpm
463 (<*> represents a single asterisk, enclosed in brackets to not break
466 static const char *process_globicons_line(gchar
*line
)
468 guchar
*pattern
, *iconpath
;
470 pattern
= strtok(line
, " \t");
471 /* We ignore empty lines, but they are no cause for a message */
475 iconpath
= strtok(NULL
, " \t");
477 /* If there is no icon, then we worry */
478 g_return_val_if_fail(iconpath
!= NULL
,
479 "Invalid line in globicons: no icon specified");
481 g_hash_table_insert(glob_icons
, g_strdup(pattern
), g_strdup(iconpath
));
486 /* Add a globicon entry to the list. If another one with the same
487 * path exists, it is replaced. Otherwise, the new entry is
488 * added to the top of the list (so that it takes precedence over
491 static void add_globicon(const gchar
*path
, const gchar
*icon
)
493 g_hash_table_insert(glob_icons
, g_strdup(path
), g_strdup(icon
));
495 /* Rewrite the globicons file */
498 /* Make sure any visible icons for the file are updated */
502 /* Remove the globicon for a certain path */
503 static void delete_globicon(const gchar
*path
)
507 if (!g_hash_table_lookup_extended(glob_icons
, path
, &key
, &value
))
510 g_hash_table_remove(glob_icons
, path
);
519 /* Set the icon for this dialog's file to 'icon' */
520 static void do_set_icon(GtkWidget
*dialog
, const gchar
*icon
)
526 path
= g_object_get_data(G_OBJECT(dialog
), "pathname");
527 g_return_if_fail(path
!= NULL
);
529 radios
= g_object_get_data(G_OBJECT(dialog
), "radios");
530 g_return_if_fail(radios
!= NULL
);
532 op
= radios_get_value(radios
);
536 if (!set_icon_path(path
, icon
))
539 else if (op
== SET_COPY
)
541 if (!create_diricon(path
, icon
))
546 gboolean just_media
= (op
== SET_MEDIA
);
549 type
= g_object_get_data(G_OBJECT(dialog
), "mime-type");
551 if (!set_icon_for_type(type
, icon
, just_media
))
555 destroy_on_idle(dialog
);
558 /* Called when a URI list is dropped onto the box in the Set Icon
559 * dialog. Make that the default icon.
561 static void drag_icon_dropped(GtkWidget
*drop_box
,
565 do_set_icon(dialog
, path
);
568 /* Set the thumbnail program for the given MIME type. We make a link */
569 static gboolean
set_thumbnail_for_type(MIME_type
*type
, const gchar
*exepath
,
576 leaf
=g_strdup(type
->media_type
);
578 leaf
=g_strconcat(type
->media_type
, "_", type
->subtype
, NULL
);
580 path
=choices_find_path_save(leaf
, "MIME-thumb", TRUE
);
584 if(symlink(exepath
, path
)) {
585 delayed_error("symlink: %s", g_strerror(errno
));
595 /* Set the icon for the given MIME type. We copy the file. */
596 static gboolean
set_icon_for_type(MIME_type
*type
, const gchar
*iconpath
,
603 item
=diritem_new("");
604 diritem_restat(iconpath
, item
, NULL
);
605 if(item
->flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
)) {
607 return set_thumbnail_for_type(type
, iconpath
, just_media
);
612 leaf
= g_strconcat(type
->media_type
, ".png", NULL
);
614 leaf
= g_strconcat(type
->media_type
, "_", type
->subtype
,
617 target
= choices_find_path_save(leaf
, "MIME-icons", TRUE
);
622 delayed_error(_("Setting icon disabled by CHOICESPATH"));
626 if (!convert_to_png(iconpath
, target
))
639 /* Load image 'src', and save it as an icon-sized image in png format.
640 * TRUE on success, error is already reported on failure.
642 static gboolean
convert_to_png(const gchar
*src
, const gchar
*dest
)
645 GError
*error
= NULL
;
647 pic
= g_fscache_lookup(pixmap_cache
, src
);
651 _("Unable to load image file -- maybe it's not in a "
652 "format I understand, or maybe the permissions are "
654 "The icon has not been changed."));
658 gdk_pixbuf_save(pic
->src_pixbuf
, dest
,
660 "tEXt::Software", PROJECT
,
666 delayed_error(_("Error creating image '%s':\n%s"),
667 dest
, error
->message
);