/* fm-properties-window.c - window that lets user modify file properties
 *
 *  Copyright (C) 2000 Eazel, Inc.
 *
 *  The Gnome Library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public License as
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  The Gnome Library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with the Gnome Library; see the file COPYING.LIB.  If not,
 *  see <http://www.gnu.org/licenses/>.
 *
 *  Authors: Darin Adler <darin@bentspoon.com>
 */

#include "nautilus-properties-window.h"

#include <cairo.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-string.h>
#include <eel/eel-vfs-extensions.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <nautilus-extension.h>
#include <string.h>
#include <sys/stat.h>

#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-desktop-thumbnail.h>

#include "nautilus-enums.h"
#include "nautilus-error-reporting.h"
#include "nautilus-file-operations.h"
#include "nautilus-file-utilities.h"
#include "nautilus-global-preferences.h"
#include "nautilus-icon-info.h"
#include "nautilus-metadata.h"
#include "nautilus-mime-actions.h"
#include "nautilus-mime-application-chooser.h"
#include "nautilus-module.h"
#include "nautilus-ui-utilities.h"

#define PREVIEW_IMAGE_WIDTH 96

#define ROW_PAD 6

static GHashTable *windows;
static GHashTable *pending_lists;

typedef struct
{
    NautilusFile *file;
    char *owner;
    GtkWindow *window;
    unsigned int timeout;
    gboolean cancelled;
} OwnerChange;

typedef struct
{
    NautilusFile *file;
    char *group;
    GtkWindow *window;
    unsigned int timeout;
    gboolean cancelled;
} GroupChange;

struct _NautilusPropertiesWindow
{
    GtkDialog parent_instance;

    GList *original_files;
    GList *target_files;

    GtkNotebook *notebook;

    GtkGrid *basic_grid;

    GtkWidget *icon_button;
    GtkWidget *icon_image;
    GtkWidget *icon_chooser;

    GtkLabel *name_label;
    GtkWidget *name_field;
    unsigned int name_row;
    char *pending_name;

    guint select_idle_id;

    GtkLabel *directory_contents_title_field;
    GtkLabel *directory_contents_value_field;
    GtkWidget *directory_contents_spinner;
    guint update_directory_contents_timeout_id;
    guint update_files_timeout_id;

    GroupChange *group_change;
    OwnerChange *owner_change;

    GList *permission_buttons;
    GList *permission_combos;
    GList *change_permission_combos;
    GHashTable *initial_permissions;
    gboolean has_recursive_apply;

    GList *value_fields;

    GList *mime_list;

    gboolean deep_count_finished;
    GList *deep_count_files;
    guint deep_count_spinner_timeout_id;

    guint long_operation_underway;

    GList *changed_files;

    guint64 volume_capacity;
    guint64 volume_free;
    guint64 volume_used;
};

enum
{
    COLUMN_NAME,
    COLUMN_VALUE,
    COLUMN_USE_ORIGINAL,
    COLUMN_ID,
    NUM_COLUMNS
};

typedef struct
{
    GList *original_files;
    GList *target_files;
    GtkWidget *parent_widget;
    GtkWindow *parent_window;
    char *startup_id;
    char *pending_key;
    GHashTable *pending_files;
    NautilusPropertiesWindowCallback callback;
    gpointer callback_data;
    NautilusPropertiesWindow *window;
    gboolean cancelled;
} StartupData;

/* drag and drop definitions */

enum
{
    TARGET_URI_LIST,
    TARGET_GNOME_URI_LIST,
};

static const GtkTargetEntry target_table[] =
{
    { "text/uri-list", 0, TARGET_URI_LIST },
    { "x-special/gnome-icon-list", 0, TARGET_GNOME_URI_LIST },
};

#define DIRECTORY_CONTENTS_UPDATE_INTERVAL      200 /* milliseconds */
#define FILES_UPDATE_INTERVAL                   200 /* milliseconds */

/*
 * A timeout before changes through the user/group combo box will be applied.
 * When quickly changing owner/groups (i.e. by keyboard or scroll wheel),
 * this ensures that the GUI doesn't end up unresponsive.
 *
 * Both combos react on changes by scheduling a new change and unscheduling
 * or cancelling old pending changes.
 */
#define CHOWN_CHGRP_TIMEOUT                     300 /* milliseconds */

static void schedule_directory_contents_update (NautilusPropertiesWindow *window);
static void directory_contents_value_field_update (NautilusPropertiesWindow *window);
static void file_changed_callback (NautilusFile *file,
                                   gpointer      user_data);
static void permission_button_update (NautilusPropertiesWindow *window,
                                      GtkToggleButton          *button);
static void permission_combo_update (NautilusPropertiesWindow *window,
                                     GtkComboBox              *combo);
static void value_field_update (NautilusPropertiesWindow *window,
                                GtkLabel                 *field);
static void properties_window_update (NautilusPropertiesWindow *window,
                                      GList                    *files);
static void is_directory_ready_callback (NautilusFile *file,
                                         gpointer      data);
static void cancel_group_change_callback (GroupChange *change);
static void cancel_owner_change_callback (OwnerChange *change);
static void select_image_button_callback (GtkWidget                *widget,
                                          NautilusPropertiesWindow *properties_window);
static void set_icon (const char               *icon_path,
                      NautilusPropertiesWindow *properties_window);
static void remove_pending (StartupData *data,
                            gboolean     cancel_call_when_ready,
                            gboolean     cancel_timed_wait);
static void append_extension_pages (NautilusPropertiesWindow *window);

static void name_field_focus_changed (GObject    *object,
                                      GParamSpec *pspec,
                                      gpointer    user_data);
static void name_field_activate (GtkWidget *name_field,
                                 gpointer   user_data);
static GtkLabel *attach_ellipsizing_value_label (GtkGrid    *grid,
                                                 GtkWidget  *sibling,
                                                 const char *initial_text);

static GtkWidget *create_pie_widget (NautilusPropertiesWindow *window);

G_DEFINE_TYPE (NautilusPropertiesWindow, nautilus_properties_window, GTK_TYPE_DIALOG);

static gboolean
is_multi_file_window (NautilusPropertiesWindow *window)
{
    GList *l;
    int count;

    count = 0;

    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
        {
            count++;
            if (count > 1)
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

static int
get_not_gone_original_file_count (NautilusPropertiesWindow *window)
{
    GList *l;
    int count;

    count = 0;

    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (!nautilus_file_is_gone (NAUTILUS_FILE (l->data)))
        {
            count++;
        }
    }

    return count;
}

static NautilusFile *
get_original_file (NautilusPropertiesWindow *window)
{
    g_return_val_if_fail (!is_multi_file_window (window), NULL);

    if (window->original_files == NULL)
    {
        return NULL;
    }

    return NAUTILUS_FILE (window->original_files->data);
}

static NautilusFile *
get_target_file_for_original_file (NautilusFile *file)
{
    NautilusFile *target_file;
    g_autoptr (GFile) location = NULL;
    g_autofree char *uri_to_display = NULL;

    uri_to_display = nautilus_file_get_uri (file);
    location = g_file_new_for_uri (uri_to_display);
    target_file = nautilus_file_get (location);

    return target_file;
}

static NautilusFile *
get_target_file (NautilusPropertiesWindow *window)
{
    return NAUTILUS_FILE (window->target_files->data);
}

static void
add_prompt (GtkWidget  *vbox,
            const char *prompt_text,
            gboolean    pack_at_start)
{
    GtkWidget *prompt;

    prompt = gtk_label_new (prompt_text);
    gtk_label_set_justify (GTK_LABEL (prompt), GTK_JUSTIFY_LEFT);
    gtk_label_set_line_wrap (GTK_LABEL (prompt), TRUE);
    gtk_widget_show (prompt);
    if (pack_at_start)
    {
        gtk_box_pack_start (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
    }
    else
    {
        gtk_box_pack_end (GTK_BOX (vbox), prompt, FALSE, FALSE, 0);
    }
}

static void
add_prompt_and_separator (GtkWidget  *vbox,
                          const char *prompt_text)
{
    GtkWidget *separator_line;

    add_prompt (vbox, prompt_text, FALSE);

    separator_line = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
    gtk_widget_show (separator_line);
    gtk_box_pack_end (GTK_BOX (vbox), separator_line, FALSE, TRUE, 2 * ROW_PAD);
}

static void
get_image_for_properties_window (NautilusPropertiesWindow  *window,
                                 char                     **icon_name,
                                 GdkPixbuf                **icon_pixbuf)
{
    NautilusIconInfo *icon, *new_icon;
    GList *l;
    gint icon_scale;

    icon = NULL;
    icon_scale = gtk_widget_get_scale_factor (GTK_WIDGET (window->notebook));

    for (l = window->original_files; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        if (!icon)
        {
            icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_SMALL, icon_scale,
                                           NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
                                           NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
        }
        else
        {
            new_icon = nautilus_file_get_icon (file, NAUTILUS_CANVAS_ICON_SIZE_SMALL, icon_scale,
                                               NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS |
                                               NAUTILUS_FILE_ICON_FLAGS_IGNORE_VISITING);
            if (!new_icon || new_icon != icon)
            {
                g_object_unref (icon);
                g_object_unref (new_icon);
                icon = NULL;
                break;
            }
            g_object_unref (new_icon);
        }
    }

    if (!icon)
    {
        icon = nautilus_icon_info_lookup_from_name ("text-x-generic",
                                                    NAUTILUS_CANVAS_ICON_SIZE_STANDARD,
                                                    icon_scale);
    }

    if (icon_name != NULL)
    {
        *icon_name = g_strdup (nautilus_icon_info_get_used_name (icon));
    }

    if (icon_pixbuf != NULL)
    {
        *icon_pixbuf = nautilus_icon_info_get_pixbuf_at_size (icon, NAUTILUS_CANVAS_ICON_SIZE_SMALL);
    }

    g_object_unref (icon);
}


static void
update_properties_window_icon (NautilusPropertiesWindow *window)
{
    GdkPixbuf *pixbuf;
    cairo_surface_t *surface;
    char *name;

    get_image_for_properties_window (window, &name, &pixbuf);

    if (name != NULL)
    {
        gtk_window_set_icon_name (GTK_WINDOW (window), name);
    }
    else
    {
        gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
    }

    surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, gtk_widget_get_scale_factor (GTK_WIDGET (window)),
                                                    gtk_widget_get_window (GTK_WIDGET (window)));
    gtk_image_set_from_surface (GTK_IMAGE (window->icon_image), surface);

    g_free (name);
    g_object_unref (pixbuf);
    cairo_surface_destroy (surface);
}

/* utility to test if a uri refers to a local image */
static gboolean
uri_is_local_image (const char *uri)
{
    GdkPixbuf *pixbuf;
    char *image_path;

    image_path = g_filename_from_uri (uri, NULL, NULL);
    if (image_path == NULL)
    {
        return FALSE;
    }

    pixbuf = gdk_pixbuf_new_from_file (image_path, NULL);
    g_free (image_path);

    if (pixbuf == NULL)
    {
        return FALSE;
    }
    g_object_unref (pixbuf);
    return TRUE;
}


static void
reset_icon (NautilusPropertiesWindow *properties_window)
{
    GList *l;

    for (l = properties_window->original_files; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        nautilus_file_set_metadata (file,
                                    NAUTILUS_METADATA_KEY_CUSTOM_ICON,
                                    NULL, NULL);
    }
}


static void
nautilus_properties_window_drag_data_received (GtkWidget        *widget,
                                               GdkDragContext   *context,
                                               int               x,
                                               int               y,
                                               GtkSelectionData *selection_data,
                                               guint             info,
                                               guint             time)
{
    char **uris;
    gboolean exactly_one;
    GtkImage *image;
    GtkWindow *window;

    image = GTK_IMAGE (widget);
    window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (image)));

    uris = g_strsplit ((const gchar *) gtk_selection_data_get_data (selection_data), "\r\n", 0);
    exactly_one = uris[0] != NULL && (uris[1] == NULL || uris[1][0] == '\0');


    if (!exactly_one)
    {
        show_dialog (_("You cannot assign more than one custom icon at a time!"),
                     _("Please drop just one image to set a custom icon."),
                     window,
                     GTK_MESSAGE_ERROR);
    }
    else
    {
        if (uri_is_local_image (uris[0]))
        {
            set_icon (uris[0], NAUTILUS_PROPERTIES_WINDOW (window));
        }
        else
        {
            GFile *f;

            f = g_file_new_for_uri (uris[0]);
            if (!g_file_is_native (f))
            {
                show_dialog (_("The file that you dropped is not local."),
                             _("You can only use local images as custom icons."),
                             window,
                             GTK_MESSAGE_ERROR);
            }
            else
            {
                show_dialog (_("The file that you dropped is not an image."),
                             _("You can only use local images as custom icons."),
                             window,
                             GTK_MESSAGE_ERROR);
            }
            g_object_unref (f);
        }
    }
    g_strfreev (uris);
}

static GtkWidget *
create_image_widget (NautilusPropertiesWindow *window,
                     gboolean                  is_customizable)
{
    GtkWidget *button;
    GtkWidget *image;

    image = gtk_image_new ();
    window->icon_image = image;

    update_properties_window_icon (window);
    gtk_widget_show (image);

    button = NULL;
    if (is_customizable)
    {
        button = gtk_button_new ();
        gtk_container_add (GTK_CONTAINER (button), image);

        /* prepare the image to receive dropped objects to assign custom images */
        gtk_drag_dest_set (GTK_WIDGET (image),
                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
                           target_table, G_N_ELEMENTS (target_table),
                           GDK_ACTION_COPY | GDK_ACTION_MOVE);

        g_signal_connect (image, "drag-data-received",
                          G_CALLBACK (nautilus_properties_window_drag_data_received), NULL);
        g_signal_connect (button, "clicked",
                          G_CALLBACK (select_image_button_callback), window);
    }

    window->icon_button = button;

    return button != NULL ? button : image;
}

static void
set_name_field (NautilusPropertiesWindow *window,
                const gchar              *original_name,
                const gchar              *name)
{
    gboolean new_widget;
    gboolean use_label;

    /* There are four cases here:
     * 1) Changing the text of a label
     * 2) Changing the text of an entry
     * 3) Creating label (potentially replacing entry)
     * 4) Creating entry (potentially replacing label)
     */
    use_label = is_multi_file_window (window) || !nautilus_file_can_rename (get_original_file (window));
    new_widget = !window->name_field || (use_label ? GTK_IS_ENTRY (window->name_field) : GTK_IS_LABEL (window->name_field));

    if (new_widget)
    {
        if (window->name_field)
        {
            gtk_widget_destroy (window->name_field);
        }

        if (use_label)
        {
            window->name_field = GTK_WIDGET
                                              (attach_ellipsizing_value_label (window->basic_grid,
                                                                               GTK_WIDGET (window->name_label),
                                                                               name));
        }
        else
        {
            window->name_field = gtk_entry_new ();
            gtk_entry_set_text (GTK_ENTRY (window->name_field), name);
            gtk_widget_show (window->name_field);

            gtk_grid_attach_next_to (window->basic_grid, window->name_field,
                                     GTK_WIDGET (window->name_label),
                                     GTK_POS_RIGHT, 1, 1);
            gtk_label_set_mnemonic_widget (GTK_LABEL (window->name_label), window->name_field);

            g_signal_connect_object (window->name_field, "notify::has-focus",
                                     G_CALLBACK (name_field_focus_changed), window, 0);
            g_signal_connect_object (window->name_field, "activate",
                                     G_CALLBACK (name_field_activate), window, 0);
        }

        gtk_widget_show (window->name_field);
    }
    /* Only replace text if the file's name has changed. */
    else if (original_name == NULL || strcmp (original_name, name) != 0)
    {
        if (use_label)
        {
            gtk_label_set_text (GTK_LABEL (window->name_field), name);
        }
        else
        {
            /* Only reset the text if it's different from what is
             * currently showing. This causes minimal ripples (e.g.
             * selection change).
             */
            gchar *displayed_name = gtk_editable_get_chars (GTK_EDITABLE (window->name_field), 0, -1);
            if (strcmp (displayed_name, name) != 0)
            {
                gtk_entry_set_text (GTK_ENTRY (window->name_field), name);
            }
            g_free (displayed_name);
        }
    }
}

static void
update_name_field (NautilusPropertiesWindow *window)
{
    NautilusFile *file;

    gtk_label_set_text_with_mnemonic (window->name_label,
                                      ngettext ("_Name:", "_Names:",
                                                get_not_gone_original_file_count (window)));

    if (is_multi_file_window (window))
    {
        /* Multifile property dialog, show all names */
        GString *str;
        char *name;
        gboolean first;
        GList *l;

        str = g_string_new ("");

        first = TRUE;

        for (l = window->target_files; l != NULL; l = l->next)
        {
            file = NAUTILUS_FILE (l->data);

            if (!nautilus_file_is_gone (file))
            {
                if (!first)
                {
                    g_string_append (str, ", ");
                }
                first = FALSE;

                name = nautilus_file_get_display_name (file);
                g_string_append (str, name);
                g_free (name);
            }
        }
        set_name_field (window, NULL, str->str);
        g_string_free (str, TRUE);
    }
    else
    {
        const char *original_name = NULL;
        char *current_name;

        file = get_original_file (window);

        if (file == NULL || nautilus_file_is_gone (file))
        {
            current_name = g_strdup ("");
        }
        else
        {
            current_name = nautilus_file_get_display_name (file);
        }

        /* If the file name has changed since the original name was stored,
         * update the text in the text field, possibly (deliberately) clobbering
         * an edit in progress. If the name hasn't changed (but some other
         * aspect of the file might have), then don't clobber changes.
         */
        if (window->name_field)
        {
            original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field), "original_name");
        }

        set_name_field (window, original_name, current_name);

        if (original_name == NULL ||
            g_strcmp0 (original_name, current_name) != 0)
        {
            g_object_set_data_full (G_OBJECT (window->name_field),
                                    "original_name",
                                    current_name,
                                    g_free);
        }
        else
        {
            g_free (current_name);
        }
    }
}

static void
name_field_restore_original_name (GtkWidget *name_field)
{
    const char *original_name;
    char *displayed_name;

    original_name = (const char *) g_object_get_data (G_OBJECT (name_field),
                                                      "original_name");

    if (!original_name)
    {
        return;
    }

    displayed_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);

    if (strcmp (original_name, displayed_name) != 0)
    {
        gtk_entry_set_text (GTK_ENTRY (name_field), original_name);
    }
    gtk_editable_select_region (GTK_EDITABLE (name_field), 0, -1);

    g_free (displayed_name);
}

static void
rename_callback (NautilusFile *file,
                 GFile        *res_loc,
                 GError       *error,
                 gpointer      callback_data)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (callback_data);

    /* Complain to user if rename failed. */
    if (error != NULL)
    {
        nautilus_report_error_renaming_file (file,
                                             window->pending_name,
                                             error,
                                             GTK_WINDOW (window));
        if (window->name_field != NULL)
        {
            name_field_restore_original_name (window->name_field);
        }
    }

    g_object_unref (window);
}

static void
set_pending_name (NautilusPropertiesWindow *window,
                  const char               *name)
{
    g_free (window->pending_name);
    window->pending_name = g_strdup (name);
}

static void
name_field_done_editing (GtkWidget                *name_field,
                         NautilusPropertiesWindow *window)
{
    NautilusFile *file;
    char *new_name;
    const char *original_name;

    g_return_if_fail (GTK_IS_ENTRY (name_field));

    /* Don't apply if the dialog has more than one file */
    if (is_multi_file_window (window))
    {
        return;
    }

    file = get_original_file (window);

    /* This gets called when the window is closed, which might be
     * caused by the file having been deleted.
     */
    if (file == NULL || nautilus_file_is_gone (file))
    {
        return;
    }

    new_name = gtk_editable_get_chars (GTK_EDITABLE (name_field), 0, -1);

    /* Special case: silently revert text if new text is empty. */
    if (strlen (new_name) == 0)
    {
        name_field_restore_original_name (name_field);
    }
    else
    {
        original_name = (const char *) g_object_get_data (G_OBJECT (window->name_field),
                                                          "original_name");
        /* Don't rename if not changed since we read the display name.
         *  This is needed so that we don't save the display name to the
         *  file when nothing is changed */
        if (strcmp (new_name, original_name) != 0)
        {
            set_pending_name (window, new_name);
            g_object_ref (window);
            nautilus_file_rename (file, new_name,
                                  rename_callback, window);
        }
    }

    g_free (new_name);
}

static void
name_field_focus_changed (GObject    *object,
                          GParamSpec *pspec,
                          gpointer    user_data)
{
    GtkWidget *widget;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data));

    widget = GTK_WIDGET (object);

    if (!gtk_widget_has_focus (widget) && gtk_widget_get_sensitive (widget))
    {
        name_field_done_editing (widget, NAUTILUS_PROPERTIES_WINDOW (user_data));
    }
}

static gboolean
select_all_at_idle (gpointer user_data)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (user_data);

    gtk_editable_select_region (GTK_EDITABLE (window->name_field),
                                0, -1);

    window->select_idle_id = 0;

    return FALSE;
}

static void
name_field_activate (GtkWidget *name_field,
                     gpointer   user_data)
{
    NautilusPropertiesWindow *window;

    g_assert (GTK_IS_ENTRY (name_field));
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (user_data));

    window = NAUTILUS_PROPERTIES_WINDOW (user_data);

    /* Accept changes. */
    name_field_done_editing (name_field, window);

    if (window->select_idle_id == 0)
    {
        window->select_idle_id = g_idle_add (select_all_at_idle,
                                                      window);
    }
}

static void
update_properties_window_title (NautilusPropertiesWindow *window)
{
    char *name, *title;
    NautilusFile *file;

    g_return_if_fail (GTK_IS_WINDOW (window));

    title = g_strdup_printf (_("Properties"));

    if (!is_multi_file_window (window))
    {
        file = get_original_file (window);

        if (file != NULL)
        {
            g_free (title);
            name = nautilus_file_get_display_name (file);
            if (nautilus_file_is_directory (file))
            {
                /* To translators: %s is the name of the folder. */
                title = g_strdup_printf (C_("folder", "%s Properties"), name);
            }
            else
            {
                /* To translators: %s is the name of the file. */
                title = g_strdup_printf (C_("file", "%s Properties"), name);
            }

            g_free (name);
        }
    }

    gtk_window_set_title (GTK_WINDOW (window), title);

    g_free (title);
}

static void
clear_extension_pages (NautilusPropertiesWindow *window)
{
    int i;
    int num_pages;
    GtkWidget *page;

    num_pages = gtk_notebook_get_n_pages
                    (GTK_NOTEBOOK (window->notebook));

    for (i = 0; i < num_pages; i++)
    {
        page = gtk_notebook_get_nth_page
                   (GTK_NOTEBOOK (window->notebook), i);

        if (g_object_get_data (G_OBJECT (page), "is-extension-page"))
        {
            gtk_notebook_remove_page
                (GTK_NOTEBOOK (window->notebook), i);
            num_pages--;
            i--;
        }
    }
}

static void
refresh_extension_pages (NautilusPropertiesWindow *window)
{
    clear_extension_pages (window);
    append_extension_pages (window);
}

static void
remove_from_dialog (NautilusPropertiesWindow *window,
                    NautilusFile             *file)
{
    int index;
    GList *original_link;
    GList *target_link;
    NautilusFile *original_file;
    NautilusFile *target_file;

    index = g_list_index (window->target_files, file);
    if (index == -1)
    {
        index = g_list_index (window->original_files, file);
        g_return_if_fail (index != -1);
    }

    original_link = g_list_nth (window->original_files, index);
    target_link = g_list_nth (window->target_files, index);

    g_return_if_fail (original_link && target_link);

    original_file = NAUTILUS_FILE (original_link->data);
    target_file = NAUTILUS_FILE (target_link->data);

    window->original_files = g_list_remove_link (window->original_files, original_link);
    g_list_free (original_link);

    window->target_files = g_list_remove_link (window->target_files, target_link);
    g_list_free (target_link);

    g_hash_table_remove (window->initial_permissions, target_file);

    g_signal_handlers_disconnect_by_func (original_file,
                                          G_CALLBACK (file_changed_callback),
                                          window);
    g_signal_handlers_disconnect_by_func (target_file,
                                          G_CALLBACK (file_changed_callback),
                                          window);

    nautilus_file_monitor_remove (original_file, &window->original_files);
    nautilus_file_monitor_remove (target_file, &window->target_files);

    nautilus_file_unref (original_file);
    nautilus_file_unref (target_file);
}

static gboolean
mime_list_equal (GList *a,
                 GList *b)
{
    while (a && b)
    {
        if (strcmp (a->data, b->data))
        {
            return FALSE;
        }
        a = a->next;
        b = b->next;
    }

    return (a == b);
}

static GList *
get_mime_list (NautilusPropertiesWindow *window)
{
    GList *ret;
    GList *l;

    ret = NULL;
    for (l = window->target_files; l != NULL; l = l->next)
    {
        ret = g_list_append (ret, nautilus_file_get_mime_type (NAUTILUS_FILE (l->data)));
    }
    ret = g_list_reverse (ret);
    return ret;
}

static gboolean
start_spinner_callback (NautilusPropertiesWindow *window)
{
    gtk_widget_show (window->directory_contents_spinner);
    gtk_spinner_start (GTK_SPINNER (window->directory_contents_spinner));
    window->deep_count_spinner_timeout_id = 0;

    return FALSE;
}

static void
schedule_start_spinner (NautilusPropertiesWindow *window)
{
    if (window->deep_count_spinner_timeout_id == 0)
    {
        window->deep_count_spinner_timeout_id
            = g_timeout_add_seconds (1,
                                     (GSourceFunc) start_spinner_callback,
                                     window);
    }
}

static void
stop_spinner (NautilusPropertiesWindow *window)
{
    gtk_spinner_stop (GTK_SPINNER (window->directory_contents_spinner));
    gtk_widget_hide (window->directory_contents_spinner);
    if (window->deep_count_spinner_timeout_id > 0)
    {
        g_source_remove (window->deep_count_spinner_timeout_id);
        window->deep_count_spinner_timeout_id = 0;
    }
}

static void
stop_deep_count_for_file (NautilusPropertiesWindow *window,
                          NautilusFile             *file)
{
    if (g_list_find (window->deep_count_files, file))
    {
        g_signal_handlers_disconnect_by_func (file,
                                              G_CALLBACK (schedule_directory_contents_update),
                                              window);
        nautilus_file_unref (file);
        window->deep_count_files = g_list_remove (window->deep_count_files, file);
    }
}

static void
start_deep_count_for_file (NautilusPropertiesWindow *window,
                           NautilusFile             *file)
{
    if (!nautilus_file_is_directory (file))
    {
        return;
    }

    if (!g_list_find (window->deep_count_files, file))
    {
        nautilus_file_ref (file);
        window->deep_count_files = g_list_prepend (window->deep_count_files, file);

        nautilus_file_recompute_deep_counts (file);
        if (!window->deep_count_finished)
        {
            g_signal_connect_object (file,
                                     "updated-deep-count-in-progress",
                                     G_CALLBACK (schedule_directory_contents_update),
                                     window, G_CONNECT_SWAPPED);
            schedule_start_spinner (window);
        }
    }
}

static void
properties_window_update (NautilusPropertiesWindow *window,
                          GList                    *files)
{
    GList *l;
    GList *mime_list;
    GList *tmp;
    NautilusFile *changed_file;
    gboolean dirty_original = FALSE;
    gboolean dirty_target = FALSE;

    if (files == NULL)
    {
        dirty_original = TRUE;
        dirty_target = TRUE;
    }

    for (tmp = files; tmp != NULL; tmp = tmp->next)
    {
        changed_file = NAUTILUS_FILE (tmp->data);

        if (changed_file && nautilus_file_is_gone (changed_file))
        {
            /* Remove the file from the property dialog */
            remove_from_dialog (window, changed_file);
            changed_file = NULL;

            if (window->original_files == NULL)
            {
                return;
            }
        }
        if (changed_file == NULL ||
            g_list_find (window->original_files, changed_file))
        {
            dirty_original = TRUE;
        }
        if (changed_file == NULL ||
            g_list_find (window->target_files, changed_file))
        {
            dirty_target = TRUE;
        }
    }

    if (dirty_original)
    {
        update_properties_window_title (window);
        update_properties_window_icon (window);
        update_name_field (window);

        /* If any of the value fields start to depend on the original
         * value, value_field_updates should be added here */
    }

    if (dirty_target)
    {
        for (l = window->permission_buttons; l != NULL; l = l->next)
        {
            permission_button_update (window, GTK_TOGGLE_BUTTON (l->data));
        }

        for (l = window->permission_combos; l != NULL; l = l->next)
        {
            permission_combo_update (window, GTK_COMBO_BOX (l->data));
        }

        for (l = window->value_fields; l != NULL; l = l->next)
        {
            value_field_update (window, GTK_LABEL (l->data));
        }
    }

    mime_list = get_mime_list (window);

    if (!window->mime_list)
    {
        window->mime_list = mime_list;
    }
    else
    {
        if (!mime_list_equal (window->mime_list, mime_list))
        {
            refresh_extension_pages (window);
        }

        g_list_free_full (window->mime_list, g_free);
        window->mime_list = mime_list;
    }
}

static gboolean
update_files_callback (gpointer data)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (data);

    window->update_files_timeout_id = 0;

    properties_window_update (window, window->changed_files);

    if (window->original_files == NULL)
    {
        /* Close the window if no files are left */
        gtk_widget_destroy (GTK_WIDGET (window));
    }
    else
    {
        nautilus_file_list_free (window->changed_files);
        window->changed_files = NULL;
    }

    return FALSE;
}

static void
schedule_files_update (NautilusPropertiesWindow *window)
{
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    if (window->update_files_timeout_id == 0)
    {
        window->update_files_timeout_id
            = g_timeout_add (FILES_UPDATE_INTERVAL,
                             update_files_callback,
                             window);
    }
}

static gboolean
file_list_attributes_identical (GList      *file_list,
                                const char *attribute_name)
{
    gboolean identical;
    char *first_attr;
    GList *l;

    first_attr = NULL;
    identical = TRUE;

    for (l = file_list; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_is_gone (file))
        {
            continue;
        }

        if (first_attr == NULL)
        {
            first_attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
        }
        else
        {
            char *attr;
            attr = nautilus_file_get_string_attribute_with_default (file, attribute_name);
            if (strcmp (attr, first_attr))
            {
                identical = FALSE;
                g_free (attr);
                break;
            }
            g_free (attr);
        }
    }

    g_free (first_attr);
    return identical;
}

static char *
file_list_get_string_attribute (GList      *file_list,
                                const char *attribute_name,
                                const char *inconsistent_value)
{
    if (file_list_attributes_identical (file_list, attribute_name))
    {
        GList *l;

        for (l = file_list; l != NULL; l = l->next)
        {
            NautilusFile *file;

            file = NAUTILUS_FILE (l->data);
            if (!nautilus_file_is_gone (file))
            {
                return nautilus_file_get_string_attribute_with_default
                           (file,
                           attribute_name);
            }
        }
        return g_strdup (_("unknown"));
    }
    else
    {
        return g_strdup (inconsistent_value);
    }
}


static gboolean
file_list_all_directories (GList *file_list)
{
    GList *l;
    for (l = file_list; l != NULL; l = l->next)
    {
        if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data)))
        {
            return FALSE;
        }
    }
    return TRUE;
}

static void
value_field_update_internal (GtkLabel *label,
                             GList    *file_list)
{
    const char *attribute_name;
    char *attribute_value;
    char *inconsistent_string;
    char *mime_type, *tmp;

    g_assert (GTK_IS_LABEL (label));

    attribute_name = g_object_get_data (G_OBJECT (label), "file_attribute");
    inconsistent_string = g_object_get_data (G_OBJECT (label), "inconsistent_string");
    attribute_value = file_list_get_string_attribute (file_list,
                                                      attribute_name,
                                                      inconsistent_string);
    if (!strcmp (attribute_name, "detailed_type") && strcmp (attribute_value, inconsistent_string))
    {
        mime_type = file_list_get_string_attribute (file_list,
                                                    "mime_type",
                                                    inconsistent_string);
        if (strcmp (mime_type, inconsistent_string))
        {
            tmp = attribute_value;
            attribute_value = g_strdup_printf (C_("MIME type description (MIME type)", "%s (%s)"), attribute_value, mime_type);
            g_free (tmp);
        }
        g_free (mime_type);
    }

    gtk_label_set_text (label, attribute_value);
    g_free (attribute_value);
}

static void
value_field_update (NautilusPropertiesWindow *window,
                    GtkLabel                 *label)
{
    gboolean use_original;

    use_original = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (label), "show_original"));

    value_field_update_internal (label,
                                 (use_original ?
                                  window->original_files :
                                  window->target_files));
}

static GtkLabel *
attach_label (GtkGrid    *grid,
              GtkWidget  *sibling,
              const char *initial_text,
              gboolean    ellipsize_text,
              gboolean    selectable,
              gboolean    mnemonic)
{
    GtkWidget *label_field;

    if (ellipsize_text)
    {
        label_field = gtk_label_new (initial_text);
        gtk_label_set_ellipsize (GTK_LABEL (label_field),
                                 PANGO_ELLIPSIZE_END);
    }
    else if (mnemonic)
    {
        label_field = gtk_label_new_with_mnemonic (initial_text);
    }
    else
    {
        label_field = gtk_label_new (initial_text);
    }

    if (selectable)
    {
        gtk_label_set_selectable (GTK_LABEL (label_field), TRUE);
    }

    gtk_label_set_xalign (GTK_LABEL (label_field), 0);
    gtk_widget_show (label_field);

    if (ellipsize_text)
    {
        gtk_widget_set_hexpand (label_field, TRUE);
        gtk_label_set_max_width_chars (GTK_LABEL (label_field), 24);
    }

    if (sibling != NULL)
    {
        gtk_grid_attach_next_to (grid, label_field, sibling,
                                 GTK_POS_RIGHT, 1, 1);
    }
    else
    {
        gtk_container_add (GTK_CONTAINER (grid), label_field);
    }

    return GTK_LABEL (label_field);
}

static GtkLabel *
attach_value_label (GtkGrid    *grid,
                    GtkWidget  *sibling,
                    const char *initial_text)
{
    return attach_label (grid, sibling, initial_text, FALSE, TRUE, FALSE);
}

static GtkLabel *
attach_ellipsizing_value_label (GtkGrid    *grid,
                                GtkWidget  *sibling,
                                const char *initial_text)
{
    return attach_label (grid, sibling, initial_text, TRUE, TRUE, FALSE);
}

static GtkWidget *
attach_value_field_internal (NautilusPropertiesWindow *window,
                             GtkGrid                  *grid,
                             GtkWidget                *sibling,
                             const char               *file_attribute_name,
                             const char               *inconsistent_string,
                             gboolean                  show_original,
                             gboolean                  ellipsize_text)
{
    GtkLabel *value_field;

    if (ellipsize_text)
    {
        value_field = attach_ellipsizing_value_label (grid, sibling, "");
    }
    else
    {
        value_field = attach_value_label (grid, sibling, "");
    }

    /* Stash a copy of the file attribute name in this field for the callback's sake. */
    g_object_set_data_full (G_OBJECT (value_field), "file_attribute",
                            g_strdup (file_attribute_name), g_free);

    g_object_set_data_full (G_OBJECT (value_field), "inconsistent_string",
                            g_strdup (inconsistent_string), g_free);

    g_object_set_data (G_OBJECT (value_field), "show_original", GINT_TO_POINTER (show_original));

    window->value_fields = g_list_prepend (window->value_fields,
                                                    value_field);
    return GTK_WIDGET (value_field);
}

static GtkWidget *
attach_value_field (NautilusPropertiesWindow *window,
                    GtkGrid                  *grid,
                    GtkWidget                *sibling,
                    const char               *file_attribute_name,
                    const char               *inconsistent_string,
                    gboolean                  show_original)
{
    return attach_value_field_internal (window,
                                        grid, sibling,
                                        file_attribute_name,
                                        inconsistent_string,
                                        show_original,
                                        FALSE);
}

static GtkWidget *
attach_ellipsizing_value_field (NautilusPropertiesWindow *window,
                                GtkGrid                  *grid,
                                GtkWidget                *sibling,
                                const char               *file_attribute_name,
                                const char               *inconsistent_string,
                                gboolean                  show_original)
{
    return attach_value_field_internal (window,
                                        grid, sibling,
                                        file_attribute_name,
                                        inconsistent_string,
                                        show_original,
                                        TRUE);
}

static void
group_change_free (GroupChange *change)
{
    nautilus_file_unref (change->file);
    g_free (change->group);
    g_object_unref (change->window);

    g_free (change);
}

static void
group_change_callback (NautilusFile *file,
                       GFile        *res_loc,
                       GError       *error,
                       GroupChange  *change)
{
    NautilusPropertiesWindow *window;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->group != NULL);

    if (!change->cancelled)
    {
        /* Report the error if it's an error. */
        eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
        nautilus_report_error_setting_group (change->file, error, change->window);
    }

    window = NAUTILUS_PROPERTIES_WINDOW (change->window);
    if (window->group_change == change)
    {
        window->group_change = NULL;
    }

    group_change_free (change);
}

static void
cancel_group_change_callback (GroupChange *change)
{
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->group != NULL);

    change->cancelled = TRUE;
    nautilus_file_cancel (change->file, (NautilusFileOperationCallback) group_change_callback, change);
}

static gboolean
schedule_group_change_timeout (GroupChange *change)
{
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->group != NULL);

    change->timeout = 0;

    eel_timed_wait_start
        ((EelCancelCallback) cancel_group_change_callback,
        change,
        _("Cancel Group Change?"),
        change->window);

    nautilus_file_set_group
        (change->file, change->group,
        (NautilusFileOperationCallback) group_change_callback, change);

    return FALSE;
}

static void
schedule_group_change (NautilusPropertiesWindow *window,
                       NautilusFile             *file,
                       const char               *group)
{
    GroupChange *change;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
    g_assert (window->group_change == NULL);
    g_assert (NAUTILUS_IS_FILE (file));

    change = g_new0 (GroupChange, 1);

    change->file = nautilus_file_ref (file);
    change->group = g_strdup (group);
    change->window = GTK_WINDOW (g_object_ref (window));
    change->timeout =
        g_timeout_add (CHOWN_CHGRP_TIMEOUT,
                       (GSourceFunc) schedule_group_change_timeout,
                       change);

    window->group_change = change;
}

static void
unschedule_or_cancel_group_change (NautilusPropertiesWindow *window)
{
    GroupChange *change;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    change = window->group_change;

    if (change != NULL)
    {
        if (change->timeout == 0)
        {
            /* The operation was started, cancel it and let the operation callback free the change */
            cancel_group_change_callback (change);
            eel_timed_wait_stop ((EelCancelCallback) cancel_group_change_callback, change);
        }
        else
        {
            g_source_remove (change->timeout);
            group_change_free (change);
        }

        window->group_change = NULL;
    }
}

static void
changed_group_callback (GtkComboBox  *combo_box,
                        NautilusFile *file)
{
    NautilusPropertiesWindow *window;
    char *group;
    char *cur_group;

    g_assert (GTK_IS_COMBO_BOX (combo_box));
    g_assert (NAUTILUS_IS_FILE (file));

    group = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box));
    cur_group = nautilus_file_get_group_name (file);

    if (group != NULL && strcmp (group, cur_group) != 0)
    {
        /* Try to change file group. If this fails, complain to user. */
        window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));

        unschedule_or_cancel_group_change (window);
        schedule_group_change (window, file, group);
    }
    g_free (group);
    g_free (cur_group);
}

/* checks whether the given column at the first level
 * of model has the specified entries in the given order. */
static gboolean
tree_model_entries_equal (GtkTreeModel *model,
                          unsigned int  column,
                          GList        *entries)
{
    GtkTreeIter iter;
    gboolean empty_model;

    g_assert (GTK_IS_TREE_MODEL (model));
    g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);

    empty_model = !gtk_tree_model_get_iter_first (model, &iter);

    if (!empty_model && entries != NULL)
    {
        GList *l;

        l = entries;

        do
        {
            char *val;

            gtk_tree_model_get (model, &iter,
                                column, &val,
                                -1);
            if ((val == NULL && l->data != NULL) ||
                (val != NULL && l->data == NULL) ||
                (val != NULL && strcmp (val, l->data)))
            {
                g_free (val);
                return FALSE;
            }

            g_free (val);
            l = l->next;
        }
        while (gtk_tree_model_iter_next (model, &iter));

        return l == NULL;
    }
    else
    {
        return (empty_model && entries == NULL) ||
               (!empty_model && entries != NULL);
    }
}

static char *
combo_box_get_active_entry (GtkComboBox *combo_box,
                            unsigned int column)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    char *val;

    g_assert (GTK_IS_COMBO_BOX (combo_box));

    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
    {
        model = gtk_combo_box_get_model (combo_box);
        g_assert (GTK_IS_TREE_MODEL (model));

        gtk_tree_model_get (model, &iter,
                            column, &val,
                            -1);
        return val;
    }

    return NULL;
}

/* returns the index of the given entry in the the given column
 * at the first level of model. Returns -1 if entry can't be found
 * or entry is NULL.
 * */
static int
tree_model_get_entry_index (GtkTreeModel *model,
                            unsigned int  column,
                            const char   *entry)
{
    GtkTreeIter iter;
    int index;
    gboolean empty_model;

    g_assert (GTK_IS_TREE_MODEL (model));
    g_assert (gtk_tree_model_get_column_type (model, column) == G_TYPE_STRING);

    empty_model = !gtk_tree_model_get_iter_first (model, &iter);
    if (!empty_model && entry != NULL)
    {
        index = 0;

        do
        {
            char *val;

            gtk_tree_model_get (model, &iter,
                                column, &val,
                                -1);
            if (val != NULL && !strcmp (val, entry))
            {
                g_free (val);
                return index;
            }

            g_free (val);
            index++;
        }
        while (gtk_tree_model_iter_next (model, &iter));
    }

    return -1;
}


static void
synch_groups_combo_box (GtkComboBox  *combo_box,
                        NautilusFile *file)
{
    GList *groups;
    GList *node;
    GtkTreeModel *model;
    GtkListStore *store;
    const char *group_name;
    char *current_group_name;
    int group_index;
    int current_group_index;

    g_assert (GTK_IS_COMBO_BOX (combo_box));
    g_assert (NAUTILUS_IS_FILE (file));

    if (nautilus_file_is_gone (file))
    {
        return;
    }

    groups = nautilus_file_get_settable_group_names (file);

    model = gtk_combo_box_get_model (combo_box);
    store = GTK_LIST_STORE (model);
    g_assert (GTK_IS_LIST_STORE (model));

    if (!tree_model_entries_equal (model, 0, groups))
    {
        /* Clear the contents of ComboBox in a wacky way because there
         * is no function to clear all items and also no function to obtain
         * the number of items in a combobox.
         */
        gtk_list_store_clear (store);

        for (node = groups, group_index = 0; node != NULL; node = node->next, ++group_index)
        {
            group_name = (const char *) node->data;
            gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo_box), group_name);
        }
    }

    current_group_name = nautilus_file_get_group_name (file);
    current_group_index = tree_model_get_entry_index (model, 0, current_group_name);

    /* If current group wasn't in list, we prepend it (with a separator).
     * This can happen if the current group is an id with no matching
     * group in the groups file.
     */
    if (current_group_index < 0 && current_group_name != NULL)
    {
        if (groups != NULL)
        {
            /* add separator */
            gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), "-");
        }

        gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (combo_box), current_group_name);
        current_group_index = 0;
    }
    gtk_combo_box_set_active (combo_box, current_group_index);

    g_free (current_group_name);
    g_list_free_full (groups, g_free);
}

static gboolean
combo_box_row_separator_func (GtkTreeModel *model,
                              GtkTreeIter  *iter,
                              gpointer      data)
{
    gchar *text;
    gboolean ret;

    gtk_tree_model_get (model, iter, 0, &text, -1);

    if (text == NULL)
    {
        return FALSE;
    }

    if (strcmp (text, "-") == 0)
    {
        ret = TRUE;
    }
    else
    {
        ret = FALSE;
    }

    g_free (text);
    return ret;
}

static GtkComboBox *
attach_combo_box (GtkGrid   *grid,
                  GtkWidget *sibling,
                  gboolean   three_columns)
{
    GtkWidget *combo_box;

    if (!three_columns)
    {
        combo_box = gtk_combo_box_text_new ();
    }
    else
    {
        GtkTreeModel *model;
        GtkCellRenderer *renderer;

        model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING));
        combo_box = gtk_combo_box_new_with_model (model);
        g_object_unref (G_OBJECT (model));

        renderer = gtk_cell_renderer_text_new ();
        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
        gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box), renderer,
                                       "text", 0);
    }
    gtk_widget_set_halign (combo_box, GTK_ALIGN_START);
    gtk_widget_show (combo_box);

    gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box),
                                          combo_box_row_separator_func,
                                          NULL,
                                          NULL);

    gtk_grid_attach_next_to (grid, combo_box, sibling,
                             GTK_POS_RIGHT, 1, 1);

    return GTK_COMBO_BOX (combo_box);
}

static GtkComboBox *
attach_group_combo_box (GtkGrid      *grid,
                        GtkWidget    *sibling,
                        NautilusFile *file)
{
    GtkComboBox *combo_box;

    combo_box = attach_combo_box (grid, sibling, FALSE);

    synch_groups_combo_box (combo_box, file);

    /* Connect to signal to update menu when file changes. */
    g_signal_connect_object (file, "changed",
                             G_CALLBACK (synch_groups_combo_box),
                             combo_box, G_CONNECT_SWAPPED);
    g_signal_connect_data (combo_box, "changed",
                           G_CALLBACK (changed_group_callback),
                           nautilus_file_ref (file),
                           (GClosureNotify) nautilus_file_unref, 0);

    return combo_box;
}

static void
owner_change_free (OwnerChange *change)
{
    nautilus_file_unref (change->file);
    g_free (change->owner);
    g_object_unref (change->window);

    g_free (change);
}

static void
owner_change_callback (NautilusFile *file,
                       GFile        *result_location,
                       GError       *error,
                       OwnerChange  *change)
{
    NautilusPropertiesWindow *window;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->owner != NULL);

    if (!change->cancelled)
    {
        /* Report the error if it's an error. */
        eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
        nautilus_report_error_setting_owner (file, error, change->window);
    }

    window = NAUTILUS_PROPERTIES_WINDOW (change->window);
    if (window->owner_change == change)
    {
        window->owner_change = NULL;
    }

    owner_change_free (change);
}

static void
cancel_owner_change_callback (OwnerChange *change)
{
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->owner != NULL);

    change->cancelled = TRUE;
    nautilus_file_cancel (change->file, (NautilusFileOperationCallback) owner_change_callback, change);
}

static gboolean
schedule_owner_change_timeout (OwnerChange *change)
{
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (change->window));
    g_assert (NAUTILUS_IS_FILE (change->file));
    g_assert (change->owner != NULL);

    change->timeout = 0;

    eel_timed_wait_start
        ((EelCancelCallback) cancel_owner_change_callback,
        change,
        _("Cancel Owner Change?"),
        change->window);

    nautilus_file_set_owner
        (change->file, change->owner,
        (NautilusFileOperationCallback) owner_change_callback, change);

    return FALSE;
}

static void
schedule_owner_change (NautilusPropertiesWindow *window,
                       NautilusFile             *file,
                       const char               *owner)
{
    OwnerChange *change;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));
    g_assert (window->owner_change == NULL);
    g_assert (NAUTILUS_IS_FILE (file));

    change = g_new0 (OwnerChange, 1);

    change->file = nautilus_file_ref (file);
    change->owner = g_strdup (owner);
    change->window = GTK_WINDOW (g_object_ref (window));
    change->timeout =
        g_timeout_add (CHOWN_CHGRP_TIMEOUT,
                       (GSourceFunc) schedule_owner_change_timeout,
                       change);

    window->owner_change = change;
}

static void
unschedule_or_cancel_owner_change (NautilusPropertiesWindow *window)
{
    OwnerChange *change;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    change = window->owner_change;

    if (change != NULL)
    {
        g_assert (NAUTILUS_IS_FILE (change->file));

        if (change->timeout == 0)
        {
            /* The operation was started, cancel it and let the operation callback free the change */
            cancel_owner_change_callback (change);
            eel_timed_wait_stop ((EelCancelCallback) cancel_owner_change_callback, change);
        }
        else
        {
            g_source_remove (change->timeout);
            owner_change_free (change);
        }

        window->owner_change = NULL;
    }
}

static void
changed_owner_callback (GtkComboBox  *combo_box,
                        NautilusFile *file)
{
    NautilusPropertiesWindow *window;
    char *new_owner;
    char *cur_owner;

    g_assert (GTK_IS_COMBO_BOX (combo_box));
    g_assert (NAUTILUS_IS_FILE (file));

    new_owner = combo_box_get_active_entry (combo_box, 2);
    if (!new_owner)
    {
        return;
    }
    cur_owner = nautilus_file_get_owner_name (file);

    if (strcmp (new_owner, cur_owner) != 0)
    {
        /* Try to change file owner. If this fails, complain to user. */
        window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (combo_box), GTK_TYPE_WINDOW));

        unschedule_or_cancel_owner_change (window);
        schedule_owner_change (window, file, new_owner);
    }
    g_free (new_owner);
    g_free (cur_owner);
}

static void
synch_user_menu (GtkComboBox  *combo_box,
                 NautilusFile *file)
{
    GList *users;
    GList *node;
    GtkTreeModel *model;
    GtkListStore *store;
    GtkTreeIter iter;
    char *user_name;
    char *owner_name;
    char *nice_owner_name;
    int user_index;
    int owner_index;
    char **name_array;
    char *combo_text;

    g_assert (GTK_IS_COMBO_BOX (combo_box));
    g_assert (NAUTILUS_IS_FILE (file));

    if (nautilus_file_is_gone (file))
    {
        return;
    }

    users = nautilus_get_user_names ();

    model = gtk_combo_box_get_model (combo_box);
    store = GTK_LIST_STORE (model);
    g_assert (GTK_IS_LIST_STORE (model));

    if (!tree_model_entries_equal (model, 1, users))
    {
        /* Clear the contents of ComboBox in a wacky way because there
         * is no function to clear all items and also no function to obtain
         * the number of items in a combobox.
         */
        gtk_list_store_clear (store);

        for (node = users, user_index = 0; node != NULL; node = node->next, ++user_index)
        {
            user_name = (char *) node->data;

            name_array = g_strsplit (user_name, "\n", 2);
            if (name_array[1] != NULL && *name_array[1] != 0)
            {
                combo_text = g_strdup_printf ("%s - %s", name_array[0], name_array[1]);
            }
            else
            {
                combo_text = g_strdup (name_array[0]);
            }

            gtk_list_store_append (store, &iter);
            gtk_list_store_set (store, &iter,
                                0, combo_text,
                                1, user_name,
                                2, name_array[0],
                                -1);

            g_strfreev (name_array);
            g_free (combo_text);
        }
    }

    owner_name = nautilus_file_get_owner_name (file);
    owner_index = tree_model_get_entry_index (model, 2, owner_name);
    nice_owner_name = nautilus_file_get_string_attribute (file, "owner");

    /* If owner wasn't in list, we prepend it (with a separator).
     * This can happen if the owner is an id with no matching
     * identifier in the passwords file.
     */
    if (owner_index < 0 && owner_name != NULL)
    {
        if (users != NULL)
        {
            /* add separator */
            gtk_list_store_prepend (store, &iter);
            gtk_list_store_set (store, &iter,
                                0, "-",
                                1, NULL,
                                2, NULL,
                                -1);
        }

        owner_index = 0;

        gtk_list_store_prepend (store, &iter);
        gtk_list_store_set (store, &iter,
                            0, nice_owner_name,
                            1, owner_name,
                            2, owner_name,
                            -1);
    }

    gtk_combo_box_set_active (combo_box, owner_index);

    g_free (owner_name);
    g_free (nice_owner_name);
    g_list_free_full (users, g_free);
}

static GtkComboBox *
attach_owner_combo_box (GtkGrid      *grid,
                        GtkWidget    *sibling,
                        NautilusFile *file)
{
    GtkComboBox *combo_box;

    combo_box = attach_combo_box (grid, sibling, TRUE);

    synch_user_menu (combo_box, file);

    /* Connect to signal to update menu when file changes. */
    g_signal_connect_object (file, "changed",
                             G_CALLBACK (synch_user_menu),
                             combo_box, G_CONNECT_SWAPPED);
    g_signal_connect_data (combo_box, "changed",
                           G_CALLBACK (changed_owner_callback),
                           nautilus_file_ref (file),
                           (GClosureNotify) nautilus_file_unref, 0);

    return combo_box;
}

static gboolean
file_has_prefix (NautilusFile *file,
                 GList        *prefix_candidates)
{
    GList *p;
    GFile *location, *candidate_location;

    location = nautilus_file_get_location (file);

    for (p = prefix_candidates; p != NULL; p = p->next)
    {
        if (file == p->data)
        {
            continue;
        }

        candidate_location = nautilus_file_get_location (NAUTILUS_FILE (p->data));
        if (g_file_has_prefix (location, candidate_location))
        {
            g_object_unref (location);
            g_object_unref (candidate_location);
            return TRUE;
        }
        g_object_unref (candidate_location);
    }

    g_object_unref (location);

    return FALSE;
}

static void
directory_contents_value_field_update (NautilusPropertiesWindow *window)
{
    NautilusRequestStatus file_status;
    char *text, *temp;
    guint directory_count;
    guint file_count;
    guint total_count;
    guint unreadable_directory_count;
    goffset total_size;
    gboolean used_two_lines;
    NautilusFile *file;
    GList *l;
    guint file_unreadable;
    goffset file_size;
    gboolean deep_count_active;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    total_count = 0;
    total_size = 0;
    unreadable_directory_count = FALSE;

    for (l = window->target_files; l; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);

        if (file_has_prefix (file, window->target_files))
        {
            /* don't count nested files twice */
            continue;
        }

        if (nautilus_file_is_directory (file))
        {
            file_status = nautilus_file_get_deep_counts (file,
                                                         &directory_count,
                                                         &file_count,
                                                         &file_unreadable,
                                                         &file_size,
                                                         TRUE);
            total_count += (file_count + directory_count);
            total_size += file_size;

            if (file_unreadable)
            {
                unreadable_directory_count = TRUE;
            }

            if (file_status == NAUTILUS_REQUEST_DONE)
            {
                stop_deep_count_for_file (window, file);
            }
        }
        else
        {
            ++total_count;
            total_size += nautilus_file_get_size (file);
        }
    }

    deep_count_active = (window->deep_count_files != NULL);
    /* If we've already displayed the total once, don't do another visible
     * count-up if the deep_count happens to get invalidated.
     * But still display the new total, since it might have changed.
     */
    if (window->deep_count_finished && deep_count_active)
    {
        return;
    }

    text = NULL;
    used_two_lines = FALSE;

    if (total_count == 0)
    {
        if (!deep_count_active)
        {
            if (unreadable_directory_count == 0)
            {
                text = g_strdup (_("nothing"));
            }
            else
            {
                text = g_strdup (_("unreadable"));
            }
        }
        else
        {
            text = g_strdup ("…");
        }
    }
    else
    {
        char *size_str;
        size_str = g_format_size (total_size);
        text = g_strdup_printf (ngettext ("%'d item, with size %s",
                                          "%'d items, totalling %s",
                                          total_count),
                                total_count, size_str);
        g_free (size_str);

        if (unreadable_directory_count != 0)
        {
            temp = text;
            text = g_strconcat (temp, "\n",
                                _("(some contents unreadable)"),
                                NULL);
            g_free (temp);
            used_two_lines = TRUE;
        }
    }

    gtk_label_set_text (window->directory_contents_value_field,
                        text);
    g_free (text);

    /* Also set the title field here, with a trailing carriage return &
     * space if the value field has two lines. This is a hack to get the
     * "Contents:" title to line up with the first line of the
     * 2-line value. Maybe there's a better way to do this, but I
     * couldn't think of one.
     */
    text = g_strdup (_("Contents:"));
    if (used_two_lines)
    {
        temp = text;
        text = g_strconcat (temp, "\n ", NULL);
        g_free (temp);
    }
    gtk_label_set_text (window->directory_contents_title_field,
                        text);
    g_free (text);

    if (!deep_count_active)
    {
        window->deep_count_finished = TRUE;
        stop_spinner (window);
    }
}

static gboolean
update_directory_contents_callback (gpointer data)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (data);

    window->update_directory_contents_timeout_id = 0;
    directory_contents_value_field_update (window);

    return FALSE;
}

static void
schedule_directory_contents_update (NautilusPropertiesWindow *window)
{
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    if (window->update_directory_contents_timeout_id == 0)
    {
        window->update_directory_contents_timeout_id
            = g_timeout_add (DIRECTORY_CONTENTS_UPDATE_INTERVAL,
                             update_directory_contents_callback,
                             window);
    }
}

static GtkLabel *
attach_directory_contents_value_field (NautilusPropertiesWindow *window,
                                       GtkGrid                  *grid,
                                       GtkWidget                *sibling)
{
    GtkLabel *value_field;

    value_field = attach_value_label (grid, sibling, "");

    g_assert (window->directory_contents_value_field == NULL);
    window->directory_contents_value_field = value_field;

    gtk_label_set_line_wrap (value_field, TRUE);

    return value_field;
}

static GtkLabel *
attach_title_field (GtkGrid    *grid,
                    const char *title)
{
    return attach_label (grid, NULL, title, FALSE, FALSE, TRUE);
}

#define INCONSISTENT_STATE_STRING \
    "\xE2\x80\x92"

static void
append_title_value_pair (NautilusPropertiesWindow *window,
                         GtkGrid                  *grid,
                         const char               *title,
                         const char               *file_attribute_name,
                         const char               *inconsistent_state,
                         gboolean                  show_original)
{
    GtkLabel *title_label;
    GtkWidget *value;

    title_label = attach_title_field (grid, title);
    value = attach_value_field (window, grid, GTK_WIDGET (title_label),
                                file_attribute_name,
                                inconsistent_state,
                                show_original);
    gtk_label_set_mnemonic_widget (title_label, value);
}

static void
append_title_and_ellipsizing_value (NautilusPropertiesWindow *window,
                                    GtkGrid                  *grid,
                                    const char               *title,
                                    const char               *file_attribute_name,
                                    const char               *inconsistent_state,
                                    gboolean                  show_original)
{
    GtkLabel *title_label;
    GtkWidget *value;

    title_label = attach_title_field (grid, title);
    value = attach_ellipsizing_value_field (window, grid,
                                            GTK_WIDGET (title_label),
                                            file_attribute_name,
                                            inconsistent_state,
                                            show_original);
    gtk_label_set_mnemonic_widget (title_label, value);
}

static void
append_directory_contents_fields (NautilusPropertiesWindow *window,
                                  GtkGrid                  *grid)
{
    GtkLabel *title_field, *value_field;
    GList *l;

    title_field = attach_title_field (grid, "");
    window->directory_contents_title_field = title_field;
    gtk_label_set_line_wrap (title_field, TRUE);

    value_field = attach_directory_contents_value_field (window, grid, GTK_WIDGET (title_field));

    window->directory_contents_spinner = gtk_spinner_new ();

    gtk_grid_attach_next_to (grid,
                             window->directory_contents_spinner,
                             GTK_WIDGET (value_field),
                             GTK_POS_RIGHT,
                             1, 1);

    for (l = window->target_files; l; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);
        start_deep_count_for_file (window, file);
    }

    /* Fill in the initial value. */
    directory_contents_value_field_update (window);

    gtk_label_set_mnemonic_widget (title_field, GTK_WIDGET (value_field));
}

static GtkWidget *
create_page_with_box (GtkNotebook    *notebook,
                      GtkOrientation  orientation,
                      const gchar    *title,
                      const gchar    *help_uri)
{
    GtkWidget *box;

    g_assert (GTK_IS_NOTEBOOK (notebook));
    g_assert (title != NULL);

    box = gtk_box_new (orientation, 0);
    gtk_widget_show (box);
    gtk_container_set_border_width (GTK_CONTAINER (box), 12);
    if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
        gtk_box_set_spacing (GTK_BOX (box), 12);
    }
    gtk_notebook_append_page (notebook, box, gtk_label_new (title));
    gtk_container_child_set (GTK_CONTAINER (notebook),
                             box,
                             "tab-expand", TRUE,
                             NULL);
    g_object_set_data_full (G_OBJECT (box), "help-uri", g_strdup (help_uri), g_free);

    return box;
}

static GtkWidget *
append_blank_row (GtkGrid *grid)
{
    return GTK_WIDGET (attach_title_field (grid, ""));
}

static void
append_blank_slim_row (GtkGrid *grid)
{
    GtkWidget *w;
    PangoAttribute *attribute;
    PangoAttrList *attr_list;

    attr_list = pango_attr_list_new ();
    attribute = pango_attr_scale_new (0.30);
    pango_attr_list_insert (attr_list, attribute);

    w = gtk_label_new (NULL);
    gtk_label_set_attributes (GTK_LABEL (w), attr_list);
    gtk_widget_show (w);

    pango_attr_list_unref (attr_list);

    gtk_container_add (GTK_CONTAINER (grid), w);
}

static GtkWidget *
create_grid_with_standard_properties (void)
{
    GtkWidget *grid;

    grid = gtk_grid_new ();
    gtk_container_set_border_width (GTK_CONTAINER (grid), 6);
    gtk_grid_set_row_spacing (GTK_GRID (grid), ROW_PAD);
    gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
    gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
    gtk_widget_show (grid);

    return grid;
}

static gboolean
is_root_directory (NautilusFile *file)
{
    GFile *location;
    gboolean result;

    location = nautilus_file_get_location (file);
    result = nautilus_is_root_directory (location);
    g_object_unref (location);

    return result;
}

static gboolean
is_network_directory (NautilusFile *file)
{
    char *file_uri;
    gboolean result;

    file_uri = nautilus_file_get_uri (file);
    result = strcmp (file_uri, "network:///") == 0;
    g_free (file_uri);

    return result;
}

static gboolean
is_burn_directory (NautilusFile *file)
{
    char *file_uri;
    gboolean result;

    file_uri = nautilus_file_get_uri (file);
    result = strcmp (file_uri, "burn:///") == 0;
    g_free (file_uri);

    return result;
}

static gboolean
should_show_custom_icon_buttons (NautilusPropertiesWindow *window)
{
    if (is_multi_file_window (window))
    {
        return FALSE;
    }

    return TRUE;
}

static gboolean
should_show_file_type (NautilusPropertiesWindow *window)
{
    if (!is_multi_file_window (window)
        && (nautilus_file_is_in_trash (get_target_file (window)) ||
            is_network_directory (get_target_file (window)) ||
            is_burn_directory (get_target_file (window))))
    {
        return FALSE;
    }


    return TRUE;
}

static gboolean
should_show_location_info (NautilusPropertiesWindow *window)
{
    GList *l;

    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
            is_root_directory (NAUTILUS_FILE (l->data)) ||
            is_network_directory (NAUTILUS_FILE (l->data)) ||
            is_burn_directory (NAUTILUS_FILE (l->data)))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean
should_show_trash_orig_path (NautilusPropertiesWindow *window)
{
    GList *l;

    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean
should_show_accessed_date (NautilusPropertiesWindow *window)
{
    /* Accessed date for directory seems useless. If we some
     * day decide that it is useful, we should separately
     * consider whether it's useful for "trash:".
     */
    if (file_list_all_directories (window->target_files)
       || is_multi_file_window (window))
    {
        return FALSE;
    }

    return TRUE;
}

static gboolean
should_show_modified_date (NautilusPropertiesWindow *window)
{
    return !is_multi_file_window (window);
}

static gboolean
should_show_trashed_on (NautilusPropertiesWindow *window)
{
    GList *l;

    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (!nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean
should_show_link_target (NautilusPropertiesWindow *window)
{
    if (!is_multi_file_window (window)
        && nautilus_file_is_symbolic_link (get_target_file (window)))
    {
        return TRUE;
    }

    return FALSE;
}

static gboolean
location_show_original (NautilusPropertiesWindow *window)
{
    NautilusFile *file;

    /* there is no way a recent item will be mixed with
     *   other items so just pick the first file to check */
    file = NAUTILUS_FILE (g_list_nth_data (window->original_files, 0));
    return (file != NULL && !nautilus_file_is_in_recent (file));
}

static gboolean
should_show_free_space (NautilusPropertiesWindow *window)
{
    if (!is_multi_file_window (window)
        && (nautilus_file_is_in_trash (get_target_file (window)) ||
            is_network_directory (get_target_file (window)) ||
            nautilus_file_is_in_recent (get_target_file (window)) ||
            is_burn_directory (get_target_file (window))))
    {
        return FALSE;
    }

    if (file_list_all_directories (window->target_files))
    {
        return TRUE;
    }

    return FALSE;
}

static gboolean
should_show_volume_info (NautilusPropertiesWindow *window)
{
    NautilusFile *file;

    if (is_multi_file_window (window))
    {
        return FALSE;
    }

    file = get_original_file (window);

    if (file == NULL)
    {
        return FALSE;
    }

    if (nautilus_file_can_unmount (file))
    {
        return TRUE;
    }

    return FALSE;
}

static gboolean
should_show_volume_usage (NautilusPropertiesWindow *window)
{
    NautilusFile *file;
    gboolean success = FALSE;

    if (is_multi_file_window (window))
    {
        return FALSE;
    }

    file = get_original_file (window);

    if (file == NULL)
    {
        return FALSE;
    }

    if (nautilus_file_can_unmount (file))
    {
        return TRUE;
    }

    success = is_root_directory (file);

#ifdef TODO_GIO
    /* Look at is_mountpoint for activation uri */
#endif

    return success;
}

static void
paint_legend (GtkWidget *widget,
              cairo_t   *cr,
              gpointer   data)
{
    GtkStyleContext *context;
    GtkAllocation allocation;

    gtk_widget_get_allocation (widget, &allocation);
    context = gtk_widget_get_style_context (widget);

    gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
    gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height);
}

static void
paint_slice (GtkWidget   *widget,
             cairo_t     *cr,
             double       percent_start,
             double       percent_width,
             const gchar *style_class)
{
    double angle1;
    double angle2;
    gboolean full;
    double offset = G_PI / 2.0;
    GdkRGBA fill;
    GdkRGBA stroke;
    GtkStateFlags state;
    GtkBorder border;
    GtkStyleContext *context;
    double x, y, radius;
    gint width, height;

    if (percent_width < .01)
    {
        return;
    }

    context = gtk_widget_get_style_context (widget);
    state = gtk_style_context_get_state (context);
    gtk_style_context_get_border (context, state, &border);

    gtk_style_context_save (context);
    gtk_style_context_add_class (context, style_class);
    gtk_style_context_get_color (context, state, &fill);
    gtk_style_context_add_class (context, "border");
    gtk_style_context_get_color (context, state, &stroke);
    gtk_style_context_restore (context);

    width = gtk_widget_get_allocated_width (widget);
    height = gtk_widget_get_allocated_height (widget);
    x = width / 2;
    y = height / 2;

    if (width < height)
    {
        radius = (width - border.left) / 2;
    }
    else
    {
        radius = (height - border.top) / 2;
    }

    angle1 = (percent_start * 2 * G_PI) - offset;
    angle2 = angle1 + (percent_width * 2 * G_PI);

    full = (percent_width > .99);

    if (!full)
    {
        cairo_move_to (cr, x, y);
    }
    cairo_arc (cr, x, y, radius, angle1, angle2);

    if (!full)
    {
        cairo_line_to (cr, x, y);
    }

    cairo_set_line_width (cr, border.top);
    gdk_cairo_set_source_rgba (cr, &fill);
    cairo_fill_preserve (cr);

    gdk_cairo_set_source_rgba (cr, &stroke);
    cairo_stroke (cr);
}

static void
paint_pie_chart (GtkWidget *widget,
                 cairo_t   *cr,
                 gpointer   data)
{
    NautilusPropertiesWindow *window;
    double free, used, reserved;

    window = NAUTILUS_PROPERTIES_WINDOW (data);

    free = (double) window->volume_free / (double) window->volume_capacity;
    used = (double) window->volume_used / (double) window->volume_capacity;
    reserved = 1.0 - (used + free);

    paint_slice (widget, cr,
                 0, free, "free");
    paint_slice (widget, cr,
                 free + used, reserved, "unknown");
    /* paint the used last so its slice strokes are on top */
    paint_slice (widget, cr,
                 free, used, "used");
}

static GtkWidget *
create_pie_widget (NautilusPropertiesWindow *window)
{
    NautilusFile *file;
    GtkGrid *grid;
    GtkStyleContext *style;
    GtkWidget *pie_canvas;
    GtkWidget *used_canvas;
    GtkWidget *used_label;
    GtkWidget *used_type_label;
    GtkWidget *free_canvas;
    GtkWidget *free_label;
    GtkWidget *free_type_label;
    GtkWidget *capacity_label;
    GtkWidget *capacity_value_label;
    GtkWidget *fstype_label;
    GtkWidget *fstype_value_label;
    GtkWidget *spacer_label;
    gchar *capacity;
    gchar *used;
    gchar *free;
    const char *fs_type;
    gchar *uri;
    GFile *location;
    GFileInfo *info;

    capacity = g_format_size (window->volume_capacity);
    free = g_format_size (window->volume_free);
    used = g_format_size (window->volume_used);

    file = get_original_file (window);

    uri = nautilus_file_get_activation_uri (file);

    grid = GTK_GRID (gtk_grid_new ());
    gtk_widget_set_hexpand (GTK_WIDGET (grid), FALSE);
    gtk_container_set_border_width (GTK_CONTAINER (grid), 5);
    gtk_grid_set_row_spacing (GTK_GRID (grid), 10);
    gtk_grid_set_column_spacing (GTK_GRID (grid), 10);

    pie_canvas = gtk_drawing_area_new ();
    gtk_widget_set_size_request (pie_canvas, 200, 200);
    style = gtk_widget_get_style_context (pie_canvas);
    gtk_style_context_add_class (style, "disk-space-display");

    used_canvas = gtk_drawing_area_new ();
    gtk_widget_set_size_request (used_canvas, 20, 20);
    style = gtk_widget_get_style_context (used_canvas);
    gtk_style_context_add_class (style, "disk-space-display");
    gtk_style_context_add_class (style, "used");

    used_label = gtk_label_new (used);
    /* Translators: "used" refers to the capacity of the filesystem */
    used_type_label = gtk_label_new (_("used"));

    free_canvas = gtk_drawing_area_new ();
    gtk_widget_set_size_request (free_canvas, 20, 20);
    style = gtk_widget_get_style_context (free_canvas);
    gtk_style_context_add_class (style, "disk-space-display");
    gtk_style_context_add_class (style, "free");

    free_label = gtk_label_new (free);
    /* Translators: "free" refers to the capacity of the filesystem */
    free_type_label = gtk_label_new (_("free"));

    capacity_label = gtk_label_new (_("Total capacity:"));
    capacity_value_label = gtk_label_new (capacity);

    fstype_label = gtk_label_new (_("Filesystem type:"));
    fstype_value_label = gtk_label_new (NULL);

    spacer_label = gtk_label_new ("");

    location = g_file_new_for_uri (uri);
    info = g_file_query_filesystem_info (location, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE,
                                         NULL, NULL);
    if (info)
    {
        fs_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE);
        if (fs_type != NULL)
        {
            gtk_label_set_text (GTK_LABEL (fstype_value_label), fs_type);
        }

        g_object_unref (info);
    }
    g_object_unref (location);

    g_free (uri);
    g_free (capacity);
    g_free (used);
    g_free (free);

    gtk_container_add_with_properties (GTK_CONTAINER (grid), pie_canvas,
                                       "height", 5,
                                       NULL);

    gtk_widget_set_vexpand (spacer_label, TRUE);
    gtk_grid_attach_next_to (grid, spacer_label, pie_canvas,
                             GTK_POS_RIGHT, 1, 1);

    gtk_widget_set_halign (used_canvas, GTK_ALIGN_END);
    gtk_widget_set_vexpand (used_canvas, FALSE);
    gtk_grid_attach_next_to (grid, used_canvas, spacer_label,
                             GTK_POS_BOTTOM, 1, 1);
    gtk_widget_set_halign (used_label, GTK_ALIGN_END);
    gtk_widget_set_vexpand (used_label, FALSE);
    gtk_grid_attach_next_to (grid, used_label, used_canvas,
                             GTK_POS_RIGHT, 1, 1);
    gtk_widget_set_halign (used_type_label, GTK_ALIGN_START);
    gtk_widget_set_vexpand (used_type_label, FALSE);
    gtk_grid_attach_next_to (grid, used_type_label, used_label,
                             GTK_POS_RIGHT, 1, 1);

    gtk_widget_set_halign (free_canvas, GTK_ALIGN_END);
    gtk_widget_set_vexpand (free_canvas, FALSE);
    gtk_grid_attach_next_to (grid, free_canvas, used_canvas,
                             GTK_POS_BOTTOM, 1, 1);
    gtk_widget_set_halign (free_label, GTK_ALIGN_END);
    gtk_widget_set_vexpand (free_label, FALSE);
    gtk_grid_attach_next_to (grid, free_label, free_canvas,
                             GTK_POS_RIGHT, 1, 1);
    gtk_widget_set_halign (free_type_label, GTK_ALIGN_START);
    gtk_widget_set_vexpand (free_type_label, FALSE);
    gtk_grid_attach_next_to (grid, free_type_label, free_label,
                             GTK_POS_RIGHT, 1, 1);

    gtk_widget_set_halign (capacity_label, GTK_ALIGN_END);
    gtk_widget_set_vexpand (capacity_label, FALSE);
    gtk_grid_attach_next_to (grid, capacity_label, free_canvas,
                             GTK_POS_BOTTOM, 1, 1);
    gtk_widget_set_halign (capacity_value_label, GTK_ALIGN_START);
    gtk_widget_set_vexpand (capacity_value_label, FALSE);
    gtk_grid_attach_next_to (grid, capacity_value_label, capacity_label,
                             GTK_POS_RIGHT, 1, 1);

    gtk_widget_set_halign (fstype_label, GTK_ALIGN_END);
    gtk_widget_set_vexpand (fstype_label, FALSE);
    gtk_grid_attach_next_to (grid, fstype_label, capacity_label,
                             GTK_POS_BOTTOM, 1, 1);
    gtk_widget_set_halign (fstype_value_label, GTK_ALIGN_START);
    gtk_widget_set_vexpand (fstype_value_label, FALSE);
    gtk_grid_attach_next_to (grid, fstype_value_label, fstype_label,
                             GTK_POS_RIGHT, 1, 1);

    g_signal_connect (pie_canvas, "draw",
                      G_CALLBACK (paint_pie_chart), window);
    g_signal_connect (used_canvas, "draw",
                      G_CALLBACK (paint_legend), window);
    g_signal_connect (free_canvas, "draw",
                      G_CALLBACK (paint_legend), window);

    return GTK_WIDGET (grid);
}

static GtkWidget *
create_volume_usage_widget (NautilusPropertiesWindow *window)
{
    GtkWidget *piewidget = NULL;
    gchar *uri;
    NautilusFile *file;
    GFile *location;
    GFileInfo *info;

    file = get_original_file (window);

    uri = nautilus_file_get_activation_uri (file);

    location = g_file_new_for_uri (uri);
    info = g_file_query_filesystem_info (location, "filesystem::*", NULL, NULL);

    if (info)
    {
        window->volume_capacity = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
        window->volume_free = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
        if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED))
        {
            window->volume_used = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED);
        }
        else
        {
            window->volume_used = window->volume_capacity - window->volume_free;
        }

        g_object_unref (info);
    }
    else
    {
        window->volume_capacity = 0;
        window->volume_free = 0;
        window->volume_used = 0;
    }

    g_object_unref (location);

    if (window->volume_capacity > 0)
    {
        piewidget = create_pie_widget (window);
        gtk_widget_show_all (piewidget);
    }

    return piewidget;
}

static void
open_in_disks (GtkButton                *button,
               NautilusPropertiesWindow *self)
{
    g_autofree char *message = NULL;
    g_autoptr (GError) error = NULL;
    g_autoptr (GAppInfo) app_info = NULL;

    app_info = g_app_info_create_from_commandline ("gnome-disks",
                                                    NULL,
                                                    G_APP_INFO_CREATE_NONE,
                                                    NULL);

    g_app_info_launch (app_info, NULL, NULL, &error);

    if (error != NULL)
    {
        message = g_strdup_printf (_("Details: %s"), error->message);
        show_dialog (_("There was an error launching the application."),
                     message,
                     GTK_WINDOW (self),
                     GTK_MESSAGE_ERROR);
    }
}

static void
create_basic_page (NautilusPropertiesWindow *window)
{
    GtkGrid *grid;
    GtkWidget *icon_pixmap_widget;
    GtkWidget *volume_usage;
    GtkWidget *hbox, *vbox;
    GtkWidget *button;

    hbox = create_page_with_box (window->notebook,
                                 GTK_ORIENTATION_HORIZONTAL,
                                 _("Basic"),
                                 "help:gnome-help/nautilus-file-properties-basic");

    /* Icon pixmap */

    icon_pixmap_widget = create_image_widget (
        window, should_show_custom_icon_buttons (window));
    gtk_widget_set_valign (icon_pixmap_widget, GTK_ALIGN_START);
    gtk_widget_show (icon_pixmap_widget);

    gtk_box_pack_start (GTK_BOX (hbox), icon_pixmap_widget, FALSE, FALSE, 0);

    window->icon_chooser = NULL;

    /* Grid */

    vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_show (vbox);
    gtk_container_add (GTK_CONTAINER (hbox), vbox);

    grid = GTK_GRID (create_grid_with_standard_properties ());
    gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (grid), FALSE, FALSE, 0);
    window->basic_grid = grid;

    /* Name label.  The text will be determined in update_name_field */
    window->name_label = attach_title_field (grid, NULL);

    /* Name field */
    window->name_field = NULL;
    update_name_field (window);

    /* Start with name field selected, if it's an entry. */
    if (GTK_IS_ENTRY (window->name_field))
    {
        gtk_widget_grab_focus (GTK_WIDGET (window->name_field));
    }

    if (should_show_file_type (window))
    {
        append_title_and_ellipsizing_value (window, grid,
                                            _("Type:"),
                                            "detailed_type",
                                            INCONSISTENT_STATE_STRING,
                                            FALSE);
    }

    if (should_show_link_target (window))
    {
        append_title_and_ellipsizing_value (window, grid,
                                            _("Link target:"),
                                            "link_target",
                                            INCONSISTENT_STATE_STRING,
                                            FALSE);
    }

    if (is_multi_file_window (window) ||
        nautilus_file_is_directory (get_target_file (window)))
    {
        append_directory_contents_fields (window, grid);
    }
    else
    {
        append_title_value_pair (window, grid, _("Size:"),
                                 "size_detail",
                                 INCONSISTENT_STATE_STRING,
                                 FALSE);
    }

    append_blank_row (grid);

    if (should_show_location_info (window))
    {
        append_title_and_ellipsizing_value (window, grid, _("Parent folder:"),
                                            "where",
                                            INCONSISTENT_STATE_STRING,
                                            location_show_original (window));
    }

    if (should_show_trash_orig_path (window))
    {
        append_title_and_ellipsizing_value (window, grid, _("Original folder:"),
                                            "trash_orig_path",
                                            INCONSISTENT_STATE_STRING,
                                            FALSE);
    }

    if (should_show_volume_info (window))
    {
        append_title_and_ellipsizing_value (window, grid,
                                            _("Volume:"),
                                            "volume",
                                            INCONSISTENT_STATE_STRING,
                                            FALSE);
    }

    if (should_show_trashed_on (window))
    {
        append_title_and_ellipsizing_value (window, grid, _("Trashed on:"),
                                            "trashed_on_full",
                                            INCONSISTENT_STATE_STRING,
                                            FALSE);
    }

    if (should_show_accessed_date (window)
       || should_show_modified_date (window))
    {
        append_blank_row (grid);
    }

    if (should_show_accessed_date (window))
    {
        append_title_value_pair (window, grid, _("Accessed:"),
                                 "date_accessed_full",
                                 INCONSISTENT_STATE_STRING,
                                 FALSE);
    }

    if (should_show_modified_date (window))
    {
        append_title_value_pair (window, grid, _("Modified:"),
                                 "date_modified_full",
                                 INCONSISTENT_STATE_STRING,
                                 FALSE);
    }

    if (should_show_free_space (window)
        && !should_show_volume_usage (window))
    {
        append_blank_row (grid);

        append_title_value_pair (window, grid, _("Free space:"),
                                 "free_space",
                                 INCONSISTENT_STATE_STRING,
                                 FALSE);
    }

    if (should_show_volume_usage (window))
    {
        volume_usage = create_volume_usage_widget (window);
        if (volume_usage != NULL)
        {
            gtk_container_add_with_properties (GTK_CONTAINER (grid),
                                               volume_usage,
                                               "width", 3,
                                               NULL);
        }

       /*Translators: Here Disks mean the name of application GNOME Disks.*/
       button = gtk_button_new_with_label (_("Open in Disks"));
       g_signal_connect (button, "clicked", G_CALLBACK (open_in_disks), NULL);
       gtk_container_add_with_properties (GTK_CONTAINER (grid),
                                          button,
                                          "width", 3,
                                          NULL);
       gtk_widget_show_all (GTK_WIDGET (grid));
    }
}

static gboolean
files_has_directory (NautilusPropertiesWindow *window)
{
    GList *l;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        file = NAUTILUS_FILE (l->data);
        if (nautilus_file_is_directory (file))
        {
            return TRUE;
        }
    }

    return FALSE;
}

static gboolean
files_has_changable_permissions_directory (NautilusPropertiesWindow *window)
{
    GList *l;
    gboolean changable = FALSE;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        file = NAUTILUS_FILE (l->data);
        if (nautilus_file_is_directory (file) &&
            nautilus_file_can_get_permissions (file) &&
            nautilus_file_can_set_permissions (file))
        {
            changable = TRUE;
        }
        else
        {
            changable = FALSE;
            break;
        }
    }

    return changable;
}

static gboolean
files_has_file (NautilusPropertiesWindow *window)
{
    GList *l;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        file = NAUTILUS_FILE (l->data);
        if (!nautilus_file_is_directory (file))
        {
            return TRUE;
        }
    }

    return FALSE;
}

static void
start_long_operation (NautilusPropertiesWindow *window)
{
    if (window->long_operation_underway == 0)
    {
        /* start long operation */
        GdkDisplay *display;
        GdkCursor *cursor;

        display = gtk_widget_get_display (GTK_WIDGET (window));
        cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), cursor);
        g_object_unref (cursor);
    }
    window->long_operation_underway++;
}

static void
end_long_operation (NautilusPropertiesWindow *window)
{
    if (gtk_widget_get_window (GTK_WIDGET (window)) != NULL &&
        window->long_operation_underway == 1)
    {
        /* finished !! */
        gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
    }
    window->long_operation_underway--;
}

static void
permission_change_callback (NautilusFile *file,
                            GFile        *res_loc,
                            GError       *error,
                            gpointer      callback_data)
{
    NautilusPropertiesWindow *window;
    g_assert (callback_data != NULL);

    window = NAUTILUS_PROPERTIES_WINDOW (callback_data);
    end_long_operation (window);

    /* Report the error if it's an error. */
    nautilus_report_error_setting_permissions (file, error, GTK_WINDOW (window));

    g_object_unref (window);
}

static void
update_permissions (NautilusPropertiesWindow *window,
                    guint32                   vfs_new_perm,
                    guint32                   vfs_mask,
                    gboolean                  is_folder,
                    gboolean                  apply_to_both_folder_and_dir,
                    gboolean                  use_original)
{
    GList *l;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        guint32 permissions;

        file = NAUTILUS_FILE (l->data);

        if (!nautilus_file_can_get_permissions (file))
        {
            continue;
        }

        if (!apply_to_both_folder_and_dir &&
            ((nautilus_file_is_directory (file) && !is_folder) ||
             (!nautilus_file_is_directory (file) && is_folder)))
        {
            continue;
        }

        permissions = nautilus_file_get_permissions (file);
        if (use_original)
        {
            gpointer ptr;
            if (g_hash_table_lookup_extended (window->initial_permissions,
                                              file, NULL, &ptr))
            {
                permissions = (permissions & ~vfs_mask) | (GPOINTER_TO_INT (ptr) & vfs_mask);
            }
        }
        else
        {
            permissions = (permissions & ~vfs_mask) | vfs_new_perm;
        }

        start_long_operation (window);
        g_object_ref (window);
        nautilus_file_set_permissions
            (file, permissions,
            permission_change_callback,
            window);
    }
}

static gboolean
initial_permission_state_consistent (NautilusPropertiesWindow *window,
                                     guint32                   mask,
                                     gboolean                  is_folder,
                                     gboolean                  both_folder_and_dir)
{
    GList *l;
    gboolean first;
    guint32 first_permissions;

    first = TRUE;
    first_permissions = 0;
    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        guint32 permissions;

        file = l->data;

        if (!both_folder_and_dir &&
            ((nautilus_file_is_directory (file) && !is_folder) ||
             (!nautilus_file_is_directory (file) && is_folder)))
        {
            continue;
        }

        permissions = GPOINTER_TO_INT (g_hash_table_lookup (window->initial_permissions,
                                                            file));

        if (first)
        {
            if ((permissions & mask) != mask &&
                (permissions & mask) != 0)
            {
                /* Not fully on or off -> inconsistent */
                return FALSE;
            }

            first_permissions = permissions;
            first = FALSE;
        }
        else if ((permissions & mask) != (first_permissions & mask))
        {
            /* Not same permissions as first -> inconsistent */
            return FALSE;
        }
    }
    return TRUE;
}

static void
permission_button_toggled (GtkToggleButton          *button,
                           NautilusPropertiesWindow *window)
{
    gboolean is_folder, is_special;
    guint32 permission_mask;
    gboolean inconsistent;
    gboolean on;

    permission_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                          "permission"));
    is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                    "is-folder"));
    is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                     "is-special"));

    if (gtk_toggle_button_get_active (button)
        && !gtk_toggle_button_get_inconsistent (button))
    {
        /* Go to the initial state unless the initial state was
         *  consistent, or we support recursive apply */
        inconsistent = TRUE;
        on = TRUE;

        if (initial_permission_state_consistent (window, permission_mask, is_folder, is_special))
        {
            inconsistent = FALSE;
            on = TRUE;
        }
    }
    else if (gtk_toggle_button_get_inconsistent (button)
             && !gtk_toggle_button_get_active (button))
    {
        inconsistent = FALSE;
        on = TRUE;
    }
    else
    {
        inconsistent = FALSE;
        on = FALSE;
    }

    g_signal_handlers_block_by_func (G_OBJECT (button),
                                     G_CALLBACK (permission_button_toggled),
                                     window);

    gtk_toggle_button_set_active (button, on);
    gtk_toggle_button_set_inconsistent (button, inconsistent);

    g_signal_handlers_unblock_by_func (G_OBJECT (button),
                                       G_CALLBACK (permission_button_toggled),
                                       window);

    update_permissions (window,
                        on ? permission_mask : 0,
                        permission_mask,
                        is_folder,
                        is_special,
                        inconsistent);
}

static void
permission_button_update (NautilusPropertiesWindow *window,
                          GtkToggleButton          *button)
{
    GList *l;
    gboolean all_set;
    gboolean all_unset;
    gboolean all_cannot_set;
    gboolean is_folder, is_special;
    gboolean no_match;
    gboolean sensitive;
    guint32 button_permission;

    button_permission = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                            "permission"));
    is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                    "is-folder"));
    is_special = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
                                                     "is-special"));

    all_set = TRUE;
    all_unset = TRUE;
    all_cannot_set = TRUE;
    no_match = TRUE;
    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        guint32 file_permissions;

        file = NAUTILUS_FILE (l->data);

        if (!nautilus_file_can_get_permissions (file))
        {
            continue;
        }

        if (!is_special &&
            ((nautilus_file_is_directory (file) && !is_folder) ||
             (!nautilus_file_is_directory (file) && is_folder)))
        {
            continue;
        }

        no_match = FALSE;

        file_permissions = nautilus_file_get_permissions (file);

        if ((file_permissions & button_permission) == button_permission)
        {
            all_unset = FALSE;
        }
        else if ((file_permissions & button_permission) == 0)
        {
            all_set = FALSE;
        }
        else
        {
            all_unset = FALSE;
            all_set = FALSE;
        }

        if (nautilus_file_can_set_permissions (file))
        {
            all_cannot_set = FALSE;
        }
    }

    sensitive = !all_cannot_set;

    g_signal_handlers_block_by_func (G_OBJECT (button),
                                     G_CALLBACK (permission_button_toggled),
                                     window);

    gtk_toggle_button_set_active (button, !all_unset);
    /* if actually inconsistent, or default value for file buttons
     *  if no files are selected. (useful for recursive apply) */
    gtk_toggle_button_set_inconsistent (button,
                                        (!all_unset && !all_set) ||
                                        (!is_folder && no_match));
    gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);

    g_signal_handlers_unblock_by_func (G_OBJECT (button),
                                       G_CALLBACK (permission_button_toggled),
                                       window);
}

static void
set_up_permissions_checkbox (NautilusPropertiesWindow *window,
                             GtkWidget                *check_button,
                             guint32                   permission,
                             gboolean                  is_folder)
{
    /* Load up the check_button with data we'll need when updating its state. */
    g_object_set_data (G_OBJECT (check_button), "permission",
                       GINT_TO_POINTER (permission));
    g_object_set_data (G_OBJECT (check_button), "properties_window",
                       window);
    g_object_set_data (G_OBJECT (check_button), "is-folder",
                       GINT_TO_POINTER (is_folder));

    window->permission_buttons =
        g_list_prepend (window->permission_buttons,
                        check_button);

    g_signal_connect_object (check_button, "toggled",
                             G_CALLBACK (permission_button_toggled),
                             window,
                             0);
}

static GtkWidget *
add_execute_checkbox_with_label (NautilusPropertiesWindow *window,
                                 GtkGrid                  *grid,
                                 GtkWidget                *sibling,
                                 const char               *label,
                                 guint32                   permission_to_check,
                                 GtkLabel                 *label_for,
                                 gboolean                  is_folder)
{
    GtkWidget *check_button;
    gboolean a11y_enabled;

    check_button = gtk_check_button_new_with_mnemonic (label);
    gtk_widget_show (check_button);

    if (sibling)
    {
        gtk_grid_attach_next_to (grid, check_button, sibling,
                                 GTK_POS_RIGHT, 1, 1);
    }
    else
    {
        gtk_container_add (GTK_CONTAINER (grid), check_button);
    }

    set_up_permissions_checkbox (window,
                                 check_button,
                                 permission_to_check,
                                 is_folder);

    a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button));
    if (a11y_enabled && label_for != NULL)
    {
        AtkObject *atk_widget;
        AtkObject *atk_label;

        atk_label = gtk_widget_get_accessible (GTK_WIDGET (label_for));
        atk_widget = gtk_widget_get_accessible (check_button);

        /* Create the label -> widget relation */
        atk_object_add_relationship (atk_label, ATK_RELATION_LABEL_FOR, atk_widget);

        /* Create the widget -> label relation */
        atk_object_add_relationship (atk_widget, ATK_RELATION_LABELLED_BY, atk_label);
    }

    return check_button;
}

enum
{
    UNIX_PERM_SUID = S_ISUID,
    UNIX_PERM_SGID = S_ISGID,
    UNIX_PERM_STICKY = 01000,           /* S_ISVTX not defined on all systems */
    UNIX_PERM_USER_READ = S_IRUSR,
    UNIX_PERM_USER_WRITE = S_IWUSR,
    UNIX_PERM_USER_EXEC = S_IXUSR,
    UNIX_PERM_USER_ALL = S_IRUSR | S_IWUSR | S_IXUSR,
    UNIX_PERM_GROUP_READ = S_IRGRP,
    UNIX_PERM_GROUP_WRITE = S_IWGRP,
    UNIX_PERM_GROUP_EXEC = S_IXGRP,
    UNIX_PERM_GROUP_ALL = S_IRGRP | S_IWGRP | S_IXGRP,
    UNIX_PERM_OTHER_READ = S_IROTH,
    UNIX_PERM_OTHER_WRITE = S_IWOTH,
    UNIX_PERM_OTHER_EXEC = S_IXOTH,
    UNIX_PERM_OTHER_ALL = S_IROTH | S_IWOTH | S_IXOTH
};

typedef enum
{
    PERMISSION_READ = (1 << 0),
    PERMISSION_WRITE = (1 << 1),
    PERMISSION_EXEC = (1 << 2)
} PermissionValue;

typedef enum
{
    PERMISSION_USER,
    PERMISSION_GROUP,
    PERMISSION_OTHER
} PermissionType;

static guint32 vfs_perms[3][3] =
{
    {UNIX_PERM_USER_READ, UNIX_PERM_USER_WRITE, UNIX_PERM_USER_EXEC},
    {UNIX_PERM_GROUP_READ, UNIX_PERM_GROUP_WRITE, UNIX_PERM_GROUP_EXEC},
    {UNIX_PERM_OTHER_READ, UNIX_PERM_OTHER_WRITE, UNIX_PERM_OTHER_EXEC},
};

static guint32
permission_to_vfs (PermissionType  type,
                   PermissionValue perm)
{
    guint32 vfs_perm;
    g_assert (type >= 0 && type < 3);

    vfs_perm = 0;
    if (perm & PERMISSION_READ)
    {
        vfs_perm |= vfs_perms[type][0];
    }
    if (perm & PERMISSION_WRITE)
    {
        vfs_perm |= vfs_perms[type][1];
    }
    if (perm & PERMISSION_EXEC)
    {
        vfs_perm |= vfs_perms[type][2];
    }

    return vfs_perm;
}


static PermissionValue
permission_from_vfs (PermissionType type,
                     guint32        vfs_perm)
{
    PermissionValue perm;
    g_assert (type >= 0 && type < 3);

    perm = 0;
    if (vfs_perm & vfs_perms[type][0])
    {
        perm |= PERMISSION_READ;
    }
    if (vfs_perm & vfs_perms[type][1])
    {
        perm |= PERMISSION_WRITE;
    }
    if (vfs_perm & vfs_perms[type][2])
    {
        perm |= PERMISSION_EXEC;
    }

    return perm;
}

static void
permission_combo_changed (GtkWidget                *combo,
                          NautilusPropertiesWindow *window)
{
    GtkTreeIter iter;
    GtkTreeModel *model;
    gboolean is_folder, use_original;
    PermissionType type;
    int new_perm, mask;
    guint32 vfs_new_perm, vfs_mask;

    is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
    type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));

    if (is_folder)
    {
        mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
    }
    else
    {
        mask = PERMISSION_READ | PERMISSION_WRITE;
    }

    vfs_mask = permission_to_vfs (type, mask);

    model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));

    if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
    {
        return;
    }
    gtk_tree_model_get (model, &iter, COLUMN_VALUE, &new_perm,
                        COLUMN_USE_ORIGINAL, &use_original, -1);
    vfs_new_perm = permission_to_vfs (type, new_perm);

    update_permissions (window, vfs_new_perm, vfs_mask,
                        is_folder, FALSE, use_original);
}

static void
permission_combo_add_multiple_choice (GtkComboBox *combo,
                                      GtkTreeIter *iter)
{
    GtkTreeModel *model;
    GtkListStore *store;
    gboolean found;

    model = gtk_combo_box_get_model (combo);
    store = GTK_LIST_STORE (model);

    found = FALSE;
    gtk_tree_model_get_iter_first (model, iter);
    do
    {
        gboolean multi;
        gtk_tree_model_get (model, iter, COLUMN_USE_ORIGINAL, &multi, -1);

        if (multi)
        {
            found = TRUE;
            break;
        }
    }
    while (gtk_tree_model_iter_next (model, iter));

    if (!found)
    {
        gtk_list_store_append (store, iter);
        gtk_list_store_set (store, iter,
                            COLUMN_NAME, "---",
                            COLUMN_VALUE, 0,
                            COLUMN_USE_ORIGINAL, TRUE, -1);
    }
}

static void
permission_combo_update (NautilusPropertiesWindow *window,
                         GtkComboBox              *combo)
{
    PermissionType type;
    PermissionValue perm, all_dir_perm, all_file_perm, all_perm;
    gboolean is_folder, no_files, no_dirs, all_file_same, all_dir_same, all_same;
    gboolean all_dir_cannot_set, all_file_cannot_set, sensitive;
    GtkTreeIter iter;
    int mask;
    GtkTreeModel *model;
    GtkListStore *store;
    GList *l;
    gboolean is_multi;

    model = gtk_combo_box_get_model (combo);

    is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));
    type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));

    is_multi = FALSE;
    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
    {
        gtk_tree_model_get (model, &iter, COLUMN_USE_ORIGINAL, &is_multi, -1);
    }

    no_files = TRUE;
    no_dirs = TRUE;
    all_dir_same = TRUE;
    all_file_same = TRUE;
    all_dir_perm = 0;
    all_file_perm = 0;
    all_dir_cannot_set = TRUE;
    all_file_cannot_set = TRUE;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        guint32 file_permissions;

        file = NAUTILUS_FILE (l->data);

        if (!nautilus_file_can_get_permissions (file))
        {
            continue;
        }

        if (nautilus_file_is_directory (file))
        {
            mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
        }
        else
        {
            mask = PERMISSION_READ | PERMISSION_WRITE;
        }

        file_permissions = nautilus_file_get_permissions (file);

        perm = permission_from_vfs (type, file_permissions) & mask;

        if (nautilus_file_is_directory (file))
        {
            if (no_dirs)
            {
                all_dir_perm = perm;
                no_dirs = FALSE;
            }
            else if (perm != all_dir_perm)
            {
                all_dir_same = FALSE;
            }

            if (nautilus_file_can_set_permissions (file))
            {
                all_dir_cannot_set = FALSE;
            }
        }
        else
        {
            if (no_files)
            {
                all_file_perm = perm;
                no_files = FALSE;
            }
            else if (perm != all_file_perm)
            {
                all_file_same = FALSE;
            }

            if (nautilus_file_can_set_permissions (file))
            {
                all_file_cannot_set = FALSE;
            }
        }
    }

    if (is_folder)
    {
        all_same = all_dir_same;
        all_perm = all_dir_perm;
    }
    else
    {
        all_same = all_file_same && !no_files;
        all_perm = all_file_perm;
    }

    store = GTK_LIST_STORE (model);
    if (all_same)
    {
        gboolean found;

        found = FALSE;
        gtk_tree_model_get_iter_first (model, &iter);
        do
        {
            int current_perm;
            gtk_tree_model_get (model, &iter, 1, &current_perm, -1);

            if (current_perm == all_perm)
            {
                found = TRUE;
                break;
            }
        }
        while (gtk_tree_model_iter_next (model, &iter));

        if (!found)
        {
            GString *str;
            str = g_string_new ("");

            if (!(all_perm & PERMISSION_READ))
            {
                /* translators: this gets concatenated to "no read",
                 * "no access", etc. (see following strings)
                 */
                g_string_append (str, _("no "));
            }
            if (is_folder)
            {
                g_string_append (str, _("list"));
            }
            else
            {
                g_string_append (str, _("read"));
            }

            g_string_append (str, ", ");

            if (!(all_perm & PERMISSION_WRITE))
            {
                g_string_append (str, _("no "));
            }
            if (is_folder)
            {
                g_string_append (str, _("create/delete"));
            }
            else
            {
                g_string_append (str, _("write"));
            }

            if (is_folder)
            {
                g_string_append (str, ", ");

                if (!(all_perm & PERMISSION_EXEC))
                {
                    g_string_append (str, _("no "));
                }
                g_string_append (str, _("access"));
            }

            gtk_list_store_append (store, &iter);
            gtk_list_store_set (store, &iter,
                                0, str->str,
                                1, all_perm, -1);

            g_string_free (str, TRUE);
        }
    }
    else
    {
        permission_combo_add_multiple_choice (combo, &iter);
    }

    g_signal_handlers_block_by_func (G_OBJECT (combo),
                                     G_CALLBACK (permission_combo_changed),
                                     window);

    gtk_combo_box_set_active_iter (combo, &iter);

    /* Also enable if no files found (for recursive
     *  file changes when only selecting folders) */
    if (is_folder)
    {
        sensitive = !all_dir_cannot_set;
    }
    else
    {
        sensitive = !all_file_cannot_set;
    }
    gtk_widget_set_sensitive (GTK_WIDGET (combo), sensitive);

    g_signal_handlers_unblock_by_func (G_OBJECT (combo),
                                       G_CALLBACK (permission_combo_changed),
                                       window);
}

static GtkWidget *
create_permissions_combo_box (PermissionType type,
                              gboolean       is_folder)
{
    GtkWidget *combo;
    GtkListStore *store;
    GtkCellRenderer *cell;
    GtkTreeIter iter;

    store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_STRING);
    combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
    gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo), COLUMN_ID);

    g_object_set_data (G_OBJECT (combo), "is-folder", GINT_TO_POINTER (is_folder));
    g_object_set_data (G_OBJECT (combo), "permission-type", GINT_TO_POINTER (type));

    if (is_folder)
    {
        if (type != PERMISSION_USER)
        {
            gtk_list_store_append (store, &iter);
            gtk_list_store_set (store, &iter,
                                /* Translators: this is referred to the permissions
                                 * the user has in a directory.
                                 */
                                COLUMN_NAME, _("None"),
                                COLUMN_VALUE, 0,
                                COLUMN_ID, "none",
                                -1);
        }
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            COLUMN_NAME, _("List files only"),
                            COLUMN_VALUE, PERMISSION_READ,
                            COLUMN_ID, "r",
                            -1);
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            COLUMN_NAME, _("Access files"),
                            COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC,
                            COLUMN_ID, "rx",
                            -1);
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            COLUMN_NAME, _("Create and delete files"),
                            COLUMN_VALUE, PERMISSION_READ | PERMISSION_EXEC | PERMISSION_WRITE,
                            COLUMN_ID, "rwx",
                            -1);
    }
    else
    {
        if (type != PERMISSION_USER)
        {
            gtk_list_store_append (store, &iter);
            gtk_list_store_set (store, &iter,
                                COLUMN_NAME, _("None"),
                                COLUMN_VALUE, 0,
                                COLUMN_ID, "none",
                                -1);
        }
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            COLUMN_NAME, _("Read-only"),
                            COLUMN_VALUE, PERMISSION_READ,
                            COLUMN_ID, "r",
                            -1);
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter,
                            COLUMN_NAME, _("Read and write"),
                            COLUMN_VALUE, PERMISSION_READ | PERMISSION_WRITE,
                            COLUMN_ID, "rw",
                            -1);
    }
    g_object_unref (store);

    cell = gtk_cell_renderer_text_new ();
    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
                                    "text", COLUMN_NAME,
                                    NULL);

    return combo;
}

static void
add_permissions_combo_box (NautilusPropertiesWindow *window,
                           GtkGrid                  *grid,
                           PermissionType            type,
                           gboolean                  is_folder,
                           gboolean                  short_label)
{
    GtkWidget *combo;
    GtkLabel *label;

    if (short_label)
    {
        label = attach_title_field (grid, _("Access:"));
    }
    else if (is_folder)
    {
        label = attach_title_field (grid, _("Folder access:"));
    }
    else
    {
        label = attach_title_field (grid, _("File access:"));
    }

    combo = create_permissions_combo_box (type, is_folder);

    window->permission_combos = g_list_prepend (window->permission_combos,
                                                         combo);

    g_signal_connect (combo, "changed", G_CALLBACK (permission_combo_changed), window);

    gtk_label_set_mnemonic_widget (label, combo);
    gtk_widget_show (combo);

    gtk_grid_attach_next_to (grid, combo, GTK_WIDGET (label),
                             GTK_POS_RIGHT, 1, 1);
}

static gboolean
all_can_get_permissions (GList *file_list)
{
    GList *l;
    for (l = file_list; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        if (!nautilus_file_can_get_permissions (file))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static gboolean
all_can_set_permissions (GList *file_list)
{
    GList *l;
    for (l = file_list; l != NULL; l = l->next)
    {
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        if (!nautilus_file_can_set_permissions (file))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static GHashTable *
get_initial_permissions (GList *file_list)
{
    GHashTable *ret;
    GList *l;

    ret = g_hash_table_new (g_direct_hash,
                            g_direct_equal);

    for (l = file_list; l != NULL; l = l->next)
    {
        guint32 permissions;
        NautilusFile *file;

        file = NAUTILUS_FILE (l->data);

        permissions = nautilus_file_get_permissions (file);
        g_hash_table_insert (ret, file,
                             GINT_TO_POINTER (permissions));
    }

    return ret;
}

static void
create_simple_permissions (NautilusPropertiesWindow *window,
                           GtkGrid                  *page_grid)
{
    gboolean has_directory;
    gboolean has_file;
    GtkLabel *group_label;
    GtkLabel *owner_label;
    GtkWidget *value;
    GtkComboBox *group_combo_box;
    GtkComboBox *owner_combo_box;

    has_directory = files_has_directory (window);
    has_file = files_has_file (window);

    if (!is_multi_file_window (window) && nautilus_file_can_set_owner (get_target_file (window)))
    {
        owner_label = attach_title_field (page_grid, _("_Owner:"));
        /* Combo box in this case. */
        owner_combo_box = attach_owner_combo_box (page_grid,
                                                  GTK_WIDGET (owner_label),
                                                  get_target_file (window));
        gtk_label_set_mnemonic_widget (owner_label,
                                       GTK_WIDGET (owner_combo_box));
    }
    else
    {
        owner_label = attach_title_field (page_grid, _("Owner:"));
        /* Static text in this case. */
        value = attach_value_field (window,
                                    page_grid, GTK_WIDGET (owner_label),
                                    "owner",
                                    INCONSISTENT_STATE_STRING,
                                    FALSE);
        gtk_label_set_mnemonic_widget (owner_label, value);
    }
    if (has_directory && has_file)
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_USER, TRUE, FALSE);
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_USER, FALSE, FALSE);
    }
    else
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_USER, has_directory, TRUE);
    }

    append_blank_slim_row (page_grid);

    if (!is_multi_file_window (window) && nautilus_file_can_set_group (get_target_file (window)))
    {
        group_label = attach_title_field (page_grid, _("_Group:"));

        /* Combo box in this case. */
        group_combo_box = attach_group_combo_box (page_grid, GTK_WIDGET (group_label),
                                                  get_target_file (window));
        gtk_label_set_mnemonic_widget (group_label,
                                       GTK_WIDGET (group_combo_box));
    }
    else
    {
        group_label = attach_title_field (page_grid, _("Group:"));

        /* Static text in this case. */
        value = attach_value_field (window, page_grid,
                                    GTK_WIDGET (group_label),
                                    "group",
                                    INCONSISTENT_STATE_STRING,
                                    FALSE);
        gtk_label_set_mnemonic_widget (group_label, value);
    }
    if (has_directory && has_file)
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_GROUP, TRUE, FALSE);
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_GROUP, FALSE, FALSE);
    }
    else
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_GROUP, has_directory, TRUE);
    }

    append_blank_slim_row (page_grid);
    attach_title_field (page_grid, _("Others"));
    if (has_directory && has_file)
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_OTHER, TRUE, FALSE);
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_OTHER, FALSE, FALSE);
    }
    else
    {
        add_permissions_combo_box (window, page_grid,
                                   PERMISSION_OTHER, has_directory, TRUE);
    }

    if (!has_directory)
    {
        GtkLabel *execute_label;
        append_blank_slim_row (page_grid);

        execute_label = attach_title_field (page_grid, _("Execute:"));
        add_execute_checkbox_with_label (window, page_grid,
                                         GTK_WIDGET (execute_label),
                                         _("Allow _executing file as program"),
                                         UNIX_PERM_USER_EXEC | UNIX_PERM_GROUP_EXEC | UNIX_PERM_OTHER_EXEC,
                                         execute_label, FALSE);
    }
}

static void
set_recursive_permissions_done (gboolean success,
                                gpointer callback_data)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (callback_data);
    end_long_operation (window);

    g_object_unref (window);
}

static void
on_change_permissions_response (GtkDialog                *dialog,
                                int                       response,
                                NautilusPropertiesWindow *window)
{
    guint32 file_permission, file_permission_mask;
    guint32 dir_permission, dir_permission_mask;
    guint32 vfs_mask, vfs_new_perm;
    GtkWidget *combo;
    gboolean is_folder, use_original;
    GList *l;
    GtkTreeModel *model;
    GtkTreeIter iter;
    PermissionType type;
    int new_perm, mask;

    if (response != GTK_RESPONSE_OK)
    {
        gtk_widget_destroy (GTK_WIDGET (dialog));
        return;
    }

    file_permission = 0;
    file_permission_mask = 0;
    dir_permission = 0;
    dir_permission_mask = 0;

    /* Simple mode, minus exec checkbox */
    for (l = window->change_permission_combos; l != NULL; l = l->next)
    {
        combo = l->data;

        if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter))
        {
            continue;
        }

        type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "permission-type"));
        is_folder = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "is-folder"));

        model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
        gtk_tree_model_get (model, &iter,
                            COLUMN_VALUE, &new_perm,
                            COLUMN_USE_ORIGINAL, &use_original, -1);
        if (use_original)
        {
            continue;
        }
        vfs_new_perm = permission_to_vfs (type, new_perm);

        if (is_folder)
        {
            mask = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXEC;
        }
        else
        {
            mask = PERMISSION_READ | PERMISSION_WRITE;
        }
        vfs_mask = permission_to_vfs (type, mask);

        if (is_folder)
        {
            dir_permission_mask |= vfs_mask;
            dir_permission |= vfs_new_perm;
        }
        else
        {
            file_permission_mask |= vfs_mask;
            file_permission |= vfs_new_perm;
        }
    }

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        char *uri;

        file = NAUTILUS_FILE (l->data);

        if (nautilus_file_is_directory (file) &&
            nautilus_file_can_set_permissions (file))
        {
            uri = nautilus_file_get_uri (file);
            start_long_operation (window);
            g_object_ref (window);
            nautilus_file_set_permissions_recursive (uri,
                                                     file_permission,
                                                     file_permission_mask,
                                                     dir_permission,
                                                     dir_permission_mask,
                                                     set_recursive_permissions_done,
                                                     window);
            g_free (uri);
        }
    }
    gtk_widget_destroy (GTK_WIDGET (dialog));
}

static void
set_active_from_umask (GtkWidget      *combo,
                       PermissionType  type,
                       gboolean        is_folder)
{
    mode_t initial;
    mode_t mask;
    mode_t p;
    const char *id;

    if (is_folder)
    {
        initial = (S_IRWXU | S_IRWXG | S_IRWXO);
    }
    else
    {
        initial = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    }

    umask (mask = umask (0));

    p = ~mask & initial;

    if (type == PERMISSION_USER)
    {
        p &= ~(S_IRWXG | S_IRWXO);
        if ((p & S_IRWXU) == S_IRWXU)
        {
            id = "rwx";
        }
        else if ((p & (S_IRUSR | S_IWUSR)) == (S_IRUSR | S_IWUSR))
        {
            id = "rw";
        }
        else if ((p & (S_IRUSR | S_IXUSR)) == (S_IRUSR | S_IXUSR))
        {
            id = "rx";
        }
        else if ((p & S_IRUSR) == S_IRUSR)
        {
            id = "r";
        }
        else
        {
            id = "none";
        }
    }
    else if (type == PERMISSION_GROUP)
    {
        p &= ~(S_IRWXU | S_IRWXO);
        if ((p & S_IRWXG) == S_IRWXG)
        {
            id = "rwx";
        }
        else if ((p & (S_IRGRP | S_IWGRP)) == (S_IRGRP | S_IWGRP))
        {
            id = "rw";
        }
        else if ((p & (S_IRGRP | S_IXGRP)) == (S_IRGRP | S_IXGRP))
        {
            id = "rx";
        }
        else if ((p & S_IRGRP) == S_IRGRP)
        {
            id = "r";
        }
        else
        {
            id = "none";
        }
    }
    else
    {
        p &= ~(S_IRWXU | S_IRWXG);
        if ((p & S_IRWXO) == S_IRWXO)
        {
            id = "rwx";
        }
        else if ((p & (S_IROTH | S_IWOTH)) == (S_IROTH | S_IWOTH))
        {
            id = "rw";
        }
        else if ((p & (S_IROTH | S_IXOTH)) == (S_IROTH | S_IXOTH))
        {
            id = "rx";
        }
        else if ((p & S_IROTH) == S_IROTH)
        {
            id = "r";
        }
        else
        {
            id = "none";
        }
    }

    gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), id);
}

static void
on_change_permissions_clicked (GtkWidget                *button,
                               NautilusPropertiesWindow *window)
{
    GtkWidget *dialog;
    GtkWidget *label;
    GtkWidget *combo;
    GtkGrid *grid;

    dialog = gtk_dialog_new_with_buttons (_("Change Permissions for Enclosed Files"),
                                          GTK_WINDOW (window),
                                          GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
                                          _("_Cancel"), GTK_RESPONSE_CANCEL,
                                          _("Change"), GTK_RESPONSE_OK,
                                          NULL);

    grid = GTK_GRID (create_grid_with_standard_properties ());
    gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                        GTK_WIDGET (grid),
                        TRUE, TRUE, 0);

    label = gtk_label_new (_("Files"));
    gtk_grid_attach (grid, label, 1, 0, 1, 1);
    label = gtk_label_new (_("Folders"));
    gtk_grid_attach (grid, label, 2, 0, 1, 1);

    label = gtk_label_new (_("Owner:"));
    gtk_label_set_xalign (GTK_LABEL (label), 0);
    gtk_grid_attach (grid, label, 0, 1, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_USER, FALSE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_USER, FALSE);
    gtk_grid_attach (grid, combo, 1, 1, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_USER, TRUE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_USER, TRUE);
    gtk_grid_attach (grid, combo, 2, 1, 1, 1);

    label = gtk_label_new (_("Group:"));
    gtk_label_set_xalign (GTK_LABEL (label), 0);
    gtk_grid_attach (grid, label, 0, 2, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_GROUP, FALSE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_GROUP, FALSE);
    gtk_grid_attach (grid, combo, 1, 2, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_GROUP, TRUE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_GROUP, TRUE);
    gtk_grid_attach (grid, combo, 2, 2, 1, 1);

    label = gtk_label_new (_("Others:"));
    gtk_label_set_xalign (GTK_LABEL (label), 0);
    gtk_grid_attach (grid, label, 0, 3, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_OTHER, FALSE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_OTHER, FALSE);
    gtk_grid_attach (grid, combo, 1, 3, 1, 1);
    combo = create_permissions_combo_box (PERMISSION_OTHER, TRUE);
    window->change_permission_combos = g_list_prepend (window->change_permission_combos,
                                                                combo);
    set_active_from_umask (combo, PERMISSION_OTHER, TRUE);
    gtk_grid_attach (grid, combo, 2, 3, 1, 1);

    g_signal_connect (dialog, "response", G_CALLBACK (on_change_permissions_response), window);
    gtk_widget_show_all (dialog);
}

static void
create_permissions_page (NautilusPropertiesWindow *window)
{
    GtkWidget *vbox, *button, *hbox;
    GtkGrid *page_grid;
    char *file_name, *prompt_text;
    GList *file_list;

    vbox = create_page_with_box (window->notebook,
                                 GTK_ORIENTATION_VERTICAL,
                                 _("Permissions"),
                                 "help:gnome-help/nautilus-file-properties-permissions");

    file_list = window->original_files;

    window->initial_permissions = NULL;

    if (all_can_get_permissions (file_list) && all_can_get_permissions (window->target_files))
    {
        window->initial_permissions = get_initial_permissions (window->target_files);
        window->has_recursive_apply = files_has_changable_permissions_directory (window);

        if (!all_can_set_permissions (file_list))
        {
            add_prompt_and_separator (
                vbox,
                _("You are not the owner, so you cannot change these permissions."));
        }

        page_grid = GTK_GRID (create_grid_with_standard_properties ());

        gtk_widget_show (GTK_WIDGET (page_grid));
        gtk_box_pack_start (GTK_BOX (vbox),
                            GTK_WIDGET (page_grid),
                            TRUE, TRUE, 0);

        create_simple_permissions (window, page_grid);

#ifdef HAVE_SELINUX
        append_blank_slim_row (page_grid);
        append_title_value_pair
            (window, page_grid, _("Security context:"),
            "selinux_context", INCONSISTENT_STATE_STRING,
            FALSE);
#endif

        append_blank_row (page_grid);

        if (window->has_recursive_apply)
        {
            hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
            gtk_widget_show (hbox);

            gtk_container_add_with_properties (GTK_CONTAINER (page_grid), hbox,
                                               "width", 2,
                                               NULL);

            button = gtk_button_new_with_mnemonic (_("Change Permissions for Enclosed Files…"));
            gtk_widget_show (button);
            gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
            g_signal_connect (button, "clicked",
                              G_CALLBACK (on_change_permissions_clicked),
                              window);
        }
    }
    else
    {
        if (!is_multi_file_window (window))
        {
            file_name = nautilus_file_get_display_name (get_target_file (window));
            prompt_text = g_strdup_printf (_("The permissions of “%s” could not be determined."), file_name);
            g_free (file_name);
        }
        else
        {
            prompt_text = g_strdup (_("The permissions of the selected file could not be determined."));
        }

        add_prompt (vbox, prompt_text, TRUE);
        g_free (prompt_text);
    }
}

static void
append_extension_pages (NautilusPropertiesWindow *window)
{
    GList *providers;
    GList *p;

    providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER);

    for (p = providers; p != NULL; p = p->next)
    {
        NautilusPropertyPageProvider *provider;
        GList *pages;
        GList *l;

        provider = NAUTILUS_PROPERTY_PAGE_PROVIDER (p->data);

        pages = nautilus_property_page_provider_get_pages
                    (provider, window->original_files);

        for (l = pages; l != NULL; l = l->next)
        {
            NautilusPropertyPage *page;
            GtkWidget *page_widget;
            GtkWidget *label;

            page = NAUTILUS_PROPERTY_PAGE (l->data);

            g_object_get (G_OBJECT (page),
                          "page", &page_widget, "label", &label,
                          NULL);

            gtk_notebook_append_page (window->notebook,
                                      page_widget, label);
            gtk_container_child_set (GTK_CONTAINER (window->notebook),
                                     page_widget,
                                     "tab-expand", TRUE,
                                     NULL);

            g_object_set_data (G_OBJECT (page_widget),
                               "is-extension-page",
                               page);

            g_object_unref (page_widget);
            g_object_unref (label);

            g_object_unref (page);
        }

        g_list_free (pages);
    }

    nautilus_module_extension_list_free (providers);
}

static gboolean
should_show_permissions (NautilusPropertiesWindow *window)
{
    GList *l;

    /* Don't show permissions for Trash and Computer since they're not
     * really file system objects.
     */
    for (l = window->original_files; l != NULL; l = l->next)
    {
        if (nautilus_file_is_in_trash (NAUTILUS_FILE (l->data)) ||
            nautilus_file_is_in_recent (NAUTILUS_FILE (l->data)))
        {
            return FALSE;
        }
    }

    return TRUE;
}

static char *
get_pending_key (GList *file_list)
{
    GList *l;
    GList *uris;
    GString *key;
    char *ret;

    uris = NULL;
    for (l = file_list; l != NULL; l = l->next)
    {
        uris = g_list_prepend (uris, nautilus_file_get_uri (NAUTILUS_FILE (l->data)));
    }
    uris = g_list_sort (uris, (GCompareFunc) strcmp);

    key = g_string_new ("");
    for (l = uris; l != NULL; l = l->next)
    {
        g_string_append (key, l->data);
        g_string_append (key, ";");
    }

    g_list_free_full (uris, g_free);

    ret = key->str;
    g_string_free (key, FALSE);

    return ret;
}

static StartupData *
startup_data_new (GList                            *original_files,
                  GList                            *target_files,
                  const char                       *pending_key,
                  GtkWidget                        *parent_widget,
                  GtkWindow                        *parent_window,
                  const char                       *startup_id,
                  NautilusPropertiesWindowCallback  callback,
                  gpointer                          callback_data,
                  NautilusPropertiesWindow         *window)
{
    StartupData *data;
    GList *l;

    data = g_new0 (StartupData, 1);
    data->original_files = nautilus_file_list_copy (original_files);
    data->target_files = nautilus_file_list_copy (target_files);
    data->parent_widget = parent_widget;
    data->parent_window = parent_window;
    data->startup_id = g_strdup (startup_id);
    data->pending_key = g_strdup (pending_key);
    data->pending_files = g_hash_table_new (g_direct_hash,
                                            g_direct_equal);
    data->callback = callback;
    data->callback_data = callback_data;
    data->window = window;

    for (l = data->target_files; l != NULL; l = l->next)
    {
        g_hash_table_insert (data->pending_files, l->data, l->data);
    }

    return data;
}

static void
startup_data_free (StartupData *data)
{
    nautilus_file_list_free (data->original_files);
    nautilus_file_list_free (data->target_files);
    g_hash_table_destroy (data->pending_files);
    g_free (data->pending_key);
    g_free (data->startup_id);
    g_free (data);
}

static void
file_changed_callback (NautilusFile *file,
                       gpointer      user_data)
{
    NautilusPropertiesWindow *window = NAUTILUS_PROPERTIES_WINDOW (user_data);

    if (!g_list_find (window->changed_files, file))
    {
        nautilus_file_ref (file);
        window->changed_files = g_list_prepend (window->changed_files, file);
        schedule_files_update (window);
    }
}

static gboolean
should_show_open_with (NautilusPropertiesWindow *window)
{
    NautilusFile *file;
    char *mime_type;
    char *extension;
    gboolean hide;
    g_autoptr (GAppInfo) app_info = NULL;

    /* Don't show open with tab for desktop special icons (trash, etc)
     * or desktop files. We don't get the open-with menu for these anyway.
     *
     * Also don't show it for folders. Changing the default app for folders
     * leads to all sort of hard to understand errors.
     */

    if (is_multi_file_window (window))
    {
        GList *l;

        if (!file_list_attributes_identical (window->target_files,
                                             "mime_type"))
        {
            return FALSE;
        }

        for (l = window->target_files; l; l = l->next)
        {
            file = NAUTILUS_FILE (l->data);
            app_info = nautilus_mime_get_default_application_for_file (file);
            if (nautilus_file_is_directory (file) || !app_info || file == NULL)
            {
                return FALSE;
            }
        }

        /* since we just confirmed all the mime types are the
         *  same we only need to test one file */
        file = window->target_files->data;
    }
    else
    {
        file = get_target_file (window);

        app_info = nautilus_mime_get_default_application_for_file (file);
        if (nautilus_file_is_directory (file) || !app_info || file == NULL)
        {
            return FALSE;
        }
    }

    mime_type = nautilus_file_get_mime_type (file);
    extension = nautilus_file_get_extension (file);
    hide = (g_content_type_is_unknown (mime_type) && extension == NULL);
    g_free (mime_type);
    g_free (extension);

    return !hide;
}

static void
create_open_with_page (NautilusPropertiesWindow *window)
{
    GtkWidget *vbox;
    char *mime_type;
    GList *files = NULL;
    NautilusFile *target_file;

    target_file = get_target_file (window);
    mime_type = nautilus_file_get_mime_type (target_file);

    if (!is_multi_file_window (window))
    {
        files = g_list_prepend (NULL, target_file);
    }
    else
    {
        files = g_list_copy (window->original_files);
        if (files == NULL)
        {
            return;
        }
    }

    vbox = nautilus_mime_application_chooser_new (files, mime_type);

    gtk_widget_show (vbox);
    g_free (mime_type);
    g_list_free (files);

    g_object_set_data_full (G_OBJECT (vbox), "help-uri", g_strdup ("help:gnome-help/files-open"), g_free);
    gtk_notebook_append_page (window->notebook,
                              vbox, gtk_label_new (_("Open With")));
    gtk_container_child_set (GTK_CONTAINER (window->notebook),
                             vbox,
                             "tab-expand", TRUE,
                             NULL);
}


static NautilusPropertiesWindow *
create_properties_window (StartupData *startup_data)
{
    NautilusPropertiesWindow *window;
    GList *l;

    window = NAUTILUS_PROPERTIES_WINDOW (gtk_widget_new (NAUTILUS_TYPE_PROPERTIES_WINDOW,
                                                         "use-header-bar", TRUE,
                                                         "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
                                                         "modal", TRUE,
                                                         NULL));

    window->original_files = nautilus_file_list_copy (startup_data->original_files);

    window->target_files = nautilus_file_list_copy (startup_data->target_files);

    if (startup_data->parent_widget)
    {
        gtk_window_set_screen (GTK_WINDOW (window),
                               gtk_widget_get_screen (startup_data->parent_widget));
    }

    if (startup_data->parent_window)
    {
        gtk_window_set_transient_for (GTK_WINDOW (window), startup_data->parent_window);
    }

    if (startup_data->startup_id)
    {
        gtk_window_set_startup_id (GTK_WINDOW (window), startup_data->startup_id);
    }

    /* Set initial window title */
    update_properties_window_title (window);

    /* Start monitoring the file attributes we display. Note that some
     * of the attributes are for the original file, and some for the
     * target files.
     */

    for (l = window->original_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        NautilusFileAttributes attributes;

        file = NAUTILUS_FILE (l->data);

        attributes =
            NAUTILUS_FILE_ATTRIBUTES_FOR_ICON |
            NAUTILUS_FILE_ATTRIBUTE_INFO;

        nautilus_file_monitor_add (file,
                                   &window->original_files,
                                   attributes);
    }

    for (l = window->target_files; l != NULL; l = l->next)
    {
        NautilusFile *file;
        NautilusFileAttributes attributes;

        file = NAUTILUS_FILE (l->data);

        attributes = 0;
        if (nautilus_file_is_directory (file))
        {
            attributes |= NAUTILUS_FILE_ATTRIBUTE_DEEP_COUNTS;
        }

        attributes |= NAUTILUS_FILE_ATTRIBUTE_INFO;
        nautilus_file_monitor_add (file, &window->target_files, attributes);
    }

    for (l = window->target_files; l != NULL; l = l->next)
    {
        g_signal_connect_object (NAUTILUS_FILE (l->data),
                                 "changed",
                                 G_CALLBACK (file_changed_callback),
                                 G_OBJECT (window),
                                 0);
    }

    for (l = window->original_files; l != NULL; l = l->next)
    {
        g_signal_connect_object (NAUTILUS_FILE (l->data),
                                 "changed",
                                 G_CALLBACK (file_changed_callback),
                                 G_OBJECT (window),
                                 0);
    }

    /* Create the notebook tabs. */
    window->notebook = GTK_NOTEBOOK (gtk_notebook_new ());
    gtk_notebook_set_show_border (window->notebook, FALSE);
    gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (window))), 0);
    gtk_widget_show (GTK_WIDGET (window->notebook));
    gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (window))),
                        GTK_WIDGET (window->notebook),
                        TRUE, TRUE, 0);

    /* Create the pages. */
    create_basic_page (window);

    if (should_show_permissions (window))
    {
        create_permissions_page (window);
    }

    if (should_show_open_with (window))
    {
        create_open_with_page (window);
    }

    /* append pages from available views */
    append_extension_pages (window);

    /* Update from initial state */
    properties_window_update (window, NULL);

    return window;
}

static GList *
get_target_file_list (GList *original_files)
{
    GList *ret;
    GList *l;

    ret = NULL;

    for (l = original_files; l != NULL; l = l->next)
    {
        NautilusFile *target;

        target = get_target_file_for_original_file (NAUTILUS_FILE (l->data));

        ret = g_list_prepend (ret, target);
    }

    ret = g_list_reverse (ret);

    return ret;
}

static void
add_window (NautilusPropertiesWindow *window)
{
    if (!is_multi_file_window (window))
    {
        g_hash_table_insert (windows,
                             get_original_file (window),
                             window);
        g_object_set_data (G_OBJECT (window), "window_key",
                           get_original_file (window));
    }
}

static void
remove_window (NautilusPropertiesWindow *window)
{
    gpointer key;

    key = g_object_get_data (G_OBJECT (window), "window_key");
    if (key)
    {
        g_hash_table_remove (windows, key);
    }
}

static NautilusPropertiesWindow *
get_existing_window (GList *file_list)
{
    if (!file_list->next)
    {
        return g_hash_table_lookup (windows, file_list->data);
    }

    return NULL;
}

static void
properties_window_finish (StartupData *data)
{
    gboolean cancel_timed_wait;

    if (data->parent_widget != NULL)
    {
        g_signal_handlers_disconnect_by_data (data->parent_widget,
                                              data);
    }
    if (data->window != NULL)
    {
        g_signal_handlers_disconnect_by_data (data->window,
                                              data);
    }

    cancel_timed_wait = (data->window == NULL && !data->cancelled);
    remove_pending (data, TRUE, cancel_timed_wait);

    startup_data_free (data);
}

static void
cancel_create_properties_window_callback (gpointer callback_data)
{
    StartupData *data;

    data = callback_data;
    data->cancelled = TRUE;

    properties_window_finish (data);
}

static void
parent_widget_destroyed_callback (GtkWidget *widget,
                                  gpointer   callback_data)
{
    g_assert (widget == ((StartupData *) callback_data)->parent_widget);

    properties_window_finish ((StartupData *) callback_data);
}

static void
cancel_call_when_ready_callback (gpointer key,
                                 gpointer value,
                                 gpointer user_data)
{
    nautilus_file_cancel_call_when_ready
        (NAUTILUS_FILE (key),
        is_directory_ready_callback,
        user_data);
}

static void
remove_pending (StartupData *startup_data,
                gboolean     cancel_call_when_ready,
                gboolean     cancel_timed_wait)
{
    if (cancel_call_when_ready)
    {
        g_hash_table_foreach (startup_data->pending_files,
                              cancel_call_when_ready_callback,
                              startup_data);
    }
    if (cancel_timed_wait)
    {
        eel_timed_wait_stop
            (cancel_create_properties_window_callback, startup_data);
    }
    if (startup_data->pending_key != NULL)
    {
        g_hash_table_remove (pending_lists, startup_data->pending_key);
    }
}

static gboolean
widget_on_destroy (GtkWidget *widget,
                   gpointer   user_data)
{
    StartupData *data = (StartupData *) user_data;


    if (data->callback != NULL)
    {
        data->callback (data->callback_data);
    }

    properties_window_finish (data);

    return GDK_EVENT_PROPAGATE;
}

static void
is_directory_ready_callback (NautilusFile *file,
                             gpointer      data)
{
    StartupData *startup_data;

    startup_data = data;

    g_hash_table_remove (startup_data->pending_files, file);

    if (g_hash_table_size (startup_data->pending_files) == 0)
    {
        NautilusPropertiesWindow *new_window;

        new_window = create_properties_window (startup_data);

        add_window (new_window);
        startup_data->window = new_window;

        remove_pending (startup_data, FALSE, TRUE);

        gtk_window_present (GTK_WINDOW (new_window));
        g_signal_connect(GTK_WIDGET (new_window), "destroy",
                         G_CALLBACK (widget_on_destroy), startup_data);
    }
}

void
nautilus_properties_window_present (GList                            *original_files,
                                    GtkWidget                        *parent_widget,
                                    const gchar                      *startup_id,
                                    NautilusPropertiesWindowCallback  callback,
                                    gpointer                          callback_data)
{
    GList *l, *next;
    GtkWindow *parent_window;
    StartupData *startup_data;
    GList *target_files;
    NautilusPropertiesWindow *existing_window;
    char *pending_key;

    g_return_if_fail (original_files != NULL);
    g_return_if_fail (parent_widget == NULL || GTK_IS_WIDGET (parent_widget));


    /* Create the hash tables first time through. */
    if (windows == NULL)
    {
        windows = g_hash_table_new (NULL, NULL);
    }

    if (pending_lists == NULL)
    {
        pending_lists = g_hash_table_new (g_str_hash, g_str_equal);
    }

    /* Look to see if there's already a window for this file. */
    existing_window = get_existing_window (original_files);
    if (existing_window != NULL)
    {
        if (parent_widget)
        {
            gtk_window_set_screen (GTK_WINDOW (existing_window),
                                   gtk_widget_get_screen (parent_widget));
        }
        else if (startup_id)
        {
            gtk_window_set_startup_id (GTK_WINDOW (existing_window), startup_id);
        }

        gtk_window_present (GTK_WINDOW (existing_window));
        startup_data = startup_data_new (NULL, NULL, NULL, NULL, NULL, NULL,
                                         callback, callback_data, existing_window);
        g_signal_connect(GTK_WIDGET (existing_window), "destroy",
                         G_CALLBACK (widget_on_destroy), startup_data);
        return;
    }


    pending_key = get_pending_key (original_files);

    /* Look to see if we're already waiting for a window for this file. */
    if (g_hash_table_lookup (pending_lists, pending_key) != NULL)
    {
        /* FIXME: No callback is done if this happen. In practice, it's a quite
         * corner case
         */
        return;
    }

    target_files = get_target_file_list (original_files);

    if (parent_widget)
    {
        parent_window = GTK_WINDOW (gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW));
    }
    else
    {
        parent_window = NULL;
    }

    startup_data = startup_data_new (original_files,
                                     target_files,
                                     pending_key,
                                     parent_widget,
                                     parent_window,
                                     startup_id,
                                     callback,
                                     callback_data,
                                     NULL);

    nautilus_file_list_free (target_files);
    g_free (pending_key);

    /* Wait until we can tell whether it's a directory before showing, since
     * some one-time layout decisions depend on that info.
     */

    g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key);
    if (parent_widget)
    {
        g_signal_connect (parent_widget, "destroy",
                          G_CALLBACK (parent_widget_destroyed_callback), startup_data);
    }

    eel_timed_wait_start
        (cancel_create_properties_window_callback,
        startup_data,
        _("Creating Properties window."),
        parent_window == NULL ? NULL : GTK_WINDOW (parent_window));

    for (l = startup_data->target_files; l != NULL; l = next)
    {
        next = l->next;
        nautilus_file_call_when_ready
            (NAUTILUS_FILE (l->data),
            NAUTILUS_FILE_ATTRIBUTE_INFO,
            is_directory_ready_callback,
            startup_data);
    }
}

static void
real_response (GtkDialog *dialog,
               int        response)
{
    switch (response)
    {
        case GTK_RESPONSE_NONE:
        case GTK_RESPONSE_CLOSE:
        case GTK_RESPONSE_DELETE_EVENT:
        {
            gtk_widget_destroy (GTK_WIDGET (dialog));
        }
        break;

        default:
        {
            g_assert_not_reached ();
        }
        break;
    }
}

static void
real_destroy (GtkWidget *object)
{
    NautilusPropertiesWindow *window;
    GList *l;

    window = NAUTILUS_PROPERTIES_WINDOW (object);

    remove_window (window);

    unschedule_or_cancel_group_change (window);
    unschedule_or_cancel_owner_change (window);

    for (l = window->original_files; l != NULL; l = l->next)
    {
        nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->original_files);
    }
    nautilus_file_list_free (window->original_files);
    window->original_files = NULL;

    for (l = window->target_files; l != NULL; l = l->next)
    {
        nautilus_file_monitor_remove (NAUTILUS_FILE (l->data), &window->target_files);
    }
    nautilus_file_list_free (window->target_files);
    window->target_files = NULL;

    nautilus_file_list_free (window->changed_files);
    window->changed_files = NULL;

    if (window->deep_count_spinner_timeout_id > 0)
    {
        g_source_remove (window->deep_count_spinner_timeout_id);
    }

    while (window->deep_count_files)
    {
        stop_deep_count_for_file (window, window->deep_count_files->data);
    }

    window->name_field = NULL;

    g_list_free (window->permission_buttons);
    window->permission_buttons = NULL;

    g_list_free (window->permission_combos);
    window->permission_combos = NULL;

    g_list_free (window->change_permission_combos);
    window->change_permission_combos = NULL;

    if (window->initial_permissions)
    {
        g_hash_table_destroy (window->initial_permissions);
        window->initial_permissions = NULL;
    }

    g_list_free (window->value_fields);
    window->value_fields = NULL;

    if (window->update_directory_contents_timeout_id != 0)
    {
        g_source_remove (window->update_directory_contents_timeout_id);
        window->update_directory_contents_timeout_id = 0;
    }

    if (window->update_files_timeout_id != 0)
    {
        g_source_remove (window->update_files_timeout_id);
        window->update_files_timeout_id = 0;
    }

    GTK_WIDGET_CLASS (nautilus_properties_window_parent_class)->destroy (object);
}

static void
real_finalize (GObject *object)
{
    NautilusPropertiesWindow *window;

    window = NAUTILUS_PROPERTIES_WINDOW (object);

    g_list_free_full (window->mime_list, g_free);

    g_free (window->pending_name);

    if (window->select_idle_id != 0)
    {
        g_source_remove (window->select_idle_id);
    }

    G_OBJECT_CLASS (nautilus_properties_window_parent_class)->finalize (object);
}

/* icon selection callback to set the image of the file object to the selected file */
static void
set_icon (const char               *icon_uri,
          NautilusPropertiesWindow *properties_window)
{
    NautilusFile *file;
    char *file_uri;
    char *icon_path;

    g_assert (icon_uri != NULL);
    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (properties_window));

    icon_path = g_filename_from_uri (icon_uri, NULL, NULL);
    /* we don't allow remote URIs */
    if (icon_path != NULL)
    {
        GList *l;

        for (l = properties_window->original_files; l != NULL; l = l->next)
        {
            g_autoptr (GFile) file_location = NULL;
            g_autoptr (GFile) icon_location = NULL;
            g_autofree gchar *real_icon_uri = NULL;

            file = NAUTILUS_FILE (l->data);
            file_uri = nautilus_file_get_uri (file);
            file_location = nautilus_file_get_location (file);
            icon_location = g_file_new_for_uri (icon_uri);

            /* ’Tis a little bit of a misnomer. Actually a path. */
            real_icon_uri = g_file_get_relative_path (icon_location,
                                                      file_location);

            if (real_icon_uri == NULL)
            {
                real_icon_uri = g_strdup (icon_uri);
            }

            nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL, real_icon_uri);

            g_free (file_uri);
        }

        g_free (icon_path);
    }
}

static void
update_preview_callback (GtkFileChooser           *icon_chooser,
                         NautilusPropertiesWindow *window)
{
    GtkWidget *preview_widget;
    GdkPixbuf *pixbuf, *scaled_pixbuf;
    char *filename;
    double scale;

    pixbuf = NULL;

    filename = gtk_file_chooser_get_filename (icon_chooser);
    if (filename != NULL)
    {
        pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
    }

    if (pixbuf != NULL)
    {
        preview_widget = gtk_file_chooser_get_preview_widget (icon_chooser);
        gtk_file_chooser_set_preview_widget_active (icon_chooser, TRUE);

        if (gdk_pixbuf_get_width (pixbuf) > PREVIEW_IMAGE_WIDTH)
        {
            scale = (double) gdk_pixbuf_get_height (pixbuf) /
                    gdk_pixbuf_get_width (pixbuf);

            scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf,
                                                     PREVIEW_IMAGE_WIDTH,
                                                     scale * PREVIEW_IMAGE_WIDTH,
                                                     GDK_INTERP_BILINEAR);
            g_object_unref (pixbuf);
            pixbuf = scaled_pixbuf;
        }

        gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
    }
    else
    {
        gtk_file_chooser_set_preview_widget_active (icon_chooser, FALSE);
    }

    g_free (filename);

    if (pixbuf != NULL)
    {
        g_object_unref (pixbuf);
    }
}

static void
custom_icon_file_chooser_response_cb (GtkDialog                *dialog,
                                      gint                      response,
                                      NautilusPropertiesWindow *window)
{
    char *uri;

    switch (response)
    {
        case GTK_RESPONSE_NO:
        {
            reset_icon (window);
        }
        break;

        case GTK_RESPONSE_OK:
        {
            uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
            if (uri != NULL)
            {
                set_icon (uri, window);
            }
            else
            {
                reset_icon (window);
            }
            g_free (uri);
        }
        break;

        default:
        {
        }
        break;
    }

    gtk_widget_hide (GTK_WIDGET (dialog));
}

static void
select_image_button_callback (GtkWidget                *widget,
                              NautilusPropertiesWindow *window)
{
    GtkWidget *dialog, *preview;
    GtkFileFilter *filter;
    GList *l;
    NautilusFile *file;
    char *uri;
    char *image_path;
    gboolean revert_is_sensitive;

    g_assert (NAUTILUS_IS_PROPERTIES_WINDOW (window));

    dialog = window->icon_chooser;

    if (dialog == NULL)
    {
        dialog = gtk_file_chooser_dialog_new (_("Select Custom Icon"), GTK_WINDOW (window),
                                              GTK_FILE_CHOOSER_ACTION_OPEN,
                                              _("_Revert"), GTK_RESPONSE_NO,
                                              _("_Cancel"), GTK_RESPONSE_CANCEL,
                                              _("_Open"), GTK_RESPONSE_OK,
                                              NULL);
        gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
                                              g_get_user_special_dir (G_USER_DIRECTORY_PICTURES),
                                              NULL);
        gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
        gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

        filter = gtk_file_filter_new ();
        gtk_file_filter_add_pixbuf_formats (filter);
        gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);

        preview = gtk_image_new ();
        gtk_widget_set_size_request (preview, PREVIEW_IMAGE_WIDTH, -1);
        gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (dialog), preview);
        gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (dialog), FALSE);
        gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (dialog), FALSE);

        g_signal_connect (dialog, "update-preview",
                          G_CALLBACK (update_preview_callback), window);

        window->icon_chooser = dialog;

        g_object_add_weak_pointer (G_OBJECT (dialog),
                                   (gpointer *) &window->icon_chooser);
    }

    /* it's likely that the user wants to pick an icon that is inside a local directory */
    if (g_list_length (window->original_files) == 1)
    {
        file = NAUTILUS_FILE (window->original_files->data);

        if (nautilus_file_is_directory (file))
        {
            uri = nautilus_file_get_uri (file);

            image_path = g_filename_from_uri (uri, NULL, NULL);
            if (image_path != NULL)
            {
                gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), image_path);
                g_free (image_path);
            }

            g_free (uri);
        }
    }

    revert_is_sensitive = FALSE;
    for (l = window->original_files; l != NULL; l = l->next)
    {
        file = NAUTILUS_FILE (l->data);
        image_path = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL);
        revert_is_sensitive = (image_path != NULL);
        g_free (image_path);

        if (revert_is_sensitive)
        {
            break;
        }
    }
    gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_NO, revert_is_sensitive);

    g_signal_connect (dialog, "response",
                      G_CALLBACK (custom_icon_file_chooser_response_cb), window);
    gtk_widget_show (dialog);
}

static void
nautilus_properties_window_class_init (NautilusPropertiesWindowClass *class)
{
    GtkBindingSet *binding_set;

    G_OBJECT_CLASS (class)->finalize = real_finalize;
    GTK_WIDGET_CLASS (class)->destroy = real_destroy;
    GTK_DIALOG_CLASS (class)->response = real_response;

    binding_set = gtk_binding_set_by_class (class);
    gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
                                  "close", 0);
}

static void
nautilus_properties_window_init (NautilusPropertiesWindow *window)
{
}
