* Only count the items in the trash when the monitor callback tells us to. * Listen to drag_motion and drag_leave signals so we know when the user is about to drop something on the applet, so we can use the "gnome-fs-trash-empty-accept" icon if available. * Fix up trash_aplet_update_icon() to only update the icon if it has changed. * When the icon theme, orientation or size changes, set the icon state to UNKNOWN so that trash_applet_update_icon() will really update it. * Simplify item count function to just count the toplevel items. This matches Nautilus's behaviour. * When the trash is empty, disable the "empty trash" context menu item. * Show a warning dialog before emptying the trash. This dialog is not shown if the warning dialog is disabled in Nautilus. * Fix the user data passed to context menu callbacks. -- James Henstridge Index: src/trashapplet.c =================================================================== RCS file: /cvsroot/trashapplet/trashapplet/src/trashapplet.c,v retrieving revision 1.7 diff -u -p -r1.7 trashapplet.c --- src/trashapplet.c 30 Jul 2004 14:19:07 -0000 1.7 +++ src/trashapplet.c 10 Sep 2004 03:17:17 -0000 @@ -34,7 +34,7 @@ #include #include -#include +#include #include "trashapplet.h" static void trash_applet_do_empty (BonoboUIComponent *component, @@ -51,7 +51,7 @@ static void trash_applet_show_help (Bono const gchar *cname); static gboolean trash_applet_is_empty (TrashApplet *trash_applet); -static gboolean trash_applet_update_icon (TrashApplet *trash_applet); +static void trash_applet_update_icon (TrashApplet *trash_applet); static void trash_applet_update_tip (TrashApplet *trash_applet); static const BonoboUIVerb trash_applet_menu_verbs [] = { @@ -69,6 +69,8 @@ static GtkTargetEntry drop_types[] = static gint n_drop_types = sizeof (drop_types) / sizeof (drop_types[0]); +static GConfClient *client = NULL; + /* TODO - Must HIGgify this dialog */ static void error_dialog (TrashApplet *ta, const gchar *error, ...) @@ -132,7 +134,7 @@ get_dir_count (const GnomeVFSURI *path_u gchar *path; guint retval = 0; - g_return_val_if_fail (path != NULL, 0); + g_return_val_if_fail (path_uri != NULL, 0); path = gnome_vfs_uri_to_string (path_uri, GNOME_VFS_URI_HIDE_NONE); @@ -155,17 +157,6 @@ get_dir_count (const GnomeVFSURI *path_u (0 == strcmp (info->name, ".."))) continue; - if (info->type & GNOME_VFS_FILE_TYPE_DIRECTORY) { - GnomeVFSURI *new_path_uri; - - new_path_uri = gnome_vfs_uri_append_file_name (path_uri, info->name); - - retval += get_dir_count (new_path_uri); - - gnome_vfs_uri_unref (new_path_uri); - continue; - } - retval++; } @@ -176,67 +167,100 @@ get_dir_count (const GnomeVFSURI *path_u return retval; } -static guint -trash_applet_items_count (TrashApplet *trash_applet) +static void +trash_applet_update_item_count (TrashApplet *trash_applet) { GnomeVFSURI *trash_dir; - gint retval = 0; + BonoboUIComponent *popup_component; - g_return_val_if_fail (trash_applet != NULL, 0); + g_return_if_fail (trash_applet != NULL); trash_dir = trash_applet_get_main_trash_directory_uri (); - retval = get_dir_count (trash_dir); + trash_applet->item_count = get_dir_count (trash_dir); gnome_vfs_uri_unref (trash_dir); - return retval; + trash_applet->is_empty = (trash_applet->item_count == 0); + + /* set sensitivity on the "empty trash" context menu item */ + popup_component = panel_applet_get_popup_component (trash_applet->applet); + bonobo_ui_component_set_prop (popup_component, + "/commands/EmptyTrash", + "sensitive", + trash_applet->is_empty ? "0" : "1", + NULL); +} + +static guint +trash_applet_items_count (TrashApplet *trash_applet) +{ + g_return_val_if_fail (trash_applet != NULL, 0); + + return trash_applet->item_count; } static gboolean trash_applet_is_empty (TrashApplet *trash_applet) { - guint count; - - count = trash_applet_items_count (trash_applet); - - switch (count) { - case 0: - trash_applet->is_empty = TRUE; - break; - default: - trash_applet->is_empty = FALSE; - } + g_return_val_if_fail (trash_applet != NULL, FALSE); return trash_applet->is_empty; } -static gboolean +static void trash_applet_update_icon (TrashApplet *trash_applet) { + TrashState new_state = TRASH_STATE_UNKNOWN; + gchar *icon_name = NULL; GdkPixbuf *new_icon; - g_return_val_if_fail (trash_applet != NULL, FALSE); + g_return_if_fail (trash_applet != NULL); if (trash_applet->icon == NULL) - return FALSE; - - new_icon = gtk_icon_theme_load_icon (trash_applet->icon_theme, - (trash_applet_is_empty (trash_applet) ? - TRASH_ICON_EMPTY : TRASH_ICON_FULL), - trash_applet->size, - 0, - NULL); + return; + + if (trash_applet->drag_hover) { + new_state = TRASH_STATE_ACCEPT; + icon_name = TRASH_ICON_EMPTY_ACCEPT; + } else if (trash_applet->is_empty) { + new_state = TRASH_STATE_EMPTY; + icon_name = TRASH_ICON_EMPTY; + } else { + new_state = TRASH_STATE_FULL; + icon_name = TRASH_ICON_FULL; + } - if (new_icon == trash_applet->icon) + /* if the icon is up to date, return */ + if (trash_applet->icon && trash_applet->icon_state == new_state) return; - g_object_unref (G_OBJECT (trash_applet->icon)); + new_icon = gtk_icon_theme_load_icon (trash_applet->icon_theme, + icon_name, + trash_applet->size, + 0, + NULL); + + /* Handle themes that do not have a -accept icon variant */ + if (!new_icon && new_state == TRASH_STATE_ACCEPT) { + icon_name = trash_applet->is_empty + ? TRASH_ICON_EMPTY : TRASH_ICON_FULL; + new_icon = gtk_icon_theme_load_icon (trash_applet->icon_theme, + icon_name, + trash_applet->size, + 0, + NULL); + } + + if (trash_applet->icon) + g_object_unref (G_OBJECT (trash_applet->icon)); + trash_applet->icon = new_icon; + trash_applet->icon_state = new_state; gtk_image_set_from_pixbuf (GTK_IMAGE (trash_applet->image), trash_applet->icon); - return TRUE; + return; } static void @@ -278,6 +302,106 @@ update_transfer_callback (GnomeVFSAsyncH } +/* this function is based on the one with the same name in + libnautilus-private/nautilus-file-operations.c */ +#define NAUTILUS_PREFERENCES_CONFIRM_TRASH "/apps/nautilus/preferences/confirm_trash" +static gboolean +confirm_empty_trash (GtkWidget *parent_view) +{ + GtkWidget *dialog; + int response; + GtkWidget *hbox, *vbox, *image, *label, *button; + gchar *str; + GdkScreen *screen; + + /* Just Say Yes if the preference says not to confirm. */ + if (!gconf_client_get_bool (client, + NAUTILUS_PREFERENCES_CONFIRM_TRASH, + NULL)) { + return TRUE; + } + + screen = gtk_widget_get_screen (parent_view); + + dialog = gtk_dialog_new (); + gtk_window_set_screen (GTK_WINDOW (dialog), screen); + atk_object_set_role (gtk_widget_get_accessible (dialog), ATK_ROLE_ALERT); + gtk_window_set_title (GTK_WINDOW (dialog), ""); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_wmclass (GTK_WINDOW (dialog), "empty_trash", + "Nautilus"); + + /* Make transient for the window group */ + gtk_widget_realize (dialog); + gdk_window_set_transient_for (GTK_WIDGET (dialog)->window, + gdk_screen_get_root_window (screen)); + gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); + + gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, + FALSE, FALSE, 0); + + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, + GTK_ICON_SIZE_DIALOG); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + str = g_strconcat ("", + _("Are you sure you want to empty " + "all of the items from the trash?"), + "", + NULL); + + label = gtk_label_new (str); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + g_free (str); + + label = gtk_label_new (_("If you empty the trash, items " + "will be permanently deleted.")); + + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); + + button = eel_gtk_button_new_with_stock_icon (_("_Empty"), + GTK_STOCK_DELETE); + gtk_widget_show (button); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, + GTK_RESPONSE_YES); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_YES); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_object_destroy (GTK_OBJECT (dialog)); + + return response == GTK_RESPONSE_YES; +} + static void trash_applet_do_empty (BonoboUIComponent *component, TrashApplet *ta, @@ -289,9 +413,14 @@ trash_applet_do_empty (BonoboUIComponent GList *dir_list, *uri_list, *l; gchar *path; + g_return_if_fail (PANEL_IS_APPLET (ta->applet)); + if (trash_applet_is_empty (ta)) return; + if (!confirm_empty_trash (GTK_WIDGET (ta->applet))) + return; + trash_dir = trash_applet_get_main_trash_directory_uri (); path = gnome_vfs_uri_to_string (trash_dir, GNOME_VFS_URI_HIDE_NONE); @@ -346,6 +475,8 @@ trash_applet_open_folder (BonoboUICompon GError *err = NULL; gboolean res; + g_return_if_fail (PANEL_IS_APPLET (ta->applet)); + res = g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, @@ -366,6 +497,8 @@ trash_applet_show_help (BonoboUIComponen { GError *error = NULL; + g_return_if_fail (PANEL_IS_APPLET (ta->applet)); + /* FIXME - Actually, we need a user guide */ gnome_help_display_desktop_on_screen ( NULL, "trashapplet", "trashapplet", NULL, @@ -414,6 +547,7 @@ update_icon_cb (GtkWidget *applet, gpoin { TrashApplet *trash_applet = (TrashApplet *) data; + trash_applet->icon_state = TRASH_STATE_UNKNOWN; trash_applet_update_icon(trash_applet); } @@ -486,6 +620,34 @@ drag_data_received_cb (GtkWidget *applet gnome_vfs_uri_list_free(list); } +static gboolean +drag_motion_cb (GtkWidget *applet, + GdkDragContext *context, + gint x, + gint y, + guint time, + TrashApplet *trash_applet) +{ + if (!trash_applet->drag_hover) { + trash_applet->drag_hover = TRUE; + trash_applet_update_icon (trash_applet); + } + gdk_drag_status (context, context->suggested_action, time); + return TRUE; +} + +static void +drag_leave_cb (GtkWidget *applet, + GdkDragContext *context, + guint time, + TrashApplet *trash_applet) +{ + if (trash_applet->drag_hover) { + trash_applet->drag_hover = FALSE; + trash_applet_update_icon (trash_applet); + } +} + static void changed_orient_cb (GtkWidget *applet, PanelAppletOrient orient, @@ -506,6 +668,7 @@ changed_orient_cb (GtkWidget *ap if (new_orient == trash_applet->orient) return; trash_applet->orient = new_orient; + trash_applet->icon_state = TRASH_STATE_UNKNOWN; trash_applet_update_icon (trash_applet); } @@ -516,18 +679,22 @@ changed_background_cb (PanelApplet const gchar *pixmap, TrashApplet *trash_applet) { - if (type == PANEL_NO_BACKGROUND) { + if (type == PANEL_NO_BACKGROUND || + type == PANEL_PIXMAP_BACKGROUND) { GtkRcStyle *rc_style; rc_style = gtk_rc_style_new (); gtk_widget_modify_style (GTK_WIDGET (trash_applet->applet), rc_style); - + gtk_widget_modify_style (GTK_WIDGET (trash_applet->image), rc_style); g_object_unref (rc_style); } else if (type == PANEL_COLOR_BACKGROUND) { gtk_widget_modify_bg (GTK_WIDGET (trash_applet->applet), GTK_STATE_NORMAL, color); + gtk_widget_modify_bg (GTK_WIDGET (trash_applet->image), + GTK_STATE_NORMAL, + color); } } @@ -542,6 +709,7 @@ changed_size_cb (PanelApplet *applet, return; trash_applet->size = size; + trash_applet->icon_state = TRASH_STATE_UNKNOWN; trash_applet_update_icon (trash_applet); trash_applet_update_tip (trash_applet); @@ -554,6 +722,8 @@ trash_monitor_cb (GnomeVFSMonitorHandle GnomeVFSMonitorEventType type, TrashApplet *trash_applet) { + trash_applet_update_item_count (trash_applet); + trash_applet_update_icon (trash_applet); trash_applet_update_tip (trash_applet); } @@ -569,7 +739,11 @@ trash_applet_fill (PanelApplet *applet) GnomeVFSResult res; GnomeVFSURI *trash_dir_uri; GnomeVFSMonitorHandle *hnd; - + + /* get default gconf client */ + if (!client) + client = gconf_client_get_default (); + panel_applet_set_flags (applet, PANEL_APPLET_EXPAND_MINOR); ta = g_new0 (TrashApplet, 1); @@ -590,7 +764,9 @@ trash_applet_fill (PanelApplet *applet) break; } + ta->item_count = 0; ta->is_empty = TRUE; + ta->drag_hover = FALSE; trash_dir_uri = trash_applet_get_main_trash_directory_uri (); trash_dir = gnome_vfs_uri_to_string (trash_dir_uri, @@ -612,7 +788,7 @@ trash_applet_fill (PanelApplet *applet) /* fetch the icon theme */ ta->icon_theme = gtk_icon_theme_get_default (); - + ta->icon_state = TRASH_STATE_UNKNOWN; /* use the trash icon from the icon theme */ @@ -634,10 +810,6 @@ trash_applet_fill (PanelApplet *applet) ta->tooltips = gtk_tooltips_new (); - /* update display */ - trash_applet_update_icon (ta); - trash_applet_update_tip (ta); - box = gtk_hbox_new(TRUE, 0); gtk_box_pack_start (GTK_BOX(box), ta->image, TRUE, TRUE, 0); @@ -655,6 +827,14 @@ trash_applet_fill (PanelApplet *applet) "drag_data_received", G_CALLBACK (drag_data_received_cb), ta); + g_signal_connect (G_OBJECT (ta->applet), + "drag_motion", + G_CALLBACK (drag_motion_cb), + ta); + g_signal_connect (G_OBJECT (ta->applet), + "drag_leave", + G_CALLBACK (drag_leave_cb), + ta); g_signal_connect (G_OBJECT (ta->applet), "change_orient", G_CALLBACK (changed_orient_cb), @@ -681,7 +861,12 @@ trash_applet_fill (PanelApplet *applet) "GNOME_Panel_TrashApplet.xml", NULL, trash_applet_menu_verbs, - ta->applet); + ta); + + /* update display */ + trash_applet_update_item_count (ta); + trash_applet_update_icon (ta); + trash_applet_update_tip (ta); gtk_widget_show_all (GTK_WIDGET(ta->applet)); Index: src/trashapplet.h =================================================================== RCS file: /cvsroot/trashapplet/trashapplet/src/trashapplet.h,v retrieving revision 1.3 diff -u -p -r1.3 trashapplet.h --- src/trashapplet.h 29 Jul 2004 17:31:00 -0000 1.3 +++ src/trashapplet.h 10 Sep 2004 03:17:17 -0000 @@ -27,8 +27,16 @@ #include #define TRASH_ICON_EMPTY "gnome-fs-trash-empty" +#define TRASH_ICON_EMPTY_ACCEPT "gnome-fs-trash-empty-accept" #define TRASH_ICON_FULL "gnome-fs-trash-full" +typedef enum { + TRASH_STATE_UNKNOWN, + TRASH_STATE_EMPTY, + TRASH_STATE_FULL, + TRASH_STATE_ACCEPT +} TrashState; + typedef struct _TrashApplet TrashApplet; struct _TrashApplet { @@ -36,12 +44,15 @@ struct _TrashApplet PanelAppletOrient orient; GtkIconTheme *icon_theme; + TrashState icon_state; GdkPixbuf *icon; GtkWidget *image; GtkTooltips *tooltips; guint size; + gint item_count; gboolean is_empty; + gboolean drag_hover; GnomeVFSMonitorHandle *trash_monitor; };