/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Caja
 *
 *  Copyright (C) 1999, 2000 Red Hat, Inc.
 *  Copyright (C) 1999, 2000, 2001 Eazel, Inc.
 *
 *  Caja is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  Caja 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
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  Authors: Elliot Lee <sopwith@redhat.com>
 *  	     John Sullivan <sullivan@eazel.com>
 *
 */

/* caja-window.c: Implementation of the main window object */

#include <config.h>
#include "caja-spatial-window.h"
#include "caja-window-private.h"
#include "caja-window-bookmarks.h"

#include "caja-actions.h"
#include "caja-application.h"
#include "caja-desktop-window.h"
#include "caja-bookmarks-window.h"
#include "caja-location-dialog.h"
#if ENABLE_LIBUNIQUE == (TRUE)
#include "caja-main.h"
#endif
#include "caja-query-editor.h"
#include "caja-search-bar.h"
#include "caja-window-manage-views.h"
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-gtk-macros.h>
#include <eel/eel-string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libcaja-private/caja-dnd.h>
#include <libcaja-private/caja-file-utilities.h>
#include <libcaja-private/caja-ui-utilities.h>
#include <libcaja-private/caja-file-attributes.h>
#include <libcaja-private/caja-global-preferences.h>
#include <libcaja-private/caja-metadata.h>
#include <libcaja-private/caja-mime-actions.h>
#include <libcaja-private/caja-program-choosing.h>
#include <libcaja-private/caja-search-directory.h>
#include <libcaja-private/caja-search-engine.h>
#include <libcaja-private/caja-signaller.h>

#if !GTK_CHECK_VERSION(3,0,0)
#define gtk_widget_get_preferred_size(x,y,z) gtk_widget_size_request(x,y)
#endif

#define MAX_TITLE_LENGTH 180
#define MAX_SHORTNAME_PATH 16

#define SPATIAL_ACTION_PLACES               "Places"
#define SPATIAL_ACTION_GO_TO_LOCATION       "Go to Location"
#define SPATIAL_ACTION_CLOSE_PARENT_FOLDERS "Close Parent Folders"
#define SPATIAL_ACTION_CLOSE_ALL_FOLDERS    "Close All Folders"
#define MENU_PATH_SPATIAL_BOOKMARKS_PLACEHOLDER	"/MenuBar/Other Menus/Places/Bookmarks Placeholder"

#if GTK_CHECK_VERSION (3, 0, 0)
#define gtk_hbox_new(X,Y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,Y)
#endif

struct _CajaSpatialWindowDetails
{
    GtkActionGroup *spatial_action_group; /* owned by ui_manager */
    char *last_geometry;
    guint save_geometry_timeout_id;

    gboolean saved_data_on_close;
    GtkWidget *content_box;
    GtkWidget *location_button;
    GtkWidget *location_label;
    GtkWidget *location_icon;
};

static const GtkTargetEntry location_button_drag_types[] =
{
    { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST },
    { CAJA_ICON_DND_URI_LIST_TYPE, 0, CAJA_ICON_DND_URI_LIST },
};

G_DEFINE_TYPE(CajaSpatialWindow, caja_spatial_window, CAJA_TYPE_WINDOW)
#define parent_class caja_spatial_window_parent_class

static void caja_spatial_window_save_geometry (CajaSpatialWindow *window,
						   CajaFile *viewed_file);

static gboolean
save_window_geometry_timeout (gpointer callback_data)
{
    CajaSpatialWindow *window;
    CajaWindowSlot *slot;

    window = CAJA_SPATIAL_WINDOW (callback_data);
    slot = caja_window_get_active_slot (CAJA_WINDOW (window));

    if (slot != NULL)
    {
        caja_spatial_window_save_geometry (window, slot->viewed_file);
    }

    window->details->save_geometry_timeout_id = 0;

    return FALSE;
}

static gboolean
caja_spatial_window_configure_event (GtkWidget *widget,
                                     GdkEventConfigure *event)
{
    CajaSpatialWindow *window;

    window = CAJA_SPATIAL_WINDOW (widget);

    GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->configure_event (widget, event);

    /* Only save the geometry if the user hasn't resized the window
     * for a second. Otherwise delay the callback another second.
     */
    if (window->details->save_geometry_timeout_id != 0)
    {
        g_source_remove (window->details->save_geometry_timeout_id);
    }

	window->details->save_geometry_timeout_id =
		g_timeout_add_seconds (1, save_window_geometry_timeout, window);

    return FALSE;
}

static void
caja_spatial_window_unrealize (GtkWidget *widget)
{
    CajaSpatialWindow *window;
    CajaWindowSlot *slot;

    window = CAJA_SPATIAL_WINDOW (widget);
    slot = caja_window_get_active_slot (CAJA_WINDOW (window));

    GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->unrealize (widget);

    if (window->details->save_geometry_timeout_id != 0)
    {
        g_source_remove (window->details->save_geometry_timeout_id);
        window->details->save_geometry_timeout_id = 0;

        if (slot != NULL)
        {
            caja_spatial_window_save_geometry (window, slot->viewed_file);
        }
    }
}

static gboolean
caja_spatial_window_state_event (GtkWidget *widget,
                                 GdkEventWindowState *event)
{
    CajaWindow *window;
    CajaWindowSlot *slot;
    CajaFile *viewed_file;

    window = CAJA_WINDOW (widget);
    slot = window->details->active_pane->active_slot;
    viewed_file = slot->viewed_file;

    if (!CAJA_IS_DESKTOP_WINDOW (widget))
    {

        if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED &&
                viewed_file != NULL)
        {
            caja_file_set_boolean_metadata (viewed_file,
                                            CAJA_METADATA_KEY_WINDOW_MAXIMIZED,
                                            FALSE,
                                            event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED);
        }

        if (event->changed_mask & GDK_WINDOW_STATE_STICKY &&
                viewed_file != NULL)
        {
            caja_file_set_boolean_metadata (viewed_file,
                                            CAJA_METADATA_KEY_WINDOW_STICKY,
                                            FALSE,
                                            event->new_window_state & GDK_WINDOW_STATE_STICKY);
        }

        if (event->changed_mask & GDK_WINDOW_STATE_ABOVE &&
                viewed_file != NULL)
        {
            caja_file_set_boolean_metadata (viewed_file,
                                            CAJA_METADATA_KEY_WINDOW_KEEP_ABOVE,
                                            FALSE,
                                            event->new_window_state & GDK_WINDOW_STATE_ABOVE);
        }

    }

    if (GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->window_state_event != NULL)
    {
        return GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->window_state_event (widget, event);
    }

    return FALSE;
}

static void
caja_spatial_window_finalize (GObject *object)
{
    CajaSpatialWindow *window;

    window = CAJA_SPATIAL_WINDOW (object);

    g_free (window->details->last_geometry);

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

static void
caja_spatial_window_save_geometry (CajaSpatialWindow *window,
        		   CajaFile *viewed_file)
{
    char *geometry_string;

    if (viewed_file == NULL)
    {
        /* We never showed a file */
        return;
    }

    if (gtk_widget_get_window (GTK_WIDGET (window)) &&
    	    gtk_widget_get_visible (GTK_WIDGET (window)) &&
	    !CAJA_IS_DESKTOP_WINDOW (window) &&
            !(gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET(window))) & GDK_WINDOW_STATE_MAXIMIZED)) {

        geometry_string = eel_gtk_window_get_geometry_string (GTK_WINDOW (window));

        if (!g_strcmp0 (window->details->last_geometry, geometry_string)) {
        	/* Don't save geometry if it's the same as before. */
        	g_free (geometry_string);
        	return;
        }

        g_free (window->details->last_geometry);
        window->details->last_geometry = geometry_string;

        caja_file_set_metadata (viewed_file,
                                CAJA_METADATA_KEY_WINDOW_GEOMETRY,
                                NULL,
                                geometry_string);
    }
}

static void
caja_spatial_window_save_scroll_position (CajaSpatialWindow *window,
					  CajaWindowSlot *slot)
{
    char *scroll_string;

    if (slot->content_view == NULL ||
            slot->viewed_file == NULL)
    {
        return;
    }

    scroll_string = caja_view_get_first_visible_file (slot->content_view);
    caja_file_set_metadata (slot->viewed_file,
                            CAJA_METADATA_KEY_WINDOW_SCROLL_POSITION,
                            NULL,
                            scroll_string);
    g_free (scroll_string);
}

static void
caja_spatial_window_save_show_hidden_files_mode (CajaSpatialWindow *window,
						 CajaFile *viewed_file)
{
    char *show_hidden_file_setting;
    CajaWindowShowHiddenFilesMode mode;

    if (viewed_file == NULL) {
        return;
    }

    mode = CAJA_WINDOW (window)->details->show_hidden_files_mode;

    if (mode != CAJA_WINDOW_SHOW_HIDDEN_FILES_DEFAULT) {
        if (mode == CAJA_WINDOW_SHOW_HIDDEN_FILES_ENABLE) {
            show_hidden_file_setting = "1";
        } else {
            show_hidden_file_setting = "0";
        }
        caja_file_set_metadata (viewed_file,
                                CAJA_METADATA_KEY_WINDOW_SHOW_HIDDEN_FILES,
                                NULL,
                                show_hidden_file_setting);
    }
}

static void
caja_spatial_window_show (GtkWidget *widget)
{
    CajaWindow *window;
    CajaWindowSlot *slot;

    window = CAJA_WINDOW (widget);
    slot = caja_window_get_active_slot (window);

    GTK_WIDGET_CLASS (caja_spatial_window_parent_class)->show (widget);

    if (slot != NULL && slot->query_editor != NULL)
    {
        caja_query_editor_grab_focus (CAJA_QUERY_EDITOR (slot->query_editor));
    }
}

static void
action_close_parent_folders_callback (GtkAction *action,
                                      gpointer user_data)
{
    caja_application_close_parent_windows (CAJA_SPATIAL_WINDOW (user_data));
}

static void
action_close_all_folders_callback (GtkAction *action,
                                   gpointer user_data)
{
    caja_application_close_all_spatial_windows ();
}

static void
real_prompt_for_location (CajaWindow *window,
                          const char     *initial)
{
    GtkWidget *dialog;

    dialog = caja_location_dialog_new (window);
    if (initial != NULL)
    {
        caja_location_dialog_set_location (CAJA_LOCATION_DIALOG (dialog),
                                           initial);
    }

    gtk_widget_show (dialog);
}

static CajaIconInfo *
real_get_icon (CajaWindow *window,
               CajaWindowSlot *slot)
{
    return caja_file_get_icon (slot->viewed_file, 48,
                               CAJA_FILE_ICON_FLAGS_IGNORE_VISITING |
                               CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON);
}

static void
sync_window_title (CajaWindow *window)
{
    CajaWindowSlot *slot;

    slot = caja_window_get_active_slot (window);

    if (slot->title == NULL || slot->title[0] == '\0')
    {
        gtk_window_set_title (GTK_WINDOW (window), _("Caja"));
    }
    else
    {
        char *window_title;

        window_title = eel_str_middle_truncate (slot->title, MAX_TITLE_LENGTH);
        gtk_window_set_title (GTK_WINDOW (window), window_title);
        g_free (window_title);
    }
}

static void
real_sync_title (CajaWindow *window,
                 CajaWindowSlot *slot)
{
    g_assert (slot == caja_window_get_active_slot (window));

    sync_window_title (window);
}

static void
real_get_min_size (CajaWindow *window,
                   guint *min_width, guint *min_height)
{
    if (min_width)
    {
        *min_width = CAJA_SPATIAL_WINDOW_MIN_WIDTH;
    }
    if (min_height)
    {
        *min_height = CAJA_SPATIAL_WINDOW_MIN_HEIGHT;
    }
}

static void
real_get_default_size (CajaWindow *window,
                       guint *default_width, guint *default_height)
{
    if (default_width)
    {
        *default_width = CAJA_SPATIAL_WINDOW_DEFAULT_WIDTH;
    }
    if (default_height)
    {
        *default_height = CAJA_SPATIAL_WINDOW_DEFAULT_HEIGHT;
    }
}

static void
real_sync_allow_stop (CajaWindow *window,
                      CajaWindowSlot *slot)
{
}

static void
real_set_allow_up (CajaWindow *window, gboolean allow)
{
    CajaSpatialWindow *spatial;
    GtkAction *action;

    spatial = CAJA_SPATIAL_WINDOW (window);

    action = gtk_action_group_get_action (spatial->details->spatial_action_group,
                                          SPATIAL_ACTION_CLOSE_PARENT_FOLDERS);
    gtk_action_set_sensitive (action, allow);

    CAJA_WINDOW_CLASS (caja_spatial_window_parent_class)->set_allow_up (window, allow);
}

static CajaWindowSlot *
real_open_slot (CajaWindowPane *pane,
                CajaWindowOpenSlotFlags flags)
{
    CajaWindowSlot *slot;
    GList *slots;

    g_assert (caja_window_get_active_slot (pane->window) == NULL);

    slots = caja_window_get_slots (pane->window);
    g_assert (slots == NULL);
    g_list_free (slots);

    slot = g_object_new (CAJA_TYPE_WINDOW_SLOT, NULL);
    slot->pane = pane;
    gtk_container_add (GTK_CONTAINER (CAJA_SPATIAL_WINDOW (pane->window)->details->content_box),
                       slot->content_box);
    gtk_widget_show (slot->content_box);
    return slot;
}

static void
save_spatial_data (CajaSpatialWindow *window,
		   CajaWindowSlot *slot)
{
    caja_spatial_window_save_geometry (window, slot->viewed_file);
    caja_spatial_window_save_scroll_position (window, slot);
    caja_spatial_window_save_show_hidden_files_mode (window, slot->viewed_file);
}

static void
real_close_slot (CajaWindowPane *pane,
                 CajaWindowSlot *slot)
{
    CajaSpatialWindow *window;

    window = CAJA_SPATIAL_WINDOW (pane->window);

    /* Save spatial data for close if we didn't already */
    if (!window->details->saved_data_on_close) {
        save_spatial_data (window, slot);
    }

    CAJA_WINDOW_CLASS (caja_spatial_window_parent_class)->close_slot (pane, slot);
}

static void
real_window_close (CajaWindow *window)
{
    CajaWindowSlot *slot;
    CajaSpatialWindow *self;

    self = CAJA_SPATIAL_WINDOW (window);

    /* We're closing the window, save the geometry. */
    /* Note that we do this in window close, not slot close, because slot
     * close is too late, by then the widgets have been unrealized.
     * This is for the close by WM case, if you're closing via Ctrl-W that
     * means we close the slots first and this is not an issue */
    slot = caja_window_get_active_slot (window);

    if (slot != NULL) {
        save_spatial_data (self, slot);
        self->details->saved_data_on_close = TRUE;
    }

    if (CAJA_WINDOW_CLASS (caja_spatial_window_parent_class)->close != NULL) {
        CAJA_WINDOW_CLASS (caja_spatial_window_parent_class)->close (window);
    }
}

static void
location_menu_item_activated_callback (GtkWidget *menu_item,
                                       CajaWindow *window)
{
    CajaWindowSlot *slot;
    GFile *current;
    GFile *dest;
    GdkEvent *event;

	slot = caja_window_get_active_slot (window);
	current = caja_window_slot_get_location (slot);
	dest = g_object_get_data (G_OBJECT (menu_item), "location");

    event = gtk_get_current_event();

    if (!g_file_equal (current, dest))
    {
        GFile *child;
        gboolean close_behind;
        GList *selection;

        close_behind = FALSE;
        selection = NULL;

        child = g_object_get_data (G_OBJECT(menu_item), "child_location");
        if (child != NULL) {
            selection = g_list_prepend (NULL, g_object_ref (child));
        }

        if (event != NULL && ((GdkEventAny *) event)->type == GDK_BUTTON_RELEASE &&
                (((GdkEventButton *) event)->button == 2 ||
                 (((GdkEventButton *) event)->state & GDK_SHIFT_MASK) != 0)) {
            close_behind = TRUE;
        }

        caja_window_slot_open_location_with_selection
        (slot, dest, selection, close_behind);

    	g_list_free_full (selection, g_object_unref);
    }

    if (event != NULL) {
        gdk_event_free (event);
    }

    g_object_unref (current);
}

static void
got_file_info_for_location_menu_callback (CajaFile *file,
        gpointer callback_data)
{
    GtkWidget *menu_item = callback_data;
    GtkWidget *label;
    GtkWidget *icon;
    GdkPixbuf *pixbuf;
    char *name;

    g_return_if_fail (CAJA_IS_FILE (file));

    pixbuf = NULL;

    name = caja_file_get_display_name (file);
    label = gtk_bin_get_child (GTK_BIN (menu_item));
    gtk_label_set_label (GTK_LABEL (label), name);
    g_free (name);

    pixbuf = caja_file_get_icon_pixbuf (file,
                                        caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
                                        TRUE,
                                        CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);

    if (pixbuf != NULL)
    {
        icon = gtk_image_new_from_pixbuf (pixbuf);
        g_object_unref (pixbuf);
    }
    else
    {
        icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
    }

    if (icon)
    {
        gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), icon);
    }
    g_object_unref (file);
    g_object_unref (menu_item);
}

static void
menu_deactivate_callback (GtkWidget *menu,
                          gpointer   data)
{
    GMainLoop *loop;

    loop = data;

    if (g_main_loop_is_running (loop))
    {
        g_main_loop_quit (loop);
    }
}

static void
menu_popup_pos (GtkMenu   *menu,
                gint      *x,
                gint      *y,
                gboolean  *push_in,
                gpointer	user_data)
{
    GtkWidget *widget;
    GtkRequisition menu_requisition, button_requisition;
    GtkAllocation allocation;

    widget = user_data;

    gtk_widget_get_preferred_size (GTK_WIDGET (menu), &menu_requisition, NULL);
    gtk_widget_get_preferred_size (widget, &button_requisition, NULL);
    gtk_widget_get_allocation (widget, &allocation);

    gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
    *x += allocation.x;
    *y += allocation.y;

    *y -= menu_requisition.height - button_requisition.height;

    *push_in = TRUE;
}

static gboolean
location_button_pressed_callback (GtkWidget      *widget,
                                  GdkEventButton *event,
                                  CajaWindow *window)
{
	CajaWindowSlot *slot;
    CajaView *view;

	slot = caja_window_get_active_slot (window);
	view = slot->content_view;

    if (event->button == 3 && view != NULL)
    {
        caja_view_pop_up_location_context_menu (view, event, NULL);
    }

    return FALSE;
}

static void
location_button_clicked_callback (GtkWidget             *widget,
                                  CajaSpatialWindow *window)
{
    CajaWindowSlot *slot;
    GtkWidget *popup, *menu_item, *first_item = NULL;
    GFile *location;
    GFile *child_location;
    GMainLoop *loop;

    slot = caja_window_get_active_slot (CAJA_WINDOW (window));

    popup = gtk_menu_new ();
    first_item = NULL;

    location = caja_window_slot_get_location (slot);
    g_return_if_fail (location != NULL);

    child_location = NULL;
    while (location != NULL) {
        CajaFile *file;
        char *name;

        file = caja_file_get (location);

        name = caja_file_get_display_name (file);
        menu_item = gtk_image_menu_item_new_with_label (name);
        gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menu_item), TRUE);
        g_free (name);

        if (first_item == NULL) {
            first_item = menu_item;
        }

        g_object_ref (menu_item);
        caja_file_call_when_ready (file,
                                   CAJA_FILE_ATTRIBUTE_INFO,
                                   got_file_info_for_location_menu_callback,
                                   menu_item);

        gtk_widget_show (menu_item);
        g_signal_connect (menu_item, "activate",
                          G_CALLBACK (location_menu_item_activated_callback),
                          window);

        g_object_set_data_full (G_OBJECT (menu_item),
                                "location",
                                g_object_ref (location),
                                (GDestroyNotify)g_object_unref);

        if (child_location) {
            g_object_set_data_full (G_OBJECT (menu_item),
                                    "child_location",
                                    g_object_ref (child_location),
                                    (GDestroyNotify)g_object_unref);
        }

        gtk_menu_shell_prepend (GTK_MENU_SHELL (popup), menu_item);

        if (child_location) {
            g_object_unref (child_location);
        }
        child_location = location;
        location = g_file_get_parent (location);
    }

    if (child_location) {
        g_object_unref (child_location);
    }
    if (location) {
        g_object_unref (location);
    }

    gtk_menu_set_screen (GTK_MENU (popup), gtk_widget_get_screen (widget));

    loop = g_main_loop_new (NULL, FALSE);

    g_signal_connect (popup, "deactivate",
                      G_CALLBACK (menu_deactivate_callback),
                      loop);

    gtk_grab_add (popup);
    gtk_menu_popup (GTK_MENU (popup), NULL, NULL, menu_popup_pos, widget, 1, GDK_CURRENT_TIME);
    gtk_menu_shell_select_item (GTK_MENU_SHELL (popup), first_item);
    g_main_loop_run (loop);
    gtk_grab_remove (popup);
    g_main_loop_unref (loop);
    g_object_ref_sink (popup);
    g_object_unref (popup);
}

static int
get_dnd_icon_size (CajaSpatialWindow *window)
{
	CajaWindowSlot *active_slot;
    CajaView *view;
    CajaZoomLevel zoom_level;

	active_slot = caja_window_get_active_slot (CAJA_WINDOW (window));
	view = active_slot->content_view;

    if (view == NULL)
    {
        return CAJA_ICON_SIZE_STANDARD;
    }
    else
    {
        zoom_level = caja_view_get_zoom_level (view);
        return caja_get_icon_size_for_zoom_level (zoom_level);
    }
}

static void
location_button_drag_begin_callback (GtkWidget             *widget,
                                     GdkDragContext        *context,
                                     CajaSpatialWindow *window)
{
    CajaWindowSlot *slot;
    GdkPixbuf *pixbuf;

    slot = CAJA_WINDOW (window)->details->active_pane->active_slot;

    pixbuf = caja_file_get_icon_pixbuf (slot->viewed_file,
                                        get_dnd_icon_size (window),
                                        FALSE,
                                        CAJA_FILE_ICON_FLAGS_IGNORE_VISITING | CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT);

    gtk_drag_set_icon_pixbuf (context, pixbuf, 0, 0);

    g_object_unref (pixbuf);
}

/* build MATE icon list, which only contains the window's URI.
 * If we just used URIs, moving the folder to trash
 * wouldn't work */
static void
get_data_binder (CajaDragEachSelectedItemDataGet iteratee,
                 gpointer                            iterator_context,
                 gpointer                            data)
{
    CajaSpatialWindow *window;
    CajaWindowSlot *slot;
    char *location;
    int icon_size;

    g_assert (CAJA_IS_SPATIAL_WINDOW (iterator_context));
    window = CAJA_SPATIAL_WINDOW (iterator_context);

    slot = CAJA_WINDOW (window)->details->active_pane->active_slot;

    location = caja_window_slot_get_location_uri (slot);
    icon_size = get_dnd_icon_size (window);

    iteratee (location,
              0,
              0,
              icon_size,
              icon_size,
              data);

    g_free (location);
}

static void
location_button_drag_data_get_callback (GtkWidget             *widget,
                                        GdkDragContext        *context,
                                        GtkSelectionData      *selection_data,
                                        guint                  info,
                                        guint                  time,
                                        CajaSpatialWindow *window)
{
    caja_drag_drag_data_get (widget, context, selection_data,
                             info, time, window, get_data_binder);
}

void
caja_spatial_window_set_location_button  (CajaSpatialWindow *window,
        GFile                 *location)
{
    if (location != NULL)
    {
        CajaFile *file;
        char *name;
        GError *error;

        file = caja_file_get (location);

        /* FIXME: monitor for name change... */
        name = caja_file_get_display_name (file);
        gtk_label_set_label (GTK_LABEL (window->details->location_label),
                             name);
        g_free (name);
        gtk_widget_set_sensitive (window->details->location_button, TRUE);

        error = caja_file_get_file_info_error (file);
        if (error == NULL)
        {
            GdkPixbuf *pixbuf;

            pixbuf = caja_file_get_icon_pixbuf (file,
                                                caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU),
                                                TRUE,
                                                CAJA_FILE_ICON_FLAGS_IGNORE_VISITING);

            if (pixbuf != NULL)
            {
                gtk_image_set_from_pixbuf (GTK_IMAGE (window->details->location_icon),  pixbuf);
                g_object_unref (pixbuf);
            }
            else
            {
                gtk_image_set_from_stock (GTK_IMAGE (window->details->location_icon),
                                          GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
            }
        }
        g_object_unref (file);

    }
    else
    {
        gtk_label_set_label (GTK_LABEL (window->details->location_label),
                             "");
        gtk_widget_set_sensitive (window->details->location_button, FALSE);
    }
}

static void
action_go_to_location_callback (GtkAction *action,
                                gpointer user_data)
{
    CajaWindow *window;

    window = CAJA_WINDOW (user_data);

    caja_window_prompt_for_location (window, NULL);
}

static void
action_add_bookmark_callback (GtkAction *action,
                              gpointer user_data)
{
    CajaWindow *window;

    window = CAJA_WINDOW (user_data);

    if (!CAJA_IS_DESKTOP_WINDOW (window))   /* don't bookmark x-caja-desktop:/// */
    {
        caja_window_add_bookmark_for_current_location (window);
    }
}

static void
action_edit_bookmarks_callback (GtkAction *action,
                                gpointer user_data)
{
    caja_window_edit_bookmarks (CAJA_WINDOW (user_data));
}

static void
action_search_callback (GtkAction *action,
                        gpointer user_data)
{
    CajaWindow *window;
    char *uri;
    GFile *f;

    window = CAJA_WINDOW (user_data);

    uri = caja_search_directory_generate_new_uri ();
    f = g_file_new_for_uri (uri);
    caja_window_go_to (window, f);
    g_object_unref (f);
    g_free (uri);
}

static const GtkActionEntry spatial_entries[] =
{
    /* name, stock id, label */  { SPATIAL_ACTION_PLACES, NULL, N_("_Places") },
    /* name, stock id, label */  {
        SPATIAL_ACTION_GO_TO_LOCATION, NULL, N_("Open _Location..."),
        "<control>L", N_("Specify a location to open"),
        G_CALLBACK (action_go_to_location_callback)
    },
    /* name, stock id, label */  {
        SPATIAL_ACTION_CLOSE_PARENT_FOLDERS, NULL, N_("Close P_arent Folders"),
        "<control><shift>W", N_("Close this folder's parents"),
        G_CALLBACK (action_close_parent_folders_callback)
    },
    /* name, stock id, label */  {
        SPATIAL_ACTION_CLOSE_ALL_FOLDERS, NULL, N_("Clos_e All Folders"),
        "<control>Q", N_("Close all folder windows"),
        G_CALLBACK (action_close_all_folders_callback)
    },
    /* name, stock id, label */  { "Add Bookmark", GTK_STOCK_ADD, N_("_Add Bookmark"),
        "<control>d", N_("Add a bookmark for the current location to this menu"),
        G_CALLBACK (action_add_bookmark_callback)
    },
    /* name, stock id, label */  { "Edit Bookmarks", NULL, N_("_Edit Bookmarks..."),
        "<control>b", N_("Display a window that allows editing the bookmarks in this menu"),
        G_CALLBACK (action_edit_bookmarks_callback)
    },
    /* name, stock id, label */  { "Search", "gtk-find", N_("_Search for Files..."),
        "<control>F", N_("Locate documents and folders on this computer by name or content"),
        G_CALLBACK (action_search_callback)
    },
};

static const char* icon_entries[] =
{
    "/MenuBar/Other Menus/Places/Home",
    "/MenuBar/Other Menus/Places/Go to Computer",
    "/MenuBar/Other Menus/Places/Go to Templates",
    "/MenuBar/Other Menus/Places/Go to Trash",
    "/MenuBar/Other Menus/Places/Go to Network"
};

static void
caja_spatial_window_init (CajaSpatialWindow *window)
{
#if !GTK_CHECK_VERSION (3, 0, 0)
    GtkRcStyle *rc_style;
#endif
    GtkWidget *arrow;
    GtkWidget *hbox, *vbox;
    GtkActionGroup *action_group;
    GtkUIManager *ui_manager;
    GtkTargetList *targets;
    const char *ui;
    int i;
    GtkWidget *menuitem;
    CajaWindow *win;
    CajaWindowPane *pane;

    window->details = G_TYPE_INSTANCE_GET_PRIVATE (window,
                      CAJA_TYPE_SPATIAL_WINDOW,
                      CajaSpatialWindowDetails);

    win = CAJA_WINDOW (window);

#if GTK_CHECK_VERSION(3, 0, 0)
    gtk_widget_set_hexpand (win->details->statusbar, TRUE);
    gtk_grid_attach (GTK_GRID (win->details->grid),
                     win->details->statusbar,
                     0, 5, 1, 1);
    gtk_widget_show (win->details->statusbar);

    pane = caja_window_pane_new (win);
    win->details->panes = g_list_prepend (win->details->panes, pane);

    vbox = gtk_vbox_new (FALSE, 0);
    gtk_widget_set_hexpand (vbox, TRUE);
    gtk_widget_set_vexpand (vbox, TRUE);
    gtk_grid_attach (GTK_GRID (CAJA_WINDOW (window)->details->grid),
                     vbox,
                     0, 1, 1, 3);
#else
    gtk_table_attach (GTK_TABLE (win->details->table),
                      win->details->statusbar,
                      /* X direction */                   /* Y direction */
                      0, 1,                               5, 6,
                      GTK_EXPAND | GTK_FILL | GTK_SHRINK, 0,
                      0,                                  0);
    gtk_widget_show (win->details->statusbar);

    pane = caja_window_pane_new (win);
    win->details->panes = g_list_prepend (win->details->panes, pane);

    vbox = gtk_vbox_new (FALSE, 0);
    gtk_table_attach (GTK_TABLE (CAJA_WINDOW (window)->details->table),
                      vbox,
                      /* X direction */                   /* Y direction */
                      0, 1,                               1, 4,
                      GTK_EXPAND | GTK_FILL | GTK_SHRINK, GTK_EXPAND | GTK_FILL | GTK_SHRINK,
                      0,                                  0);
#endif
    gtk_widget_show (vbox);
    window->details->content_box = vbox;

    window->details->location_button = gtk_button_new ();
    g_signal_connect (window->details->location_button,
                      "button-press-event",
                      G_CALLBACK (location_button_pressed_callback),
                      window);
    gtk_button_set_relief (GTK_BUTTON (window->details->location_button),
                           GTK_RELIEF_NORMAL);
#if !GTK_CHECK_VERSION (3, 0, 0)
    rc_style = gtk_widget_get_modifier_style (window->details->location_button);
    rc_style->xthickness = 0;
    rc_style->ythickness = 0;
    gtk_widget_modify_style (window->details->location_button,
                             rc_style);
#endif

    gtk_widget_show (window->details->location_button);
    hbox = gtk_hbox_new (FALSE, 3);
    gtk_container_add (GTK_CONTAINER (window->details->location_button),
                       hbox);
    gtk_widget_show (hbox);

    window->details->location_icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
    gtk_box_pack_start (GTK_BOX (hbox), window->details->location_icon, FALSE, FALSE, 0);
    gtk_widget_show (window->details->location_icon);

    window->details->location_label = gtk_label_new ("");
    gtk_label_set_ellipsize (GTK_LABEL (window->details->location_label), PANGO_ELLIPSIZE_END);
    gtk_label_set_max_width_chars (GTK_LABEL (window->details->location_label), MAX_SHORTNAME_PATH);
    gtk_box_pack_start (GTK_BOX (hbox), window->details->location_label,
                        FALSE, FALSE, 0);
    gtk_widget_show (window->details->location_label);

#if GTK_CHECK_VERSION (3, 0, 0)
    arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
#else
    arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
#endif
    gtk_box_pack_start (GTK_BOX (hbox), arrow, FALSE, FALSE, 0);
    gtk_widget_show (arrow);

    gtk_drag_source_set (window->details->location_button,
                         GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, location_button_drag_types,
                         G_N_ELEMENTS (location_button_drag_types),
                         GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
    g_signal_connect (window->details->location_button,
                      "drag_begin",
                      G_CALLBACK (location_button_drag_begin_callback),
                      window);
    g_signal_connect (window->details->location_button,
                      "drag_data_get",
                      G_CALLBACK (location_button_drag_data_get_callback),
                      window);

    targets = gtk_drag_source_get_target_list (window->details->location_button);
    gtk_target_list_add_text_targets (targets, CAJA_ICON_DND_TEXT);

    gtk_widget_set_sensitive (window->details->location_button, FALSE);
    g_signal_connect (window->details->location_button,
                      "clicked",
                      G_CALLBACK (location_button_clicked_callback), window);
    gtk_box_pack_start (GTK_BOX (CAJA_WINDOW (window)->details->statusbar),
                        window->details->location_button,
                        FALSE, TRUE, 0);

    gtk_box_reorder_child (GTK_BOX (CAJA_WINDOW (window)->details->statusbar),
                           window->details->location_button, 0);

    action_group = gtk_action_group_new ("SpatialActions");
    gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
    window->details->spatial_action_group = action_group;
    gtk_action_group_add_actions (action_group,
                                  spatial_entries, G_N_ELEMENTS (spatial_entries),
                                  window);

    ui_manager = caja_window_get_ui_manager (CAJA_WINDOW (window));
    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    g_object_unref (action_group); /* owned by ui manager */

    ui = caja_ui_string_get ("caja-spatial-window-ui.xml");
    gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);

    for (i = 0; i < G_N_ELEMENTS (icon_entries); i++)
    {
        menuitem = gtk_ui_manager_get_widget (ui_manager, icon_entries[i]);
        gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (menuitem), TRUE);
    }
    caja_window_set_active_pane (win, pane);
}

static void
caja_spatial_window_class_init (CajaSpatialWindowClass *klass)
{
    GtkBindingSet *binding_set;
	CajaWindowClass *nclass = CAJA_WINDOW_CLASS (klass);
	GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);

	nclass->window_type = CAJA_WINDOW_SPATIAL;
	nclass->bookmarks_placeholder = MENU_PATH_SPATIAL_BOOKMARKS_PLACEHOLDER;
	nclass->prompt_for_location = real_prompt_for_location;
	nclass->get_icon = real_get_icon;
	nclass->sync_title = real_sync_title;
	nclass->get_min_size = real_get_min_size;
	nclass->get_default_size = real_get_default_size;
	nclass->sync_allow_stop = real_sync_allow_stop;
	nclass->set_allow_up = real_set_allow_up;
	nclass->open_slot = real_open_slot;
	nclass->close = real_window_close;
	nclass->close_slot = real_close_slot;

	wclass->show = caja_spatial_window_show;
	wclass->configure_event = caja_spatial_window_configure_event;
	wclass->unrealize = caja_spatial_window_unrealize;
	wclass->window_state_event = caja_spatial_window_state_event;

	G_OBJECT_CLASS (klass)->finalize = caja_spatial_window_finalize;

	binding_set = gtk_binding_set_by_class (klass);
	gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, GDK_SHIFT_MASK,
                                  "go_up", 1,
                                  G_TYPE_BOOLEAN, TRUE);
	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_SHIFT_MASK | GDK_MOD1_MASK,
                                  "go_up", 1,
                                  G_TYPE_BOOLEAN, TRUE);

	g_type_class_add_private (klass, sizeof(CajaSpatialWindowDetails));
}