r3198: Fixed a load of XML escaping problems.
[rox-filer.git] / ROX-Filer / src / usericons.c
blobbc3b5c51633cf355844ac7401e09bf6408683215
1 /*
2 * $Id$
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)
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 /* usericons.c - handle user-defined icons. Diego Zamboni, Feb 7, 2001. */
24 #include "config.h"
26 #include <gtk/gtk.h>
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <fnmatch.h>
32 #include <libxml/parser.h>
33 #include <time.h>
35 #include "global.h"
37 #include "fscache.h"
38 #include "diritem.h"
39 #include "dir.h"
40 #include "gui_support.h"
41 #include "choices.h"
42 #include "pixmaps.h"
43 #include "type.h"
44 #include "run.h"
45 #include "dnd.h"
46 #include "support.h"
47 #include "usericons.h"
48 #include "main.h"
49 #include "menu.h"
50 #include "filer.h"
51 #include "action.h"
52 #include "display.h"
53 #include "xml.h"
54 #include "dropbox.h"
56 #define SET_MEDIA 2
57 #define SET_TYPE 1
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,
69 const guchar *path,
70 GtkWidget *dialog);
71 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
72 gboolean just_media);
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 */
82 void read_globicons()
84 static time_t last_read = (time_t) 0;
85 struct stat info;
86 guchar *path;
87 xmlDocPtr doc;
89 if (!glob_icons)
90 glob_icons = g_hash_table_new(g_str_hash, g_str_equal);
92 path = choices_find_path_load("globicons", PROJECT);
93 if (!path)
94 return; /* Nothing to load */
96 if (mc_stat(path, &info))
97 goto out;
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);
105 if (doc)
107 xmlNodePtr node, icon, root;
108 char *match;
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)
118 continue;
119 if (strcmp(node->name, "rule") != 0)
120 continue;
121 icon = get_subnode(node, NULL, "icon");
122 if (!icon)
123 continue;
124 match = xmlGetProp(node, "match");
125 if (!match)
126 continue;
128 icon_path = xmlNodeGetContent(icon);
129 path = expand_path(match);
130 g_hash_table_insert(glob_icons, path, icon_path);
131 g_free(match);
134 xmlFreeDoc(doc);
136 else
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 */
145 out:
146 g_free(path);
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)
154 gchar *gi;
156 g_return_if_fail(item && !item->image);
158 gi = g_hash_table_lookup(glob_icons, path);
159 if (gi)
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")))
166 return FALSE;
168 dir_check_this(filepath);
170 return TRUE;
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)
176 MaskedPixmap *pic;
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."));
183 return FALSE;
186 /* Check if we can load the image, warn the user if not. */
187 pic = g_fscache_lookup(pixmap_cache, iconpath);
188 if (!pic)
190 delayed_error(
191 _("Unable to load image file -- maybe it's not in a "
192 "format I understand, or maybe the permissions are "
193 "wrong?\n"
194 "The icon has not been changed."));
195 return FALSE;
197 g_object_unref(pic);
199 /* Add the globicon mapping and update visible icons */
200 add_globicon(filepath, iconpath);
202 return TRUE;
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)
212 Radios *radios;
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)
219 const guchar *path;
221 path = g_object_get_data(G_OBJECT(dialog), "pathname");
222 g_return_if_fail(path != NULL);
224 delete_globicon(path);
226 else
228 const guchar *path;
229 guchar *tmp;
230 DropBox *drop_box;
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))
241 if (unlink(path))
242 delayed_error(_("Can't delete '%s':\n%s"),
243 path, g_strerror(errno));
245 g_free(tmp);
248 full_refresh();
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)
257 struct stat info;
258 GtkDialog *dialog;
259 GtkWidget *frame;
260 Radios *radios;
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);
277 #if 0
278 radios_add(radios,
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);
283 #endif
285 radios_add(radios,
286 _("Use a copy of the image for all files of this MIME "
287 "type."), SET_TYPE,
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);
293 radios_add(radios,
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))
304 radios_add(radios,
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,
331 NULL);
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);
347 DropBox *drop_box;
348 Radios *radios;
349 const guchar *path;
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))
364 case SET_MEDIA:
366 char *path, *type;
368 type = g_strconcat(mime_type->media_type, ".png", NULL);
369 path = choices_find_path_load(type, "MIME-icons");
370 g_free(type);
371 drop_box_set_path(drop_box, path);
372 g_free(path);
373 break;
375 case SET_TYPE:
377 char *path, *type;
379 type = g_strconcat(mime_type->media_type, "_",
380 mime_type->subtype, ".png", NULL);
381 path = choices_find_path_load(type, "MIME-icons");
382 g_free(type);
383 drop_box_set_path(drop_box, path);
384 g_free(path);
385 break;
387 case SET_PATH:
389 const char *gi;
390 gi = g_hash_table_lookup(glob_icons, path);
391 drop_box_set_path(drop_box, gi);
392 break;
394 case SET_COPY:
396 const char *diricon;
397 diricon = make_path(path, ".DirIcon");
398 if (file_exists(diricon))
399 drop_box_set_path(drop_box, diricon);
400 else
401 drop_box_set_path(drop_box, NULL);
402 break;
404 default:
405 drop_box_set_path(drop_box, NULL);
406 break;
410 static gboolean free_globicon(gpointer key, gpointer value, gpointer data)
412 g_free(key);
413 g_free(value);
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;
421 xmlNodePtr tree;
423 tree = xmlNewTextChild(doc, NULL, "rule", NULL);
424 xmlSetProp(tree, "match", key);
425 xmlNewTextChild(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);
436 if (!save)
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));
452 g_free(save_new);
453 g_free(save);
455 if (doc)
456 xmlFreeDoc(doc);
459 /* Process a globicon line. Format:
460 glob-pattern icon-path
461 Example:
462 /home/<*>/Mail /usr/local/icons/mailbox.xpm
463 (<*> represents a single asterisk, enclosed in brackets to not break
464 the C comment).
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 */
472 if (pattern == NULL)
473 return NULL;
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));
483 return NULL;
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
489 * other entries).
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 */
496 write_globicons();
498 /* Make sure any visible icons for the file are updated */
499 examine(path);
502 /* Remove the globicon for a certain path */
503 static void delete_globicon(const gchar *path)
505 gpointer key, value;
507 if (!g_hash_table_lookup_extended(glob_icons, path, &key, &value))
508 return;
510 g_hash_table_remove(glob_icons, path);
512 g_free(key);
513 g_free(value);
515 write_globicons();
516 examine(path);
519 /* Set the icon for this dialog's file to 'icon' */
520 static void do_set_icon(GtkWidget *dialog, const gchar *icon)
522 guchar *path = NULL;
523 Radios *radios;
524 int op;
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);
534 if (op == SET_PATH)
536 if (!set_icon_path(path, icon))
537 return;
539 else if (op == SET_COPY)
541 if (!create_diricon(path, icon))
542 return;
544 else
546 gboolean just_media = (op == SET_MEDIA);
547 MIME_type *type;
549 type = g_object_get_data(G_OBJECT(dialog), "mime-type");
551 if (!set_icon_for_type(type, icon, just_media))
552 return;
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,
562 const guchar *path,
563 GtkWidget *dialog)
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,
570 gboolean just_media)
572 gchar *leaf;
573 gchar *path;
575 if(just_media)
576 leaf=g_strdup(type->media_type);
577 else
578 leaf=g_strconcat(type->media_type, "_", type->subtype, NULL);
580 path=choices_find_path_save(leaf, "MIME-thumb", TRUE);
581 g_free(leaf);
583 if(path) {
584 if(symlink(exepath, path)) {
585 delayed_error("symlink: %s", g_strerror(errno));
586 g_free(path);
587 return FALSE;
589 g_free(path);
592 return TRUE;
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,
597 gboolean just_media)
599 gchar *target;
600 gchar *leaf;
601 DirItem *item;
603 item=diritem_new("");
604 diritem_restat(iconpath, item, NULL);
605 if(item->flags & (ITEM_FLAG_APPDIR | ITEM_FLAG_EXEC_FILE)) {
606 diritem_free(item);
607 return set_thumbnail_for_type(type, iconpath, just_media);
609 diritem_free(item);
611 if (just_media)
612 leaf = g_strconcat(type->media_type, ".png", NULL);
613 else
614 leaf = g_strconcat(type->media_type, "_", type->subtype,
615 ".png", NULL);
617 target = choices_find_path_save(leaf, "MIME-icons", TRUE);
618 g_free(leaf);
620 if (!target)
622 delayed_error(_("Setting icon disabled by CHOICESPATH"));
623 return FALSE;
626 if (!convert_to_png(iconpath, target))
628 g_free(target);
629 return FALSE;
632 g_free(target);
634 full_refresh();
636 return TRUE;
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)
644 MaskedPixmap *pic;
645 GError *error = NULL;
647 pic = g_fscache_lookup(pixmap_cache, src);
648 if (!pic)
650 delayed_error(
651 _("Unable to load image file -- maybe it's not in a "
652 "format I understand, or maybe the permissions are "
653 "wrong?\n"
654 "The icon has not been changed."));
655 return FALSE;
658 gdk_pixbuf_save(pic->src_pixbuf, dest,
659 "png", &error,
660 "tEXt::Software", PROJECT,
661 NULL);
662 g_object_unref(pic);
664 if (error)
666 delayed_error(_("Error creating image '%s':\n%s"),
667 dest, error->message);
668 g_error_free(error);
669 return FALSE;
672 return TRUE;