r1492: Added a separator before 'Backdrop...'.
[rox-filer.git] / ROX-Filer / src / icon.c
bloba990f452d8118404c24646e8e9fabf401dfd89ef
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 /* icon.c - abstract base class for pinboard and panel icons.
24 * An Icon contains the full pathname of its file and the DirItem for that
25 * file. Icons emit the following signals:
27 * redraw - the image, details or selection state have changed.
28 * update - the name or path has changed.
29 * destroy - someone wishes to remove the icon.
31 * Note that an icon may be removed without emitting 'destroy'.
34 #include "config.h"
36 #include <string.h>
37 #include <gtk/gtk.h>
39 #include "global.h"
41 #include "main.h"
42 #include "gui_support.h"
43 #include "support.h"
44 #include "icon.h"
45 #include "diritem.h"
46 #include "menu.h"
47 #include "appmenu.h"
48 #include "dnd.h"
49 #include "run.h"
50 #include "infobox.h"
51 #include "pixmaps.h"
52 #include "mount.h"
53 #include "type.h"
54 #include "usericons.h"
55 #include "pinboard.h" /* For pinboard_set_backdrop */
57 static gboolean have_primary = FALSE; /* We own the PRIMARY selection? */
59 GtkWidget *icon_menu; /* The popup icon menu */
60 static GtkWidget *icon_file_menu; /* The file submenu */
61 static GtkWidget *icon_file_item; /* 'File' label */
62 static GtkWidget *file_shift_item; /* 'Shift Open' label */
64 /* A list of selected Icons. Every icon in the list is from the same group
65 * (eg, you can't have icons from two different panels selected at the
66 * same time).
68 GList *icon_selection = NULL;
70 /* Each entry is a GList of Icons which have the given pathname.
71 * This allows us to update all necessary icons when something changes.
73 static GHashTable *icons_hash = NULL; /* path -> [Icon] */
75 static Icon *menu_icon = NULL; /* Item clicked if there is no selection */
77 /* Static prototypes */
78 static void rename_activate(GtkWidget *dialog);
79 static void menu_closed(GtkWidget *widget);
80 static void lose_selection(GtkClipboard *primary, gpointer data);
81 static void selection_get(GtkClipboard *primary,
82 GtkSelectionData *selection_data,
83 guint info,
84 gpointer data);
85 static void remove_items(gpointer data, guint action, GtkWidget *widget);
86 static void set_backdrop(gpointer data, guint action, GtkWidget *widget);
87 static void file_op(gpointer data, guint action, GtkWidget *widget);
88 static void show_rename_box(Icon *icon);
89 static void icon_set_selected_int(Icon *icon, gboolean selected);
90 static void icon_class_init(gpointer gclass, gpointer data);
91 static void icon_init(GTypeInstance *object, gpointer gclass);
92 static void icon_hash_path(Icon *icon);
93 static void icon_unhash_path(Icon *icon);
94 static void menu_set_hidden(GtkWidget *menu, gboolean hidden, int from, int n);
96 enum {
97 ACTION_SHIFT,
98 ACTION_HELP,
99 ACTION_INFO,
100 ACTION_RUN_ACTION,
101 ACTION_SET_ICON,
102 ACTION_EDIT,
103 ACTION_LOCATION,
106 #undef N_
107 #define N_(x) x
108 static GtkItemFactoryEntry menu_def[] = {
109 {N_("ROX-Filer"), NULL, NULL, 0, "<Branch>"},
110 {">" N_("Help"), NULL, menu_rox_help, 0, NULL},
111 {">" N_("Options..."), NULL, menu_show_options, 0, NULL},
112 {">" N_("Home Directory"), NULL, open_home, 0, NULL},
113 {N_("File"), NULL, NULL, 0, "<Branch>"},
114 {">" N_("Shift Open"), NULL, file_op, ACTION_SHIFT, NULL},
115 {">" N_("Help"), NULL, file_op, ACTION_HELP, NULL},
116 {">" N_("Info"), NULL, file_op, ACTION_INFO, NULL},
117 {">" N_("Set Run Action..."), NULL, file_op, ACTION_RUN_ACTION, NULL},
118 {">" N_("Set Icon..."), NULL, file_op, ACTION_SET_ICON, NULL},
119 {N_("Edit Item"), NULL, file_op, ACTION_EDIT, NULL},
120 {N_("Show Location"), NULL, file_op, ACTION_LOCATION, NULL},
121 {N_("Remove Item(s)"), NULL, remove_items, 0, NULL},
122 {"", NULL, NULL, 0, "<Separator>"},
123 {N_("Backdrop..."), NULL, set_backdrop, 0, NULL},
126 /****************************************************************
127 * EXTERNAL INTERFACE *
128 ****************************************************************/
130 /* Called when the pointer moves over the icon */
131 void icon_may_update(Icon *icon)
133 MaskedPixmap *image;
134 int flags;
136 g_return_if_fail(icon != NULL);
138 image = icon->item->image;
139 flags = icon->item->flags;
141 if (image)
142 g_object_ref(image);
143 mount_update(FALSE);
144 diritem_restat(icon->path, icon->item, NULL);
146 if (icon->item->image != image || icon->item->flags != flags)
148 /* Appearance changed; need to redraw */
149 g_signal_emit_by_name(icon, "redraw");
152 if (image)
153 g_object_unref(image);
156 /* If path is on an icon then it may have changed... check! */
157 void icons_may_update(const gchar *path)
159 GList *affected;
161 if (icons_hash)
163 affected = g_hash_table_lookup(icons_hash, path);
165 for (; affected; affected = affected->next)
166 icon_may_update((Icon *) affected->data);
170 typedef struct _CheckData CheckData;
171 struct _CheckData {
172 const gchar *path;
173 gboolean found;
176 static void check_has(gpointer key, GList *icons, CheckData *check)
178 Icon *icon;
180 g_return_if_fail(icons != NULL);
182 icon = icons->data;
184 if (is_sub_dir(icon->path, check->path))
185 check->found = TRUE;
188 /* Returns TRUE if any icon links to this file (or any file inside
189 * this directory). Used to check that it's OK to delete 'path'.
191 gboolean icons_require(const gchar *path)
193 CheckData check;
195 if (!icons_hash)
196 return FALSE;
198 check.path = path;
199 check.found = FALSE;
200 g_hash_table_foreach(icons_hash, (GHFunc) check_has, &check);
202 return check.found;
205 /* Menu was clicked over this icon. Set things up correctly (shade items,
206 * add app menu stuff, etc).
207 * You should show icon_menu after calling this...
209 void icon_prepare_menu(Icon *icon, gboolean pinboard)
211 appmenu_remove();
213 menu_icon = icon;
215 menu_set_hidden(icon_menu, !pinboard, 5, 2);
217 /* Shade Remove Item(s) unless there is a selection */
218 menu_set_items_shaded(icon_menu,
219 (icon_selection || menu_icon) ? FALSE : TRUE,
220 4, 1);
222 menu_show_shift_action(file_shift_item, icon ? icon->item : NULL,
223 FALSE);
225 /* Shade the File/Edit/Show items unless an item was clicked */
226 if (icon)
228 guchar *tmp;
230 menu_set_items_shaded(icon_menu, FALSE, 1, 3);
231 menu_set_items_shaded(icon_file_menu, FALSE, 0, 6);
232 if (!can_set_run_action(icon->item))
233 menu_set_items_shaded(icon_file_menu, TRUE, 3, 1);
235 tmp = g_strdup_printf("%s '%s'",
236 basetype_name(icon->item),
237 icon->item->leafname);
238 gtk_label_set_text(GTK_LABEL(icon_file_item), tmp);
239 g_free(tmp);
241 /* Check for app-specific menu */
242 appmenu_add(icon->path, icon->item, icon_menu);
244 else
246 menu_set_items_shaded(icon_menu, TRUE, 1, 3);
247 menu_set_items_shaded(icon_file_menu, TRUE, 0, 6);
248 gtk_label_set_text(GTK_LABEL(icon_file_item), _("Nothing"));
252 /* Set whether this icon is selected. Will automatically clear the selection
253 * if it contains icons from a different group.
255 void icon_set_selected(Icon *icon, gboolean selected)
257 if (selected && icon_selection)
259 IconClass *iclass;
260 Icon *other = (Icon *) icon_selection->data;
262 iclass = (IconClass *) G_OBJECT_GET_CLASS(icon);
263 if (!iclass->same_group(icon, other))
265 icon_select_only(icon);
266 return;
270 icon_set_selected_int(icon, selected);
273 /* Clear everything, except 'select', which is selected.
274 * If select is NULL, unselects everything.
276 void icon_select_only(Icon *select)
278 GList *to_clear, *next;
280 if (select)
281 icon_set_selected_int(select, TRUE);
283 to_clear = g_list_copy(icon_selection);
285 if (select)
286 to_clear = g_list_remove(to_clear, select);
288 for (next = to_clear; next; next = next->next)
289 icon_set_selected_int((Icon *) next->data, FALSE);
291 g_list_free(to_clear);
294 /* Destroys this icon's widget, causing it to be removed from the screen */
295 void icon_destroy(Icon *icon)
297 g_return_if_fail(icon != NULL);
299 icon_set_selected_int(icon, FALSE);
301 g_signal_emit_by_name(icon, "destroy");
304 /* Return a text/uri-list of all the icons in the selection */
305 gchar *icon_create_uri_list(void)
307 GString *tmp;
308 guchar *retval;
309 guchar *leader;
310 GList *next;
312 tmp = g_string_new(NULL);
313 leader = g_strdup_printf("file://%s", our_host_name_for_dnd());
315 for (next = icon_selection; next; next = next->next)
317 Icon *icon = (Icon *) next->data;
319 g_string_append(tmp, leader);
320 g_string_append(tmp, icon->path);
321 g_string_append(tmp, "\r\n");
324 g_free(leader);
325 retval = tmp->str;
326 g_string_free(tmp, FALSE);
328 return retval;
331 GType icon_get_type(void)
333 static GType type = 0;
335 if (!type)
337 static const GTypeInfo info =
339 sizeof (IconClass),
340 NULL, /* base_init */
341 NULL, /* base_finalise */
342 icon_class_init,
343 NULL, /* class_finalise */
344 NULL, /* class_data */
345 sizeof(Icon),
346 0, /* n_preallocs */
347 icon_init
350 type = g_type_register_static(G_TYPE_OBJECT, "Icon",
351 &info, 0);
354 return type;
357 /* Sets, unsets or changes the pathname and name for an icon.
358 * Updates icons_hash and (re)stats the item.
359 * If name is NULL then gets the leafname from pathname.
360 * If pathname is NULL, frees everything.
362 void icon_set_path(Icon *icon, const char *pathname, const char *name)
364 if (icon->path)
366 icon_unhash_path(icon);
367 icon->src_path = NULL;
368 icon->path = NULL;
370 diritem_free(icon->item);
371 icon->item = NULL;
374 if (pathname)
376 icon->src_path = g_strdup(pathname);
377 icon->path = expand_path(pathname);
379 icon_hash_path(icon);
381 if (!name)
382 name = g_basename(pathname);
384 icon->item = diritem_new(name);
385 diritem_restat(icon->path, icon->item, NULL);
389 /****************************************************************
390 * INTERNAL FUNCTIONS *
391 ****************************************************************/
393 /* The icons_hash table allows us to convert from a path to a list
394 * of icons that use that path.
395 * Add this icon to the list for its path.
397 static void icon_hash_path(Icon *icon)
399 GList *list;
401 g_return_if_fail(icon != NULL);
403 /* g_print("[ hashing '%s' ]\n", icon->path); */
405 list = g_hash_table_lookup(icons_hash, icon->path);
406 list = g_list_prepend(list, icon);
407 g_hash_table_insert(icons_hash, icon->path, list);
410 /* Remove this icon from the icons_hash table */
411 static void icon_unhash_path(Icon *icon)
413 GList *list;
415 g_return_if_fail(icon != NULL);
417 /* g_print("[ unhashing '%s' ]\n", icon->path); */
419 list = g_hash_table_lookup(icons_hash, icon->path);
420 g_return_if_fail(list != NULL);
422 list = g_list_remove(list, icon);
424 /* Remove it first; the hash key may have changed address */
425 g_hash_table_remove(icons_hash, icon->path);
426 if (list)
427 g_hash_table_insert(icons_hash,
428 ((Icon *) list->data)->path, list);
431 static void rename_activate(GtkWidget *dialog)
433 GtkWidget *entry, *src;
434 Icon *icon;
435 const guchar *new_name, *new_src;
437 entry = g_object_get_data(G_OBJECT(dialog), "new_name");
438 icon = g_object_get_data(G_OBJECT(dialog), "callback_icon");
439 src = g_object_get_data(G_OBJECT(dialog), "new_path");
441 g_return_if_fail(entry != NULL &&
442 src != NULL &&
443 icon != NULL);
445 new_name = gtk_entry_get_text(GTK_ENTRY(entry));
446 new_src = gtk_entry_get_text(GTK_ENTRY(src));
448 if (*new_name == '\0')
449 report_error(
450 _("The label must contain at least one character!"));
451 else if (*new_src == '\0')
452 report_error(
453 _("The location must contain at least one character!"));
454 else
456 icon_set_path(icon, new_src, new_name);
457 g_signal_emit_by_name(icon, "update");
458 gtk_widget_destroy(dialog);
462 static void menu_closed(GtkWidget *widget)
464 appmenu_remove();
465 menu_icon = NULL;
468 /* Called when another application takes the selection away from us */
469 static void lose_selection(GtkClipboard *primary, gpointer data)
471 have_primary = FALSE;
472 icon_select_only(NULL);
475 /* Called when another application wants the contents of our selection */
476 static void selection_get(GtkClipboard *primary,
477 GtkSelectionData *selection_data,
478 guint info,
479 gpointer data)
481 gchar *text;
483 if (info == TARGET_URI_LIST)
484 text = icon_create_uri_list();
485 else
487 GList *next;
488 GString *str;
490 str = g_string_new(NULL);
492 for (next = icon_selection; next; next = next->next)
494 Icon *icon = (Icon *) next->data;
496 g_string_append(str, icon->path);
497 g_string_append_c(str, ' ');
500 text = str->str;
501 g_string_free(str, FALSE);
504 gtk_selection_data_set(selection_data,
505 gdk_atom_intern("STRING", FALSE),
506 8, text, strlen(text));
509 static void set_backdrop(gpointer data, guint action, GtkWidget *widget)
511 pinboard_set_backdrop();
514 static void remove_items(gpointer data, guint action, GtkWidget *widget)
516 IconClass *iclass;
518 if (menu_icon)
519 icon_set_selected(menu_icon, TRUE);
521 if (!icon_selection)
523 delayed_error(
524 _("You must first select some items to remove"));
525 return;
528 iclass = (IconClass *)
529 G_OBJECT_GET_CLASS(G_OBJECT(icon_selection->data));
531 iclass->remove_items();
534 static void file_op(gpointer data, guint action, GtkWidget *widget)
536 if (!menu_icon)
538 delayed_error(_("You must open the menu over an item"));
539 return;
542 switch (action)
544 case ACTION_SHIFT:
545 run_diritem(menu_icon->path, menu_icon->item,
546 NULL, NULL, TRUE);
547 break;
548 case ACTION_EDIT:
549 show_rename_box(menu_icon);
550 break;
551 case ACTION_LOCATION:
552 open_to_show(menu_icon->path);
553 break;
554 case ACTION_HELP:
555 show_item_help(menu_icon->path, menu_icon->item);
556 break;
557 case ACTION_INFO:
558 infobox_new(menu_icon->path);
559 break;
560 case ACTION_RUN_ACTION:
561 if (can_set_run_action(menu_icon->item))
562 type_set_handler_dialog(
563 menu_icon->item->mime_type);
564 else
565 report_error(
566 _("You can only set the run action for a "
567 "regular file"));
568 break;
569 case ACTION_SET_ICON:
570 icon_set_handler_dialog(menu_icon->item,
571 menu_icon->path);
572 break;
576 static void edit_response(GtkWidget *dialog, gint response, gpointer data)
578 if (response == GTK_RESPONSE_OK)
579 rename_activate(dialog);
580 else if (response == GTK_RESPONSE_CANCEL)
581 gtk_widget_destroy(dialog);
584 /* Opens a box allowing the user to change the name of a pinned icon.
585 * If the icon is destroyed then the box will close.
586 * If the user chooses OK then the callback is called once the icon's
587 * name, src_path and path fields have been updated and the item field
588 * restatted.
590 static void show_rename_box(Icon *icon)
592 GtkDialog *dialog;
593 GtkWidget *label, *entry;
595 if (icon->dialog)
597 gtk_window_present(GTK_WINDOW(icon->dialog));
598 return;
601 icon->dialog = gtk_dialog_new();
602 g_signal_connect(icon->dialog, "destroy",
603 G_CALLBACK(gtk_widget_destroyed), &icon->dialog);
605 dialog = GTK_DIALOG(icon->dialog);
607 gtk_window_set_title(GTK_WINDOW(dialog), _("Edit Item"));
608 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
610 label = gtk_label_new(_("Clicking the icon opens:"));
611 gtk_box_pack_start(GTK_BOX(dialog->vbox), label, TRUE, TRUE, 0);
613 entry = gtk_entry_new();
614 gtk_box_pack_start(GTK_BOX(dialog->vbox), entry, TRUE, FALSE, 2);
615 gtk_entry_set_text(GTK_ENTRY(entry), icon->src_path);
616 g_object_set_data(G_OBJECT(dialog), "new_path", entry);
617 g_signal_connect_swapped(entry, "activate",
618 G_CALLBACK(rename_activate), dialog);
620 gtk_box_pack_start(GTK_BOX(dialog->vbox),
621 gtk_hseparator_new(), TRUE, TRUE, 2);
623 label = gtk_label_new(_("The text displayed under the icon is:"));
624 gtk_box_pack_start(GTK_BOX(dialog->vbox), label, TRUE, TRUE, 0);
625 entry = gtk_entry_new();
626 gtk_box_pack_start(GTK_BOX(dialog->vbox), entry, TRUE, FALSE, 2);
627 gtk_entry_set_text(GTK_ENTRY(entry), icon->item->leafname);
628 gtk_widget_grab_focus(entry);
629 g_object_set_data(G_OBJECT(dialog), "new_name", entry);
630 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
632 g_object_set_data(G_OBJECT(dialog), "callback_icon", icon);
634 gtk_dialog_add_buttons(dialog,
635 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
636 GTK_STOCK_OK, GTK_RESPONSE_OK,
637 NULL);
638 gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
640 g_signal_connect(dialog, "response", G_CALLBACK(edit_response), NULL);
642 gtk_widget_show_all(GTK_WIDGET(dialog));
645 static gpointer parent_class = NULL;
647 static void icon_finialize(GObject *object)
649 Icon *icon = (Icon *) object;
651 g_return_if_fail(!icon->selected);
653 if (icon->dialog)
654 gtk_widget_destroy(icon->dialog);
656 if (icon == menu_icon)
657 menu_icon = NULL;
659 icon_set_path(icon, NULL, NULL);
661 G_OBJECT_CLASS(parent_class)->finalize(object);
664 static void icon_class_init(gpointer gclass, gpointer data)
666 guchar *tmp;
667 GList *items;
668 GtkItemFactory *item_factory;
669 GObjectClass *object = (GObjectClass *) gclass;
670 IconClass *icon = (IconClass *) gclass;
672 parent_class = g_type_class_peek_parent(gclass);
674 object->finalize = icon_finialize;
675 icon->destroy = NULL;
676 icon->redraw = NULL;
677 icon->update = NULL;
678 icon->same_group = NULL;
680 g_signal_new("update",
681 G_TYPE_FROM_CLASS(gclass),
682 G_SIGNAL_RUN_LAST,
683 G_STRUCT_OFFSET(IconClass, update),
684 NULL, NULL,
685 g_cclosure_marshal_VOID__VOID,
686 G_TYPE_NONE, 0);
688 g_signal_new("destroy",
689 G_TYPE_FROM_CLASS(gclass),
690 G_SIGNAL_RUN_LAST,
691 G_STRUCT_OFFSET(IconClass, destroy),
692 NULL, NULL,
693 g_cclosure_marshal_VOID__VOID,
694 G_TYPE_NONE, 0);
696 g_signal_new("redraw",
697 G_TYPE_FROM_CLASS(gclass),
698 G_SIGNAL_RUN_LAST,
699 G_STRUCT_OFFSET(IconClass, redraw),
700 NULL, NULL,
701 g_cclosure_marshal_VOID__VOID,
702 G_TYPE_NONE, 0);
704 icons_hash = g_hash_table_new(g_str_hash, g_str_equal);
706 item_factory = menu_create(menu_def,
707 sizeof(menu_def) / sizeof(*menu_def),
708 "<icon>", NULL);
710 tmp = g_strconcat("<icon>/", _("File"), NULL);
711 icon_menu = gtk_item_factory_get_widget(item_factory, "<icon>");
712 icon_file_menu = gtk_item_factory_get_widget(item_factory, tmp);
713 g_free(tmp);
715 /* File '' label... */
716 items = gtk_container_get_children(GTK_CONTAINER(icon_menu));
717 icon_file_item = GTK_BIN(g_list_nth(items, 1)->data)->child;
718 g_list_free(items);
720 /* Shift Open... label */
721 items = gtk_container_get_children(GTK_CONTAINER(icon_file_menu));
722 file_shift_item = GTK_BIN(items->data)->child;
723 g_list_free(items);
725 g_signal_connect(icon_menu, "unmap_event",
726 G_CALLBACK(menu_closed), NULL);
729 static void icon_init(GTypeInstance *object, gpointer gclass)
731 Icon *icon = (Icon *) object;
733 icon->selected = FALSE;
734 icon->src_path = NULL;
735 icon->path = NULL;
736 icon->item = NULL;
737 icon->dialog = NULL;
740 /* As icon_set_selected(), but doesn't automatically unselect incompatible
741 * icons.
743 static void icon_set_selected_int(Icon *icon, gboolean selected)
745 static GtkClipboard *primary;
747 g_return_if_fail(icon != NULL);
749 if (icon->selected == selected)
750 return;
752 if (!primary)
753 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
755 if (selected)
757 icon_selection = g_list_prepend(icon_selection, icon);
758 if (!have_primary)
760 GtkTargetEntry target_table[] =
762 {"text/uri-list", 0, TARGET_URI_LIST},
763 {"UTF8", 0, TARGET_STRING},
764 {"COMPOUND_TEXT", 0, TARGET_STRING},
765 {"STRING", 0, TARGET_STRING},
768 /* Grab selection */
769 have_primary = gtk_clipboard_set_with_data(primary,
770 target_table,
771 sizeof(target_table) / sizeof(*target_table),
772 selection_get, lose_selection, NULL);
775 else
777 icon_selection = g_list_remove(icon_selection, icon);
778 if (have_primary && !icon_selection)
780 have_primary = FALSE;
781 gtk_clipboard_clear(primary);
785 icon->selected = selected;
786 g_signal_emit_by_name(icon, "redraw");
789 static void menu_set_hidden(GtkWidget *menu, gboolean hidden, int from, int n)
791 GList *items, *item;
793 items = gtk_container_get_children(GTK_CONTAINER(menu));
795 item = g_list_nth(items, from);
796 while (item && n--)
798 if (hidden)
799 gtk_widget_hide(GTK_WIDGET(item->data));
800 else
801 gtk_widget_show(GTK_WIDGET(item->data));
802 item = item->next;
804 g_list_free(items);