r898: Applied Bernard Jungen's latest patch:
[rox-filer.git] / ROX-Filer / src / usericons.c
blob62a675e265a819973f883e233d2364dbab05e2eb
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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 <fnmatch.h>
31 #include <parser.h>
33 #include "global.h"
34 #include "dir.h"
35 #include "gui_support.h"
36 #include "choices.h"
37 #include "pixmaps.h"
38 #include "run.h"
39 #include "dnd.h"
40 #include "support.h"
41 #include "usericons.h"
43 /* Store glob-to-icon mappings */
44 typedef struct _GlobIcon {
45 guchar *pattern;
46 guchar *iconpath;
47 } GlobIcon;
49 static GList *glob_icons = NULL;
51 /* Static prototypes */
52 static char *process_globicons_line(guchar *line);
53 static GlobIcon *get_globicon_struct(guchar *path);
54 static void free_globicon(GlobIcon *gi, gpointer user_data);
55 static void get_path_set_icon(GtkWidget *dialog);
56 static void show_icon_help(gpointer data);
57 static void write_globicons(void);
59 /****************************************************************
60 * EXTERNAL INTERFACE *
61 ****************************************************************/
63 /* Read glob-pattern -> icon mappings from "globicons" in Choices */
64 void read_globicons()
66 static time_t last_read = (time_t) 0;
67 struct stat info;
68 guchar *path;
69 xmlDocPtr doc;
71 path = choices_find_path_load("globicons", PROJECT);
72 if (!path)
73 return; /* Nothing to load */
75 if (mc_stat(path, &info) == -1)
76 goto out;
78 if (info.st_mtime <= last_read)
79 goto out; /* File hasn't been modified since we last read it */
81 if (glob_icons)
83 g_list_foreach(glob_icons, (GFunc) free_globicon, NULL);
84 g_list_free(glob_icons);
85 glob_icons = NULL;
88 doc = xmlParseFile(path);
89 if (doc)
91 xmlNodePtr node, icon, root;
92 char *match;
93 GlobIcon *gi;
95 root = xmlDocGetRootElement(doc);
97 /* Handle the new XML file format */
98 for (node = root->xmlChildrenNode; node; node = node->next)
100 if (node->type != XML_ELEMENT_NODE)
101 continue;
102 if (strcmp(node->name, "rule") != 0)
103 continue;
104 icon = get_subnode(node, NULL, "icon");
105 if (!icon)
106 continue;
107 match = xmlGetProp(node, "match");
108 if (!match)
109 continue;
111 gi = g_new(GlobIcon, 1);
112 gi->pattern = match;
113 gi->iconpath = xmlNodeGetContent(icon);
115 /* Prepend so that later patterns override earlier ones
116 * when we loop through the list.
118 glob_icons = g_list_prepend(glob_icons, gi);
122 xmlFreeDoc(doc);
124 else
126 /* Handle the old non-XML format */
127 parse_file(path, process_globicons_line);
128 if (glob_icons)
129 write_globicons(); /* Upgrade to new format */
132 last_read = time(NULL); /* Update time stamp */
133 out:
134 g_free(path);
137 /* Set an item's image field according to the globicons patterns if
138 * it matches one of them and the file exists.
140 void check_globicon(guchar *path, DirItem *item)
142 GlobIcon *gi;
144 g_return_if_fail(item && !item->image);
146 gi = get_globicon_struct(path);
147 if (gi)
148 item->image = g_fscache_lookup(pixmap_cache, gi->iconpath);
151 /****************************************************************
152 * INTERNAL FUNCTIONS *
153 ****************************************************************/
155 static void free_globicon(GlobIcon *gi, gpointer user_data)
157 g_return_if_fail(gi != NULL);
159 g_free(gi->pattern);
160 g_free(gi->iconpath);
161 g_free(gi);
164 /* Write globicons file */
165 static void write_globicons(void)
167 gchar *save = NULL;
168 GList *next;
169 gchar *save_new = NULL;
170 xmlDocPtr doc = NULL;
172 save = choices_find_path_save("globicons", PROJECT, TRUE);
174 if (!save)
175 return; /* Saving is disabled */
177 save_new = g_strconcat(save, ".new", NULL);
179 doc = xmlNewDoc("1.0");
180 xmlDocSetRootElement(doc,
181 xmlNewDocNode(doc, NULL, "special-files", NULL));
183 for (next = g_list_last(glob_icons); next; next = next->prev)
185 GlobIcon *gi = (GlobIcon *) next->data;
186 xmlNodePtr tree;
188 tree = xmlNewTextChild(xmlDocGetRootElement(doc),
189 NULL, "rule", NULL);
190 xmlSetProp(tree, "match", gi->pattern);
191 xmlNewChild(tree, NULL, "icon", gi->iconpath);
194 #if LIBXML_VERSION > 20400
195 if (xmlSaveFormatFileEnc(save_new, doc, NULL, 1) < 0)
196 goto err;
197 #else
199 FILE *out;
201 out = fopen(save_new, "w");
202 if (!out)
203 goto err;
204 xmlDocDump(out, doc); /* Some versions return void */
205 if (fclose(out))
206 goto err;
208 #endif
210 if (rename(save_new, save))
211 goto err;
212 goto out;
213 err:
214 delayed_rox_error(_("Error saving globicons: %s"), g_strerror(errno));
215 out:
216 if (doc)
217 xmlFreeDoc(doc);
218 g_free(save_new);
219 g_free(save);
222 /* Process a globicon line. Format:
223 glob-pattern icon-path
224 Example:
225 /home/<*>/Mail /usr/local/icons/mailbox.xpm
226 (<*> represents a single asterisk, enclosed in brackets to not break
227 the C comment).
229 static char *process_globicons_line(guchar *line)
231 guchar *pattern, *iconpath;
232 GlobIcon *gi;
234 pattern = strtok(line, " \t");
235 /* We ignore empty lines, but they are no cause for a message */
236 if (pattern == NULL)
237 return NULL;
239 iconpath = strtok(NULL, " \t");
241 /* If there is no icon, then we worry */
242 g_return_val_if_fail(iconpath != NULL,
243 "Invalid line in globicons: no icon specified");
245 gi = g_new(GlobIcon, 1);
246 gi->pattern = g_strdup(pattern);
247 gi->iconpath = g_strdup(iconpath);
249 /* Prepend so that later patterns override earlier ones when we loop
250 * through the list.
252 glob_icons = g_list_prepend(glob_icons, gi);
254 return NULL;
257 /* If there is a globicon entry that matches the given path, return
258 * a pointer to the GlobIcon structure, otherwise return NULL.
259 * The returned pointer should not be freed because it is part of
260 * the glob_icons list.
262 static GlobIcon *get_globicon_struct(guchar *path)
264 GList *list;
266 for (list = glob_icons; list; list = list->next)
268 GlobIcon *gi = (GlobIcon *) list->data;
270 if (fnmatch(gi->pattern, path, FNM_PATHNAME) == 0)
271 return gi;
274 /* If we get here, there is no corresponding globicon */
275 return NULL;
278 /* Add a globicon entry to the list. If another one with the same
279 * path exists, it is replaced. Otherwise, the new entry is
280 * added to the top of the list (so that it takes precedence over
281 * other entries).
283 static void add_globicon(guchar *path, guchar *icon)
285 GList *list;
286 GlobIcon *gi;
288 for (list = glob_icons; list; list = list->next)
290 gi = (GlobIcon *) list->data;
292 if (strcmp(gi->pattern, path) == 0)
294 g_free(gi->iconpath);
295 gi->iconpath = g_strdup(icon);
296 goto out;
300 gi = g_new(GlobIcon, 1);
301 gi->pattern = g_strdup(path);
302 gi->iconpath = g_strdup(icon);
304 /* Prepend so that later patterns override earlier ones when we loop
305 * through the list.
307 glob_icons = g_list_prepend(glob_icons, gi);
309 out:
310 /* Rewrite the globicons file */
311 write_globicons();
313 /* Make sure any visible icons for the file are updated */
314 examine(path);
317 /* Remove the globicon for a certain path from the list. If the path
318 * has no associated globicon, the list is not modified.
320 static void delete_globicon(guchar *path)
322 GlobIcon *gi;
324 /* XXX: What happens if the user tries to unset /home/fred/Mail
325 * and there is a rule for /home/<*>/Mail ?
327 gi = get_globicon_struct(path);
328 if (!gi)
329 return; /* Not in the list */
331 glob_icons = g_list_remove(glob_icons, gi);
332 free_globicon(gi, NULL);
333 write_globicons();
334 examine(path);
337 /* Called when a URI list is dropped onto the box in the Set Icon
338 * dialog. Make that the default icon.
340 static void drag_icon_dropped(GtkWidget *frame,
341 GdkDragContext *context,
342 gint x,
343 gint y,
344 GtkSelectionData *selection_data,
345 guint info,
346 guint32 time,
347 GtkWidget *dialog)
349 GList *uris;
350 guchar *icon = NULL;
351 guchar *path = NULL;
353 if (!selection_data->data)
354 return;
356 uris = uri_list_to_glist(selection_data->data);
358 if (g_list_length(uris) == 1)
359 icon = get_local_path((guchar *) uris->data);
360 g_list_free(uris);
362 if (!icon)
364 delayed_rox_error(_("You should drop a single local icon file "
365 "onto the drop box - that icon will be "
366 "used for this file from now on."));
367 return;
370 path = gtk_object_get_data(GTK_OBJECT(dialog), "pathname");
372 if (!set_icon_path(path, icon))
373 return;
375 destroy_on_idle(dialog);
378 /* Called if the user clicks on the OK button on the Set Icon dialog */
379 static void get_path_set_icon(GtkWidget *dialog)
381 GtkEntry *entry;
382 guchar *icon, *path;
384 entry = gtk_object_get_data(GTK_OBJECT(dialog), "icon_path");
385 path = gtk_object_get_data(GTK_OBJECT(dialog), "pathname");
386 g_return_if_fail(entry != NULL && path != NULL);
388 icon = gtk_entry_get_text(entry);
390 if (!set_icon_path(path, icon))
391 return;
393 destroy_on_idle(dialog);
396 /* Called if the user clicks on the "Remove custom icon" button */
397 static void remove_icon(GtkWidget *dialog)
399 guchar *path;
401 g_return_if_fail(dialog != NULL);
403 path = gtk_object_get_data(GTK_OBJECT(dialog), "pathname");
404 g_return_if_fail(path != NULL);
406 delete_globicon(path);
408 destroy_on_idle(dialog);
411 /* Add a globicon mapping for the given file to the given icon path */
412 gboolean set_icon_path(guchar *filepath, guchar *iconpath)
414 struct stat icon;
415 MaskedPixmap *pic;
417 /* Check if file exists */
418 if (!mc_stat(iconpath, &icon) == 0) {
419 delayed_rox_error(_("The pathname you gave does not exist. "
420 "The icon has not been changed."));
421 return FALSE;
424 /* Check if we can load the image, warn the user if not. */
425 pic = g_fscache_lookup(pixmap_cache, iconpath);
426 if (!pic)
428 delayed_rox_error(
429 _("Unable to load image file -- maybe it's not in a "
430 "format I understand, or maybe the permissions are "
431 "wrong?\n"
432 "The icon has not been changed."));
433 return FALSE;
435 g_fscache_data_unref(pixmap_cache, pic);
437 /* Add the globicon mapping and update visible icons */
438 add_globicon(filepath, iconpath);
440 return TRUE;
443 /* Display a dialog box allowing the user to set the icon for
444 * a file or directory.
446 void icon_set_handler_dialog(DirItem *item, guchar *path)
448 guchar *tmp;
449 GtkWidget *dialog, *vbox, *frame, *hbox, *entry, *label, *button;
450 GtkTargetEntry targets[] = {
451 {"text/uri-list", 0, TARGET_URI_LIST},
453 GlobIcon *gi;
455 gi = get_globicon_struct(path);
457 g_return_if_fail(item != NULL && path != NULL);
459 dialog = gtk_window_new(GTK_WINDOW_DIALOG);
460 gtk_object_set_data_full(GTK_OBJECT(dialog),
461 "pathname",
462 strdup(path),
463 g_free);
465 gtk_window_set_title(GTK_WINDOW(dialog), _("Set icon"));
466 gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
468 vbox = gtk_vbox_new(FALSE, 4);
469 gtk_container_add(GTK_CONTAINER(dialog), vbox);
471 tmp = g_strconcat(_("Path: "), path, NULL);
472 gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(tmp), FALSE, TRUE, 0);
473 g_free(tmp);
475 frame = gtk_frame_new(NULL);
476 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 4);
477 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
478 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
480 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
481 targets, sizeof(targets) / sizeof(*targets),
482 GDK_ACTION_COPY);
483 gtk_signal_connect(GTK_OBJECT(frame), "drag_data_received",
484 GTK_SIGNAL_FUNC(drag_icon_dropped), dialog);
486 label = gtk_label_new(_("Drop an icon file here"));
487 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
488 gtk_container_add(GTK_CONTAINER(frame), label);
490 hbox = gtk_hbox_new(FALSE, 4);
491 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 4);
492 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
493 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("OR")),
494 FALSE, TRUE, 0);
495 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
497 hbox = gtk_hbox_new(FALSE, 4);
498 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
500 label = gtk_label_new(_("Enter the path of an icon file:")),
501 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
502 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
504 gtk_box_pack_start(GTK_BOX(hbox),
505 new_help_button(show_icon_help, NULL), FALSE, TRUE, 0);
507 entry = gtk_entry_new();
508 /* Set the current icon as the default text if there is one */
509 if (gi && gi->iconpath)
510 gtk_entry_set_text(GTK_ENTRY(entry), gi->iconpath);
512 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
513 gtk_widget_grab_focus(entry);
514 gtk_object_set_data(GTK_OBJECT(dialog), "icon_path", entry);
515 gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
516 GTK_SIGNAL_FUNC(get_path_set_icon),
517 GTK_OBJECT(dialog));
519 hbox = gtk_hbox_new(FALSE, 4);
520 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 4);
521 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
522 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("OR")),
523 FALSE, TRUE, 0);
524 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
526 hbox = gtk_hbox_new(TRUE, 4);
527 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
529 button = gtk_button_new_with_label(_("Remove custom icon"));
530 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
531 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
532 GTK_SIGNAL_FUNC(remove_icon),
533 GTK_OBJECT(dialog));
535 hbox = gtk_hbox_new(TRUE, 4);
536 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
538 button = gtk_button_new_with_label(_("OK"));
539 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
540 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
541 gtk_window_set_default(GTK_WINDOW(dialog), button);
542 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
543 GTK_SIGNAL_FUNC(get_path_set_icon),
544 GTK_OBJECT(dialog));
546 button = gtk_button_new_with_label(_("Cancel"));
547 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
548 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
549 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
550 GTK_SIGNAL_FUNC(gtk_widget_destroy),
551 GTK_OBJECT(dialog));
553 gtk_widget_show_all(dialog);
556 static void show_icon_help(gpointer data)
558 report_rox_error(
559 _("Enter the full path of a file that contains a valid "
560 "image to be used as the icon for this file or directory."));