r4648: When setting or clearing a globicon, redraw pinboard and panel icons too
[rox-filer/th.git] / ROX-Filer / src / usericons.c
blob27f3a36d3e7ab3f5ff15a3059bebaf30d28013d5
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* usericons.c - handle user-defined icons. Diego Zamboni, Feb 7, 2001. */
22 #include "config.h"
24 #include <gtk/gtk.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <fnmatch.h>
30 #include <libxml/parser.h>
31 #include <time.h>
33 #include "global.h"
35 #include "fscache.h"
36 #include "diritem.h"
37 #include "dir.h"
38 #include "gui_support.h"
39 #include "choices.h"
40 #include "pixmaps.h"
41 #include "type.h"
42 #include "run.h"
43 #include "dnd.h"
44 #include "support.h"
45 #include "usericons.h"
46 #include "main.h"
47 #include "menu.h"
48 #include "filer.h"
49 #include "action.h"
50 #include "display.h"
51 #include "xml.h"
52 #include "dropbox.h"
54 #define SET_MEDIA 2
55 #define SET_TYPE 1
56 #define SET_PATH 0 /* Store in globicons */
57 #define SET_COPY 3 /* Create .DirIcon */
59 static GHashTable *glob_icons = NULL; /* Pathname -> Icon pathname */
61 /* Static prototypes */
62 static const char *process_globicons_line(gchar *line);
63 static gboolean free_globicon(gpointer key, gpointer value, gpointer data);
64 static void write_globicons(void);
65 static void drag_icon_dropped(GtkWidget *drop_box,
66 const guchar *path,
67 GtkWidget *dialog);
68 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
69 gboolean just_media);
70 static gboolean convert_to_png(const gchar *src, const gchar *dest);
71 static void radios_changed(Radios *radios, gpointer data);
73 /****************************************************************
74 * EXTERNAL INTERFACE *
75 ****************************************************************/
77 /* Read glob-pattern -> icon mappings from "globicons" in Choices */
78 void read_globicons()
80 static time_t last_read = (time_t) 0;
81 struct stat info;
82 guchar *path;
83 xmlDocPtr doc;
85 if (!glob_icons)
86 glob_icons = g_hash_table_new(g_str_hash, g_str_equal);
88 path = choices_find_xdg_path_load("globicons", PROJECT, SITE);
89 if (!path)
90 return; /* Nothing to load */
92 if (mc_stat(path, &info))
93 goto out;
95 if (info.st_mtime <= last_read)
96 goto out; /* File hasn't been modified since we last read it */
98 g_hash_table_foreach_remove(glob_icons, free_globicon, NULL);
100 doc = xmlParseFile(path);
101 if (doc)
103 xmlNodePtr node, icon, root;
104 char *match;
106 root = xmlDocGetRootElement(doc);
108 /* Handle the new XML file format */
109 for (node = root->xmlChildrenNode; node; node = node->next)
111 gchar *path, *icon_path;
113 if (node->type != XML_ELEMENT_NODE)
114 continue;
115 if (strcmp(node->name, "rule") != 0)
116 continue;
117 icon = get_subnode(node, NULL, "icon");
118 if (!icon)
119 continue;
120 match = xmlGetProp(node, "match");
121 if (!match)
122 continue;
124 icon_path = xmlNodeGetContent(icon);
125 path = expand_path(match);
126 g_hash_table_insert(glob_icons, path, icon_path);
127 g_free(match);
130 xmlFreeDoc(doc);
132 else
134 /* Handle the old non-XML format */
135 parse_file(path, process_globicons_line);
136 if (g_hash_table_size(glob_icons))
137 write_globicons(); /* Upgrade to new format */
140 last_read = time(NULL); /* Update time stamp */
141 out:
142 g_free(path);
145 /* Set an item's image field according to the globicons patterns if
146 * it matches one of them and the file exists.
148 void check_globicon(const guchar *path, DirItem *item)
150 gchar *gi;
152 g_return_if_fail(item && !item->_image);
154 gi = g_hash_table_lookup(glob_icons, path);
155 if (gi)
156 item->_image = g_fscache_lookup(pixmap_cache, gi);
159 static gboolean create_diricon(const guchar *filepath, const guchar *iconpath)
161 if (!convert_to_png(iconpath, make_path(filepath, ".DirIcon")))
162 return FALSE;
164 dir_check_this(filepath);
165 icons_may_update(filepath);
167 return TRUE;
170 /* Add a globicon mapping for the given file to the given icon path */
171 static gboolean set_icon_path(const guchar *filepath, const guchar *iconpath)
173 MaskedPixmap *pic;
175 /* Check if file exists */
176 if (!file_exists(iconpath))
178 delayed_error(_("The pathname you gave does not exist. "
179 "The icon has not been changed."));
180 return FALSE;
183 /* Check if we can load the image, warn the user if not. */
184 pic = g_fscache_lookup(pixmap_cache, iconpath);
185 if (!pic)
187 delayed_error(
188 _("Unable to load image file -- maybe it's not in a "
189 "format I understand, or maybe the permissions are "
190 "wrong?\n"
191 "The icon has not been changed."));
192 return FALSE;
194 g_object_unref(pic);
196 /* Add the globicon mapping and update visible icons */
197 add_globicon(filepath, iconpath);
199 return TRUE;
202 static void dialog_response(GtkWidget *dialog, gint response, gpointer data)
204 gtk_widget_destroy(dialog);
207 static void clear_icon(DropBox *drop_box, GObject *dialog)
209 Radios *radios;
210 const guchar *pathname;
212 pathname = g_object_get_data(G_OBJECT(dialog), "pathname");
213 g_return_if_fail(pathname != NULL);
215 radios = g_object_get_data(G_OBJECT(dialog), "radios");
216 g_return_if_fail(radios != NULL);
218 if (radios_get_value(radios) == SET_PATH)
220 delete_globicon(pathname);
222 else
224 const guchar *path;
225 guchar *tmp;
226 DropBox *drop_box;
228 drop_box = g_object_get_data(G_OBJECT(dialog), "rox-dropbox");
229 g_return_if_fail(drop_box != NULL);
231 path = drop_box_get_path(drop_box);
232 g_return_if_fail(path != NULL);
234 tmp = g_strdup_printf(_("Really delete icon '%s'?"), path);
235 if (confirm(tmp, GTK_STOCK_DELETE, NULL))
237 if (unlink(path))
238 delayed_error(_("Can't delete '%s':\n%s"),
239 path, g_strerror(errno));
240 else
242 dir_check_this(pathname);
243 icons_may_update(pathname);
246 g_free(tmp);
249 full_refresh();
250 radios_changed(g_object_get_data(dialog, "radios"), dialog);
253 /* Display a dialog box allowing the user to set the icon for
254 * a file or directory.
256 void icon_set_handler_dialog(DirItem *item, const guchar *path)
258 struct stat info;
259 GtkDialog *dialog;
260 GtkWidget *frame;
261 Radios *radios;
263 g_return_if_fail(item != NULL && path != NULL);
265 dialog = GTK_DIALOG(gtk_dialog_new());
266 gtk_dialog_set_has_separator(dialog, FALSE);
267 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
268 g_object_set_data_full(G_OBJECT(dialog), "pathname",
269 strdup(path), g_free);
271 gtk_window_set_title(GTK_WINDOW(dialog), _("Set icon"));
273 radios = radios_new(radios_changed, dialog);
275 g_object_set_data(G_OBJECT(dialog), "radios", radios);
276 g_object_set_data(G_OBJECT(dialog), "mime-type", item->mime_type);
278 #if 0
279 radios_add(radios,
280 _("Use a copy of the image as the default for all "
281 "files of these MIME types."), SET_MEDIA,
282 _("Set icon for all `%s/<anything>'"),
283 item->mime_type->media_type);
284 #endif
286 radios_add(radios,
287 _("Use a copy of the image for all files of this MIME "
288 "type."), SET_TYPE,
289 _("For all files of type `%s' (%s/%s)"),
290 mime_type_comment(item->mime_type),
291 item->mime_type->media_type,
292 item->mime_type->subtype);
294 radios_add(radios,
295 _("Add the file and image filenames to your "
296 "personal list. The setting will be lost if the image "
297 "or the file is moved."), SET_PATH,
298 _("Only for the file `%s'"), path);
300 radios_set_value(radios, SET_PATH);
302 /* If it's a directory, offer to create a .DirIcon */
303 if (mc_stat(path, &info) == 0 && S_ISDIR(info.st_mode))
305 radios_add(radios,
306 _("Copy the image inside the directory, as "
307 "a hidden file called '.DirIcon'. "
308 "All users will then see the "
309 "icon, and you can move the directory around safely. "
310 "This is usually the best option if you can write to "
311 "the directory."), SET_COPY,
312 _("Copy image into directory"));
313 if (access(path, W_OK) == 0)
314 radios_set_value(radios, SET_COPY);
318 frame = drop_box_new(_("Drop an icon file here"));
319 g_object_set_data(G_OBJECT(dialog), "rox-dropbox", frame);
321 /* Make sure rox-dropbox is set before packing (calls changed) */
322 radios_pack(radios, GTK_BOX(dialog->vbox));
323 gtk_box_pack_start(GTK_BOX(dialog->vbox), frame, TRUE, TRUE, 4);
325 g_signal_connect(frame, "path_dropped",
326 G_CALLBACK(drag_icon_dropped), dialog);
327 g_signal_connect(frame, "clear",
328 G_CALLBACK(clear_icon), dialog);
330 gtk_dialog_add_buttons(dialog,
331 GTK_STOCK_CLOSE, GTK_RESPONSE_OK,
332 NULL);
333 g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), NULL);
334 gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
336 gtk_widget_show_all(GTK_WIDGET(dialog));
340 /****************************************************************
341 * INTERNAL FUNCTIONS *
342 ****************************************************************/
344 /* The dropbox shows the path for the currently selected radio setting.
346 static void radios_changed(Radios *radios, gpointer data)
348 GObject *dialog = G_OBJECT(data);
349 DropBox *drop_box;
350 const guchar *path;
351 MIME_type *mime_type;
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_xdg_path_load(type, "MIME-icons",
370 SITE);
371 g_free(type);
372 drop_box_set_path(drop_box, path);
373 g_free(path);
374 break;
376 case SET_TYPE:
378 char *path, *type;
380 type = g_strconcat(mime_type->media_type, "_",
381 mime_type->subtype, ".png", NULL);
382 path = choices_find_xdg_path_load(type, "MIME-icons",
383 SITE);
384 g_free(type);
385 drop_box_set_path(drop_box, path);
386 g_free(path);
387 break;
389 case SET_PATH:
391 const char *gi;
392 gi = g_hash_table_lookup(glob_icons, path);
393 drop_box_set_path(drop_box, gi);
394 break;
396 case SET_COPY:
398 const char *diricon;
399 diricon = make_path(path, ".DirIcon");
400 if (file_exists(diricon))
401 drop_box_set_path(drop_box, diricon);
402 else
403 drop_box_set_path(drop_box, NULL);
404 break;
406 default:
407 drop_box_set_path(drop_box, NULL);
408 break;
412 static gboolean free_globicon(gpointer key, gpointer value, gpointer data)
414 g_free(key);
415 g_free(value);
417 return TRUE; /* For g_hash_table_foreach_remove() */
420 static void write_globicon(gpointer key, gpointer value, gpointer data)
422 xmlNodePtr doc = (xmlNodePtr) data;
423 xmlNodePtr tree;
425 tree = xmlNewTextChild(doc, NULL, "rule", NULL);
426 xmlSetProp(tree, "match", key);
427 xmlNewTextChild(tree, NULL, "icon", value);
430 /* Write globicons file */
431 static void write_globicons(void)
433 gchar *save = NULL, *save_new = NULL;
434 xmlDocPtr doc = NULL;
436 save = choices_find_xdg_path_save("globicons", PROJECT, SITE, TRUE);
438 if (!save)
439 return; /* Saving is disabled */
441 save_new = g_strconcat(save, ".new", NULL);
443 doc = xmlNewDoc("1.0");
444 xmlDocSetRootElement(doc,
445 xmlNewDocNode(doc, NULL, "special-files", NULL));
447 g_hash_table_foreach(glob_icons, write_globicon,
448 xmlDocGetRootElement(doc));
450 if (save_xml_file(doc, save_new) || rename(save_new, save))
451 delayed_error(_("Error saving %s: %s"),
452 save, g_strerror(errno));
454 g_free(save_new);
455 g_free(save);
457 if (doc)
458 xmlFreeDoc(doc);
461 /* Process a globicon line. Format:
462 glob-pattern icon-path
463 Example:
464 /home/<*>/Mail /usr/local/icons/mailbox.xpm
465 (<*> represents a single asterisk, enclosed in brackets to not break
466 the C comment).
468 static const char *process_globicons_line(gchar *line)
470 guchar *pattern, *iconpath;
472 pattern = strtok(line, " \t");
473 /* We ignore empty lines, but they are no cause for a message */
474 if (pattern == NULL)
475 return NULL;
477 iconpath = strtok(NULL, " \t");
479 /* If there is no icon, then we worry */
480 g_return_val_if_fail(iconpath != NULL,
481 "Invalid line in globicons: no icon specified");
483 g_hash_table_insert(glob_icons, g_strdup(pattern), g_strdup(iconpath));
485 return NULL;
488 /* Add a globicon entry to the list. If another one with the same
489 * path exists, it is replaced. Otherwise, the new entry is
490 * added to the top of the list (so that it takes precedence over
491 * other entries).
493 void add_globicon(const gchar *path, const gchar *icon)
495 g_hash_table_insert(glob_icons, g_strdup(path), g_strdup(icon));
497 /* Rewrite the globicons file */
498 write_globicons();
500 /* Make sure any visible icons for the file are updated */
501 examine(path);
504 /* Remove the globicon for a certain path */
505 void delete_globicon(const gchar *path)
507 gpointer key, value;
509 if (!g_hash_table_lookup_extended(glob_icons, path, &key, &value))
510 return;
512 g_hash_table_remove(glob_icons, path);
514 g_free(key);
515 g_free(value);
517 write_globicons();
518 examine(path);
521 /* Set the icon for this dialog's file to 'icon' */
522 static void do_set_icon(GtkWidget *dialog, const gchar *icon)
524 guchar *path = NULL;
525 Radios *radios;
526 int op;
528 path = g_object_get_data(G_OBJECT(dialog), "pathname");
529 g_return_if_fail(path != NULL);
531 radios = g_object_get_data(G_OBJECT(dialog), "radios");
532 g_return_if_fail(radios != NULL);
534 op = radios_get_value(radios);
536 if (op == SET_PATH)
538 if (!set_icon_path(path, icon))
539 return;
541 else if (op == SET_COPY)
543 if (!create_diricon(path, icon))
544 return;
546 else
548 gboolean just_media = (op == SET_MEDIA);
549 MIME_type *type;
551 type = g_object_get_data(G_OBJECT(dialog), "mime-type");
553 if (!set_icon_for_type(type, icon, just_media))
554 return;
557 destroy_on_idle(dialog);
560 /* Called when a URI list is dropped onto the box in the Set Icon
561 * dialog. Make that the default icon.
563 static void drag_icon_dropped(GtkWidget *drop_box,
564 const guchar *path,
565 GtkWidget *dialog)
567 do_set_icon(dialog, path);
570 /* Set the icon for the given MIME type. We copy the file. */
571 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
572 gboolean just_media)
574 gchar *target;
575 gchar *leaf;
577 if (just_media)
578 leaf = g_strconcat(type->media_type, ".png", NULL);
579 else
580 leaf = g_strconcat(type->media_type, "_", type->subtype,
581 ".png", NULL);
583 target = choices_find_xdg_path_save(leaf, "MIME-icons", SITE, TRUE);
584 g_free(leaf);
586 if (!target)
588 delayed_error(_("Setting icon disabled by CHOICESPATH"));
589 return FALSE;
592 if (!convert_to_png(iconpath, target))
594 g_free(target);
595 return FALSE;
598 g_free(target);
600 full_refresh();
602 return TRUE;
605 /* Load image 'src', and save it as an icon-sized image in png format.
606 * TRUE on success, error is already reported on failure.
608 static gboolean convert_to_png(const gchar *src, const gchar *dest)
610 MaskedPixmap *pic;
611 GError *error = NULL;
613 pic = g_fscache_lookup(pixmap_cache, src);
614 if (!pic)
616 delayed_error(
617 _("Unable to load image file -- maybe it's not in a "
618 "format I understand, or maybe the permissions are "
619 "wrong?\n"
620 "The icon has not been changed."));
621 return FALSE;
624 gdk_pixbuf_save(pic->src_pixbuf, dest,
625 "png", &error,
626 "tEXt::Software", PROJECT,
627 NULL);
628 g_object_unref(pic);
630 if (error)
632 delayed_error(_("Error creating image '%s':\n%s"),
633 dest, error->message);
634 g_error_free(error);
635 return FALSE;
638 return TRUE;