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)
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. */
35 #include "gui_support.h"
41 #include "usericons.h"
43 /* Store glob-to-icon mappings */
44 typedef struct _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 */
66 static time_t last_read
= (time_t) 0;
71 path
= choices_find_path_load("globicons", PROJECT
);
73 return; /* Nothing to load */
75 if (mc_stat(path
, &info
) == -1)
78 if (info
.st_mtime
<= last_read
)
79 goto out
; /* File hasn't been modified since we last read it */
83 g_list_foreach(glob_icons
, (GFunc
) free_globicon
, NULL
);
84 g_list_free(glob_icons
);
88 doc
= xmlParseFile(path
);
91 xmlNodePtr node
, icon
, root
;
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
)
102 if (strcmp(node
->name
, "rule") != 0)
104 icon
= get_subnode(node
, NULL
, "icon");
107 match
= xmlGetProp(node
, "match");
111 gi
= g_new(GlobIcon
, 1);
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
);
126 /* Handle the old non-XML format */
127 parse_file(path
, process_globicons_line
);
129 write_globicons(); /* Upgrade to new format */
132 last_read
= time(NULL
); /* Update time stamp */
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
)
144 g_return_if_fail(item
&& !item
->image
);
146 gi
= get_globicon_struct(path
);
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
);
160 g_free(gi
->iconpath
);
164 /* Write globicons file */
165 static void write_globicons(void)
169 gchar
*save_new
= NULL
;
170 xmlDocPtr doc
= NULL
;
172 save
= choices_find_path_save("globicons", PROJECT
, TRUE
);
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
;
188 tree
= xmlNewTextChild(xmlDocGetRootElement(doc
),
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)
201 out
= fopen(save_new
, "w");
204 xmlDocDump(out
, doc
); /* Some versions return void */
210 if (rename(save_new
, save
))
214 delayed_rox_error(_("Error saving globicons: %s"), g_strerror(errno
));
222 /* Process a globicon line. Format:
223 glob-pattern icon-path
225 /home/<*>/Mail /usr/local/icons/mailbox.xpm
226 (<*> represents a single asterisk, enclosed in brackets to not break
229 static char *process_globicons_line(guchar
*line
)
231 guchar
*pattern
, *iconpath
;
234 pattern
= strtok(line
, " \t");
235 /* We ignore empty lines, but they are no cause for a message */
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
252 glob_icons
= g_list_prepend(glob_icons
, gi
);
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
)
266 for (list
= glob_icons
; list
; list
= list
->next
)
268 GlobIcon
*gi
= (GlobIcon
*) list
->data
;
270 if (fnmatch(gi
->pattern
, path
, FNM_PATHNAME
) == 0)
274 /* If we get here, there is no corresponding globicon */
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
283 static void add_globicon(guchar
*path
, guchar
*icon
)
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
);
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
307 glob_icons
= g_list_prepend(glob_icons
, gi
);
310 /* Rewrite the globicons file */
313 /* Make sure any visible icons for the file are updated */
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
)
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
);
329 return; /* Not in the list */
331 glob_icons
= g_list_remove(glob_icons
, gi
);
332 free_globicon(gi
, NULL
);
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
,
344 GtkSelectionData
*selection_data
,
353 if (!selection_data
->data
)
356 uris
= uri_list_to_glist(selection_data
->data
);
358 if (g_list_length(uris
) == 1)
359 icon
= get_local_path((guchar
*) uris
->data
);
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."));
370 path
= gtk_object_get_data(GTK_OBJECT(dialog
), "pathname");
372 if (!set_icon_path(path
, icon
))
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
)
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
))
393 destroy_on_idle(dialog
);
396 /* Called if the user clicks on the "Remove custom icon" button */
397 static void remove_icon(GtkWidget
*dialog
)
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
)
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."));
424 /* Check if we can load the image, warn the user if not. */
425 pic
= g_fscache_lookup(pixmap_cache
, iconpath
);
429 _("Unable to load image file -- maybe it's not in a "
430 "format I understand, or maybe the permissions are "
432 "The icon has not been changed."));
435 g_fscache_data_unref(pixmap_cache
, pic
);
437 /* Add the globicon mapping and update visible icons */
438 add_globicon(filepath
, iconpath
);
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
)
449 GtkWidget
*dialog
, *vbox
, *frame
, *hbox
, *entry
, *label
, *button
;
450 GtkTargetEntry targets
[] = {
451 {"text/uri-list", 0, TARGET_URI_LIST
},
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
),
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);
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
),
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")),
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
),
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")),
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
),
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
),
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
),
553 gtk_widget_show_all(dialog
);
556 static void show_icon_help(gpointer data
)
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."));