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)
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 /* 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'.
42 #include "gui_support.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
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
,
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
);
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
)
136 g_return_if_fail(icon
!= NULL
);
138 image
= icon
->item
->image
;
139 flags
= icon
->item
->flags
;
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");
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
)
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
;
176 static void check_has(gpointer key
, GList
*icons
, CheckData
*check
)
180 g_return_if_fail(icons
!= NULL
);
184 if (is_sub_dir(icon
->path
, check
->path
))
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
)
200 g_hash_table_foreach(icons_hash
, (GHFunc
) check_has
, &check
);
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
)
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
,
222 menu_show_shift_action(file_shift_item
, icon
? icon
->item
: NULL
,
225 /* Shade the File/Edit/Show items unless an item was clicked */
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
);
241 /* Check for app-specific menu */
242 appmenu_add(icon
->path
, icon
->item
, icon_menu
);
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
)
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
);
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
;
281 icon_set_selected_int(select
, TRUE
);
283 to_clear
= g_list_copy(icon_selection
);
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)
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");
326 g_string_free(tmp
, FALSE
);
331 GType
icon_get_type(void)
333 static GType type
= 0;
337 static const GTypeInfo info
=
340 NULL
, /* base_init */
341 NULL
, /* base_finalise */
343 NULL
, /* class_finalise */
344 NULL
, /* class_data */
350 type
= g_type_register_static(G_TYPE_OBJECT
, "Icon",
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
)
366 icon_unhash_path(icon
);
367 icon
->src_path
= NULL
;
370 diritem_free(icon
->item
);
376 icon
->src_path
= g_strdup(pathname
);
377 icon
->path
= expand_path(pathname
);
379 icon_hash_path(icon
);
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
)
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
)
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
);
427 g_hash_table_insert(icons_hash
,
428 ((Icon
*) list
->data
)->path
, list
);
431 static void rename_activate(GtkWidget
*dialog
)
433 GtkWidget
*entry
, *src
;
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
&&
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')
450 _("The label must contain at least one character!"));
451 else if (*new_src
== '\0')
453 _("The location must contain at least one character!"));
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
)
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
,
483 if (info
== TARGET_URI_LIST
)
484 text
= icon_create_uri_list();
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
, ' ');
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
)
519 icon_set_selected(menu_icon
, TRUE
);
524 _("You must first select some items to remove"));
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
)
538 delayed_error(_("You must open the menu over an item"));
545 run_diritem(menu_icon
->path
, menu_icon
->item
,
549 show_rename_box(menu_icon
);
551 case ACTION_LOCATION
:
552 open_to_show(menu_icon
->path
);
555 show_item_help(menu_icon
->path
, menu_icon
->item
);
558 infobox_new(menu_icon
->path
);
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
);
566 _("You can only set the run action for a "
569 case ACTION_SET_ICON
:
570 icon_set_handler_dialog(menu_icon
->item
,
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
590 static void show_rename_box(Icon
*icon
)
593 GtkWidget
*label
, *entry
;
597 gtk_window_present(GTK_WINDOW(icon
->dialog
));
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
,
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
);
654 gtk_widget_destroy(icon
->dialog
);
656 if (icon
== menu_icon
)
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
)
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
;
678 icon
->same_group
= NULL
;
680 g_signal_new("update",
681 G_TYPE_FROM_CLASS(gclass
),
683 G_STRUCT_OFFSET(IconClass
, update
),
685 g_cclosure_marshal_VOID__VOID
,
688 g_signal_new("destroy",
689 G_TYPE_FROM_CLASS(gclass
),
691 G_STRUCT_OFFSET(IconClass
, destroy
),
693 g_cclosure_marshal_VOID__VOID
,
696 g_signal_new("redraw",
697 G_TYPE_FROM_CLASS(gclass
),
699 G_STRUCT_OFFSET(IconClass
, redraw
),
701 g_cclosure_marshal_VOID__VOID
,
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
),
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
);
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
;
720 /* Shift Open... label */
721 items
= gtk_container_get_children(GTK_CONTAINER(icon_file_menu
));
722 file_shift_item
= GTK_BIN(items
->data
)->child
;
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
;
740 /* As icon_set_selected(), but doesn't automatically unselect incompatible
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
)
753 primary
= gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE
));
757 icon_selection
= g_list_prepend(icon_selection
, icon
);
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
},
769 have_primary
= gtk_clipboard_set_with_data(primary
,
771 sizeof(target_table
) / sizeof(*target_table
),
772 selection_get
, lose_selection
, NULL
);
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
)
793 items
= gtk_container_get_children(GTK_CONTAINER(menu
));
795 item
= g_list_nth(items
, from
);
799 gtk_widget_hide(GTK_WIDGET(item
->data
));
801 gtk_widget_show(GTK_WIDGET(item
->data
));