r1283: Added a whole load of 'const' modifiers so we can do extra checking...
[rox-filer.git] / ROX-Filer / src / usericons.c
blob42b11c759201b9c59e7cc68f2651b783dd3a849d
1 /*
2 * $Id$
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)
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 "icon.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,
67 gint x,
68 gint y,
69 GtkSelectionData *selection_data,
70 guint info,
71 guint32 time,
72 GtkWidget *dialog);
73 static void remove_icon(GtkWidget *dialog);
74 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
75 gboolean just_media);
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) == -1)
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 #ifndef GTK2
131 gchar *loc_match, *loc_icon;
133 loc_match = from_utf8(match);
134 path = icon_convert_path(loc_match);
135 g_free(loc_match);
136 loc_icon = from_utf8(icon_path);
137 g_hash_table_insert(glob_icons, path, loc_icon);
138 g_free(icon_path);
140 #else
141 path = icon_convert_path(match);
142 g_hash_table_insert(glob_icons, path, icon_path);
143 #endif
144 g_free(match);
147 xmlFreeDoc(doc);
149 else
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 */
158 out:
159 g_free(path);
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)
167 gchar *gi;
169 g_return_if_fail(item && !item->image);
171 gi = g_hash_table_lookup(glob_icons, path);
172 if (gi)
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)
179 struct stat icon;
180 MaskedPixmap *pic;
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."));
186 return FALSE;
189 /* Check if we can load the image, warn the user if not. */
190 pic = g_fscache_lookup(pixmap_cache, iconpath);
191 if (!pic)
193 delayed_error(
194 _("Unable to load image file -- maybe it's not in a "
195 "format I understand, or maybe the permissions are "
196 "wrong?\n"
197 "The icon has not been changed."));
198 return FALSE;
200 g_fscache_data_unref(pixmap_cache, pic);
202 /* Add the globicon mapping and update visible icons */
203 add_globicon(filepath, iconpath);
205 return TRUE;
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)
213 guchar *tmp;
214 GtkWidget *dialog, *vbox, *frame, *hbox, *vbox2;
215 GtkWidget *entry, *label, *button, *align, *icon;
216 GtkWidget **radio;
217 GtkTargetEntry targets[] = {
218 {"text/uri-list", 0, TARGET_URI_LIST},
220 char *gi;
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),
230 "pathname",
231 strdup(path),
232 g_free);
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);
246 g_free(tmp);
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);
254 g_free(tmp);
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);
260 g_free(tmp);
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),
276 GDK_ACTION_COPY);
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"),
294 NULL);
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")),
302 FALSE, TRUE, 0);
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 */
317 if (gi)
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),
325 GTK_OBJECT(dialog));
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")),
331 FALSE, TRUE, 0);
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),
341 GTK_OBJECT(dialog));
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),
352 GTK_OBJECT(dialog));
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),
359 GTK_OBJECT(dialog));
361 gtk_widget_show_all(dialog);
365 /****************************************************************
366 * INTERNAL FUNCTIONS *
367 ****************************************************************/
369 static gboolean free_globicon(gpointer key, gpointer value, gpointer data)
371 g_free(key);
372 g_free(value);
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;
380 xmlNodePtr tree;
381 #ifndef GTK2
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);
391 g_free(u8_path);
392 g_free(u8_icon);
393 #else
394 tree = xmlNewTextChild(doc, NULL, "rule", NULL);
395 xmlSetProp(tree, "match", key);
396 xmlNewChild(tree, NULL, "icon", value);
397 #endif
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);
408 if (!save)
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));
424 g_free(save_new);
425 g_free(save);
427 if (doc)
428 xmlFreeDoc(doc);
431 /* Process a globicon line. Format:
432 glob-pattern icon-path
433 Example:
434 /home/<*>/Mail /usr/local/icons/mailbox.xpm
435 (<*> represents a single asterisk, enclosed in brackets to not break
436 the C comment).
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 */
444 if (pattern == NULL)
445 return NULL;
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));
455 return NULL;
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
461 * other entries).
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 */
468 write_globicons();
470 /* Make sure any visible icons for the file are updated */
471 examine(path);
474 /* Remove the globicon for a certain path */
475 static void delete_globicon(guchar *path)
477 gpointer key, value;
479 if (!g_hash_table_lookup_extended(glob_icons, path, &key, &value))
480 return;
482 g_hash_table_remove(glob_icons, path);
484 g_free(key);
485 g_free(value);
487 write_globicons();
488 examine(path);
491 /* Set the icon for this dialog's file to 'icon' */
492 static void do_set_icon(GtkWidget *dialog, const gchar *icon)
494 guchar *path = NULL;
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))
506 return;
508 else
510 gboolean just_media;
511 MIME_type *type;
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))
517 return;
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,
528 gint x,
529 gint y,
530 GtkSelectionData *selection_data,
531 guint info,
532 guint32 time,
533 GtkWidget *dialog)
535 GList *uris, *next;
536 guchar *icon = NULL;
538 if (!selection_data->data)
539 return;
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)
547 g_free(next->data);
548 g_list_free(uris);
550 if (!icon)
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."));
555 return;
558 do_set_icon(dialog, icon);
560 g_free(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)
566 GtkEntry *entry;
567 const gchar *icon;
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)
580 guchar *path;
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)
594 report_error(
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,
601 gboolean just_media)
603 gchar *target;
604 gchar *leaf;
605 gchar *dir;
606 GList *paths;
608 /* XXX: Should convert to XPM format... */
610 if (just_media)
611 leaf = g_strconcat(type->media_type, ".xpm", NULL);
612 else
613 leaf = g_strconcat(type->media_type, "_", type->subtype,
614 ".xpm", NULL);
616 target = choices_find_path_save(leaf, "MIME-icons", TRUE);
617 if (!target)
619 delayed_error(_("Setting icon disabled by CHOICESPATH"));
620 g_free(leaf);
621 return FALSE;
624 dir = g_dirname(target);
625 paths = g_list_append(NULL, (gchar *) iconpath);
627 action_copy(paths, dir, leaf, -1);
629 g_free(leaf);
630 g_free(dir);
631 g_free(target);
632 g_list_free(paths);
634 return TRUE;
637 static void get_dir(gpointer key, gpointer value, gpointer data)
639 GHashTable *names = (GHashTable *) data;
640 gchar *dir;
642 dir = g_dirname(value); /* Freed in add_dir_to_menu */
643 if (dir)
645 g_hash_table_insert(names, dir, NULL);
649 static void open_icon_dir(GtkMenuItem *item, gpointer data)
651 FilerWindow *filer;
652 char *dir;
654 gtk_label_get(GTK_LABEL(GTK_BIN(item)->child), &dir);
655 filer = filer_opendir(dir, NULL);
656 if (filer)
657 display_set_thumbs(filer, TRUE);
660 static void add_dir_to_menu(gpointer key, gpointer value, gpointer data)
662 GtkMenuShell *menu = (GtkMenuShell *) data;
663 GtkWidget *item;
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);
669 g_free(key);
670 gtk_menu_shell_append(menu, item);
673 static void show_current_dirs_menu(GtkWidget *button, gpointer data)
675 GHashTable *names;
676 GtkWidget *menu;
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"));
686 return;
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);