r1322: Converted MaskedPixmap to GObject.
[rox-filer.git] / ROX-Filer / src / usericons.c
blob1790025a6821eecc0e3af8a8cc508cbe8efb7669
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 #define DELETE_ICON 1
57 static GHashTable *glob_icons = NULL; /* Pathname -> Icon pathname */
59 /* Static prototypes */
60 static const char *process_globicons_line(gchar *line);
61 static gboolean free_globicon(gpointer key, gpointer value, gpointer data);
62 static void get_path_set_icon(GtkWidget *dialog);
63 static void show_icon_help(gpointer data);
64 static void write_globicons(void);
65 static void show_current_dirs_menu(GtkWidget *button, gpointer data);
66 static void add_globicon(const gchar *path, const gchar *icon);
67 static void drag_icon_dropped(GtkWidget *frame,
68 GdkDragContext *context,
69 gint x,
70 gint y,
71 GtkSelectionData *selection_data,
72 guint info,
73 guint32 time,
74 GtkWidget *dialog);
75 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
76 gboolean just_media);
77 static void delete_globicon(const gchar *path);
79 /****************************************************************
80 * EXTERNAL INTERFACE *
81 ****************************************************************/
83 /* Read glob-pattern -> icon mappings from "globicons" in Choices */
84 void read_globicons()
86 static time_t last_read = (time_t) 0;
87 struct stat info;
88 guchar *path;
89 xmlDocPtr doc;
91 if (!glob_icons)
92 glob_icons = g_hash_table_new(g_str_hash, g_str_equal);
94 path = choices_find_path_load("globicons", PROJECT);
95 if (!path)
96 return; /* Nothing to load */
98 if (mc_stat(path, &info) == -1)
99 goto out;
101 if (info.st_mtime <= last_read)
102 goto out; /* File hasn't been modified since we last read it */
104 g_hash_table_foreach_remove(glob_icons, free_globicon, NULL);
106 doc = xmlParseFile(path);
107 if (doc)
109 xmlNodePtr node, icon, root;
110 char *match;
112 root = xmlDocGetRootElement(doc);
114 /* Handle the new XML file format */
115 for (node = root->xmlChildrenNode; node; node = node->next)
117 gchar *path, *icon_path;
119 if (node->type != XML_ELEMENT_NODE)
120 continue;
121 if (strcmp(node->name, "rule") != 0)
122 continue;
123 icon = get_subnode(node, NULL, "icon");
124 if (!icon)
125 continue;
126 match = xmlGetProp(node, "match");
127 if (!match)
128 continue;
130 icon_path = xmlNodeGetContent(icon);
131 path = icon_convert_path(match);
132 g_hash_table_insert(glob_icons, path, icon_path);
133 g_free(match);
136 xmlFreeDoc(doc);
138 else
140 /* Handle the old non-XML format */
141 parse_file(path, process_globicons_line);
142 if (g_hash_table_size(glob_icons))
143 write_globicons(); /* Upgrade to new format */
146 last_read = time(NULL); /* Update time stamp */
147 out:
148 g_free(path);
151 /* Set an item's image field according to the globicons patterns if
152 * it matches one of them and the file exists.
154 void check_globicon(const guchar *path, DirItem *item)
156 gchar *gi;
158 g_return_if_fail(item && !item->image);
160 gi = g_hash_table_lookup(glob_icons, path);
161 if (gi)
162 item->image = g_fscache_lookup(pixmap_cache, gi);
165 /* Add a globicon mapping for the given file to the given icon path */
166 gboolean set_icon_path(const guchar *filepath, const guchar *iconpath)
168 struct stat icon;
169 MaskedPixmap *pic;
171 /* Check if file exists */
172 if (!mc_stat(iconpath, &icon) == 0) {
173 delayed_error(_("The pathname you gave does not exist. "
174 "The icon has not been changed."));
175 return FALSE;
178 /* Check if we can load the image, warn the user if not. */
179 pic = g_fscache_lookup(pixmap_cache, iconpath);
180 if (!pic)
182 delayed_error(
183 _("Unable to load image file -- maybe it's not in a "
184 "format I understand, or maybe the permissions are "
185 "wrong?\n"
186 "The icon has not been changed."));
187 return FALSE;
189 g_fscache_data_unref(pixmap_cache, pic);
191 /* Add the globicon mapping and update visible icons */
192 add_globicon(filepath, iconpath);
194 return TRUE;
197 static void dialog_response(GtkWidget *dialog, gint response, gpointer data)
199 if (response == GTK_RESPONSE_OK)
200 get_path_set_icon(dialog);
201 else if (response == GTK_RESPONSE_CANCEL)
202 gtk_widget_destroy(dialog);
203 else if (response == DELETE_ICON)
205 const gchar *path;
207 path = g_object_get_data(G_OBJECT(dialog), "pathname");
208 g_return_if_fail(path != NULL);
210 delete_globicon(path);
212 gtk_widget_destroy(dialog);
216 /* Display a dialog box allowing the user to set the icon for
217 * a file or directory.
219 void icon_set_handler_dialog(DirItem *item, const guchar *path)
221 guchar *tmp;
222 GtkDialog *dialog;
223 GtkWidget *frame, *hbox, *vbox2;
224 GtkWidget *entry, *label, *button, *align, *icon;
225 GtkWidget **radio;
226 GtkTargetEntry targets[] = {
227 {"text/uri-list", 0, TARGET_URI_LIST},
229 char *gi;
231 g_return_if_fail(item != NULL && path != NULL);
233 gi = g_hash_table_lookup(glob_icons, path);
235 dialog = GTK_DIALOG(gtk_dialog_new());
236 gtk_dialog_set_has_separator(dialog, FALSE);
237 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
238 g_object_set_data_full(G_OBJECT(dialog), "pathname",
239 strdup(path), g_free);
241 gtk_window_set_title(GTK_WINDOW(dialog), _("Set icon"));
243 radio = g_new(GtkWidget *, 3);
245 tmp = g_strdup_printf(_("Set icon for all `%s/<anything>'"),
246 item->mime_type->media_type);
247 radio[2] = gtk_radio_button_new_with_label(NULL, tmp);
248 gtk_box_pack_start(GTK_BOX(dialog->vbox), radio[2], FALSE, TRUE, 0);
249 g_free(tmp);
251 tmp = g_strdup_printf(_("Only for the type `%s/%s'"),
252 item->mime_type->media_type,
253 item->mime_type->subtype);
254 radio[1] = gtk_radio_button_new_with_label_from_widget(
255 GTK_RADIO_BUTTON(radio[2]), tmp);
256 gtk_box_pack_start(GTK_BOX(dialog->vbox), radio[1], FALSE, TRUE, 0);
257 g_free(tmp);
259 tmp = g_strdup_printf(_("Only for the file `%s'"), path);
260 radio[0] = gtk_radio_button_new_with_label_from_widget(
261 GTK_RADIO_BUTTON(radio[2]), tmp);
262 gtk_box_pack_start(GTK_BOX(dialog->vbox), radio[0], FALSE, TRUE, 0);
263 g_free(tmp);
265 g_object_set_data_full(G_OBJECT(dialog), "radios", radio, g_free);
266 g_object_set_data(G_OBJECT(dialog), "mime_type", item->mime_type);
268 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE);
270 frame = gtk_frame_new(NULL);
271 gtk_box_pack_start(GTK_BOX(dialog->vbox), frame, TRUE, TRUE, 4);
272 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
273 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
275 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
276 targets, sizeof(targets) / sizeof(*targets),
277 GDK_ACTION_COPY);
278 g_signal_connect(frame, "drag_data_received",
279 G_CALLBACK(drag_icon_dropped), dialog);
281 vbox2 = gtk_vbox_new(FALSE, 0);
282 gtk_container_add(GTK_CONTAINER(frame), vbox2);
284 label = gtk_label_new(_("Drop an icon file here"));
285 gtk_misc_set_padding(GTK_MISC(label), 10, 10);
286 gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0);
287 align = gtk_alignment_new(1, 1, 0, 0);
288 gtk_box_pack_start(GTK_BOX(vbox2), align, FALSE, TRUE, 0);
289 button = gtk_button_new();
290 gtk_container_add(GTK_CONTAINER(align), button);
291 icon = gtk_image_new_from_pixmap(im_dirs->pixmap, im_dirs->mask);
292 gtk_container_add(GTK_CONTAINER(button), icon);
293 gtk_tooltips_set_tip(tooltips, button,
294 _("Menu of directories previously used for icons"),
295 NULL);
296 g_signal_connect(button, "clicked",
297 G_CALLBACK(show_current_dirs_menu), NULL);
299 hbox = gtk_hbox_new(FALSE, 4);
300 gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, FALSE, TRUE, 4);
301 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
302 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("OR")),
303 FALSE, TRUE, 0);
304 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
306 hbox = gtk_hbox_new(FALSE, 4);
307 gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, FALSE, TRUE, 0);
309 label = gtk_label_new(_("Enter the path of an icon file:")),
310 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
311 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
313 gtk_box_pack_start(GTK_BOX(hbox),
314 new_help_button(show_icon_help, NULL), FALSE, TRUE, 0);
316 entry = gtk_entry_new();
317 /* Set the current icon as the default text if there is one */
318 if (gi)
319 gtk_entry_set_text(GTK_ENTRY(entry), gi);
321 gtk_box_pack_start(GTK_BOX(dialog->vbox), entry, FALSE, TRUE, 0);
322 gtk_widget_grab_focus(entry);
323 g_object_set_data(G_OBJECT(dialog), "icon_path", entry);
324 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
326 button = button_new_mixed(GTK_STOCK_DELETE, _("_Remove"));
327 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
328 gtk_dialog_add_action_widget(dialog, button, DELETE_ICON);
329 gtk_dialog_add_buttons(dialog,
330 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
331 GTK_STOCK_OK, 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 static gboolean free_globicon(gpointer key, gpointer value, gpointer data)
346 g_free(key);
347 g_free(value);
349 return TRUE; /* For g_hash_table_foreach_remove() */
352 static void write_globicon(gpointer key, gpointer value, gpointer data)
354 xmlNodePtr doc = (xmlNodePtr) data;
355 xmlNodePtr tree;
357 tree = xmlNewTextChild(doc, NULL, "rule", NULL);
358 xmlSetProp(tree, "match", key);
359 xmlNewChild(tree, NULL, "icon", value);
362 /* Write globicons file */
363 static void write_globicons(void)
365 gchar *save = NULL, *save_new = NULL;
366 xmlDocPtr doc = NULL;
368 save = choices_find_path_save("globicons", PROJECT, TRUE);
370 if (!save)
371 return; /* Saving is disabled */
373 save_new = g_strconcat(save, ".new", NULL);
375 doc = xmlNewDoc("1.0");
376 xmlDocSetRootElement(doc,
377 xmlNewDocNode(doc, NULL, "special-files", NULL));
379 g_hash_table_foreach(glob_icons, write_globicon,
380 xmlDocGetRootElement(doc));
382 if (save_xml_file(doc, save_new) || rename(save_new, save))
383 delayed_error(_("Error saving %s: %s"),
384 save, g_strerror(errno));
386 g_free(save_new);
387 g_free(save);
389 if (doc)
390 xmlFreeDoc(doc);
393 /* Process a globicon line. Format:
394 glob-pattern icon-path
395 Example:
396 /home/<*>/Mail /usr/local/icons/mailbox.xpm
397 (<*> represents a single asterisk, enclosed in brackets to not break
398 the C comment).
400 static const char *process_globicons_line(gchar *line)
402 guchar *pattern, *iconpath;
404 pattern = strtok(line, " \t");
405 /* We ignore empty lines, but they are no cause for a message */
406 if (pattern == NULL)
407 return NULL;
409 iconpath = strtok(NULL, " \t");
411 /* If there is no icon, then we worry */
412 g_return_val_if_fail(iconpath != NULL,
413 "Invalid line in globicons: no icon specified");
415 g_hash_table_insert(glob_icons, g_strdup(pattern), g_strdup(iconpath));
417 return NULL;
420 /* Add a globicon entry to the list. If another one with the same
421 * path exists, it is replaced. Otherwise, the new entry is
422 * added to the top of the list (so that it takes precedence over
423 * other entries).
425 static void add_globicon(const gchar *path, const gchar *icon)
427 g_hash_table_insert(glob_icons, g_strdup(path), g_strdup(icon));
429 /* Rewrite the globicons file */
430 write_globicons();
432 /* Make sure any visible icons for the file are updated */
433 examine(path);
436 /* Remove the globicon for a certain path */
437 static void delete_globicon(const gchar *path)
439 gpointer key, value;
441 if (!g_hash_table_lookup_extended(glob_icons, path, &key, &value))
442 return;
444 g_hash_table_remove(glob_icons, path);
446 g_free(key);
447 g_free(value);
449 write_globicons();
450 examine(path);
453 /* Set the icon for this dialog's file to 'icon' */
454 static void do_set_icon(GtkWidget *dialog, const gchar *icon)
456 guchar *path = NULL;
457 GtkToggleButton **radio;
459 path = g_object_get_data(G_OBJECT(dialog), "pathname");
460 g_return_if_fail(path != NULL);
462 radio = g_object_get_data(G_OBJECT(dialog), "radios");
463 g_return_if_fail(radio != NULL);
465 if (gtk_toggle_button_get_active(radio[0]))
467 if (!set_icon_path(path, icon))
468 return;
470 else
472 gboolean just_media;
473 MIME_type *type;
475 type = g_object_get_data(G_OBJECT(dialog), "mime_type");
476 just_media = gtk_toggle_button_get_active(radio[2]);
478 if (!set_icon_for_type(type, icon, just_media))
479 return;
482 destroy_on_idle(dialog);
485 /* Called when a URI list is dropped onto the box in the Set Icon
486 * dialog. Make that the default icon.
488 static void drag_icon_dropped(GtkWidget *frame,
489 GdkDragContext *context,
490 gint x,
491 gint y,
492 GtkSelectionData *selection_data,
493 guint info,
494 guint32 time,
495 GtkWidget *dialog)
497 GList *uris, *next;
498 guchar *icon = NULL;
500 if (!selection_data->data)
501 return;
503 uris = uri_list_to_glist(selection_data->data);
505 if (g_list_length(uris) == 1)
506 icon = g_strdup(get_local_path((guchar *) uris->data));
508 for (next = uris; next; next = next->next)
509 g_free(next->data);
510 g_list_free(uris);
512 if (!icon)
514 delayed_error(_("You should drop a single local icon file "
515 "onto the drop box - that icon will be "
516 "used for this file from now on."));
517 return;
520 do_set_icon(dialog, icon);
522 g_free(icon);
525 /* Called if the user clicks on the OK button on the Set Icon dialog */
526 static void get_path_set_icon(GtkWidget *dialog)
528 GtkEntry *entry;
529 const gchar *icon;
531 entry = g_object_get_data(G_OBJECT(dialog), "icon_path");
532 g_return_if_fail(entry != NULL);
534 icon = gtk_entry_get_text(entry);
536 do_set_icon(dialog, icon);
539 static void show_icon_help(gpointer data)
541 report_error(
542 _("Enter the full path of a file that contains a valid "
543 "image to be used as the icon for this file or directory."));
546 /* Set the icon for the given MIME type. We copy the file. */
547 static gboolean set_icon_for_type(MIME_type *type, const gchar *iconpath,
548 gboolean just_media)
550 gchar *target;
551 gchar *leaf;
552 gchar *dir;
553 GList *paths;
555 /* XXX: Should convert to XPM format... */
557 if (just_media)
558 leaf = g_strconcat(type->media_type, ".xpm", NULL);
559 else
560 leaf = g_strconcat(type->media_type, "_", type->subtype,
561 ".xpm", NULL);
563 target = choices_find_path_save(leaf, "MIME-icons", TRUE);
564 if (!target)
566 delayed_error(_("Setting icon disabled by CHOICESPATH"));
567 g_free(leaf);
568 return FALSE;
571 dir = g_dirname(target);
572 paths = g_list_append(NULL, (gchar *) iconpath);
574 action_copy(paths, dir, leaf, -1);
576 g_free(leaf);
577 g_free(dir);
578 g_free(target);
579 g_list_free(paths);
581 return TRUE;
584 static void get_dir(gpointer key, gpointer value, gpointer data)
586 GHashTable *names = (GHashTable *) data;
587 gchar *dir;
589 dir = g_dirname(value); /* Freed in add_dir_to_menu */
590 if (dir)
592 g_hash_table_insert(names, dir, NULL);
596 static void open_icon_dir(GtkMenuItem *item, gpointer data)
598 FilerWindow *filer;
599 const char *dir;
601 dir = gtk_label_get_text(GTK_LABEL(GTK_BIN(item)->child));
602 filer = filer_opendir(dir, NULL);
603 if (filer)
604 display_set_thumbs(filer, TRUE);
607 static void add_dir_to_menu(gpointer key, gpointer value, gpointer data)
609 GtkMenuShell *menu = (GtkMenuShell *) data;
610 GtkWidget *item;
612 item = gtk_menu_item_new_with_label(key);
613 gtk_widget_set_accel_path(item, NULL, NULL); /* XXX */
614 g_signal_connect(item, "activate",
615 G_CALLBACK(open_icon_dir), NULL);
616 g_free(key);
617 gtk_menu_shell_append(menu, item);
620 static void show_current_dirs_menu(GtkWidget *button, gpointer data)
622 GHashTable *names;
623 GtkWidget *menu;
625 names = g_hash_table_new(g_str_hash, g_str_equal);
627 g_hash_table_foreach(glob_icons, get_dir, names);
628 if (g_hash_table_size(glob_icons) == 0)
630 /* TODO: Include MIME-icons? */
631 delayed_error(_("You have not yet set any special icons; "
632 "therefore, I have no directories to show you"));
633 return;
636 menu = gtk_menu_new();
638 g_hash_table_foreach(names, add_dir_to_menu, menu);
640 g_hash_table_destroy(names);
642 show_popup_menu(menu, gtk_get_current_event(), 0);