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

/* fm-desktop-icon-view.c - implementation of icon view for managing the desktop.

   Copyright (C) 2000, 2001 Eazel, Inc.mou

   The Ukui 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 Ukui 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 Ukui Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
   Boston, MA 02110-1301, USA.

   Authors: Mike Engber <engber@eazel.com>
   	    Gene Z. Ragan <gzr@eazel.com>
	    Miguel de Icaza <miguel@ximian.com>
*/

#include <config.h>
#include "fm-icon-container.h"
#include "fm-desktop-icon-view.h"
#include "fm-actions.h"

#include <X11/Xatom.h>
#include <gtk/gtk.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-gtk-extensions.h>
#include <eel/eel-vfs-extensions.h>
#include <fcntl.h>
#include <gdk/gdkx.h>
#include <glib/gi18n.h>
#include <libpeony-private/peony-desktop-icon-file.h>
#include <libpeony-private/peony-directory-background.h>
#include <libpeony-private/peony-directory-notify.h>
#include <libpeony-private/peony-file-changes-queue.h>
#include <libpeony-private/peony-file-operations.h>
#include <libpeony-private/peony-file-utilities.h>
#include <libpeony-private/peony-ui-utilities.h>
#include <libpeony-private/peony-global-preferences.h>
#include <libpeony-private/peony-view-factory.h>
#include <libpeony-private/peony-link.h>
#include <libpeony-private/peony-metadata.h>
#include <libpeony-private/peony-monitor.h>
#include <libpeony-private/peony-program-choosing.h>
#include <libpeony-private/peony-trash-monitor.h>
#include <libpeony-private/peony-icon-private.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/* Timeout to check the desktop directory for updates */
#define RESCAN_TIMEOUT 4

struct FMDesktopIconViewDetails
{
    GdkWindow *root_window;
    GtkActionGroup *desktop_action_group;
    guint desktop_merge_id;

    /* For the desktop rescanning
     */
    gulong delayed_init_signal;
    guint reload_desktop_timeout;
    gboolean pending_rescan;
};

static void     default_zoom_level_changed                        (gpointer                user_data);
static gboolean real_supports_auto_layout                         (FMIconView             *view);
static gboolean real_supports_scaling	                          (FMIconView             *view);
static gboolean real_supports_keep_aligned                        (FMIconView             *view);
static gboolean real_supports_labels_beside_icons                 (FMIconView             *view);
static void     real_merge_menus                                  (FMDirectoryView        *view);
static void     real_update_menus                                 (FMDirectoryView        *view);
static gboolean real_supports_zooming                             (FMDirectoryView        *view);
static void     fm_desktop_icon_view_update_icon_container_fonts  (FMDesktopIconView      *view);
static void     font_changed_callback                             (gpointer                callback_data);
static PeonyZoomLevel get_default_zoom_level (void);

G_DEFINE_TYPE (FMDesktopIconView, fm_desktop_icon_view, FM_TYPE_ICON_VIEW)

static char *desktop_directory;
static time_t desktop_dir_modify_time;

static void
desktop_directory_changed_callback (gpointer callback_data)
{
    g_free (desktop_directory);
    desktop_directory = peony_get_desktop_directory ();
}

static PeonyIconContainer *
get_icon_container (FMDesktopIconView *icon_view)
{
    g_return_val_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view), NULL);
    g_return_val_if_fail (PEONY_IS_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view))), NULL);

    return PEONY_ICON_CONTAINER (gtk_bin_get_child (GTK_BIN (icon_view)));
}

static void
icon_container_set_workarea (PeonyIconContainer *icon_container,
                             GdkScreen             *screen,
                             long                  *workareas,
                             int                    n_items)
{
    int left, right, top, bottom;
    int screen_width, screen_height;
    int i;

    left = right = top = bottom = 0;

    screen_width  = gdk_screen_get_width (screen);
    screen_height = gdk_screen_get_height (screen);

    for (i = 0; i < n_items; i += 4)
    {
        int x      = workareas [i];
        int y      = workareas [i + 1];
        int width  = workareas [i + 2];
        int height = workareas [i + 3];

        if ((x + width) > screen_width || (y + height) > screen_height)
            continue;

        left   = MAX (left, x);
        right  = MAX (right, screen_width - width - x);
        top    = MAX (top, y);
        bottom = MAX (bottom, screen_height - height - y);
    }

    peony_icon_container_set_margins (icon_container,
                                     left, right, top, bottom);
}

static void
net_workarea_changed (FMDesktopIconView *icon_view,
                      GdkWindow         *window)
{
    long *nworkareas = NULL;
    long *workareas = NULL;
    GdkAtom type_returned;
    int format_returned;
    int length_returned;
    PeonyIconContainer *icon_container;
    GdkScreen *screen;

    g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (icon_view));

    icon_container = get_icon_container (icon_view);

    /* Find the number of desktops so we know how long the
     * workareas array is going to be (each desktop will have four
     * elements in the workareas array describing
     * x,y,width,height) */
    gdk_error_trap_push ();
    if (!gdk_property_get (window,
                           gdk_atom_intern ("_NET_NUMBER_OF_DESKTOPS", FALSE),
                           gdk_x11_xatom_to_atom (XA_CARDINAL),
                           0, 4, FALSE,
                           &type_returned,
                           &format_returned,
                           &length_returned,
                           (guchar **) &nworkareas))
    {
        g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS");
    }
    if (gdk_error_trap_pop()
            || nworkareas == NULL
            || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL)
            || format_returned != 32)
        g_warning("Can not calculate _NET_NUMBER_OF_DESKTOPS");

    /* Note : gdk_property_get() is broken (API documents admit
     * this).  As a length argument, it expects the number of
     * _bytes_ of data you require.  Internally, gdk_property_get
     * converts that value to a count of 32 bit (4 byte) elements.
     * However, the length returned is in bytes, but is calculated
     * via the count of returned elements * sizeof(long).  This
     * means on a 64 bit system, the number of bytes you have to
     * request does not correspond to the number of bytes you get
     * back, and is the reason for the workaround below.
     */
    gdk_error_trap_push ();
    if (nworkareas == NULL || (*nworkareas < 1)
            || !gdk_property_get (window,
                                  gdk_atom_intern ("_NET_WORKAREA", FALSE),
                                  gdk_x11_xatom_to_atom (XA_CARDINAL),
                                  0, ((*nworkareas) * 4 * 4), FALSE,
                                  &type_returned,
                                  &format_returned,
                                  &length_returned,
                                  (guchar **) &workareas))
    {
        g_warning("Can not get _NET_WORKAREA");
        workareas = NULL;
    }

    if (gdk_error_trap_pop ()
            || workareas == NULL
            || type_returned != gdk_x11_xatom_to_atom (XA_CARDINAL)
            || ((*nworkareas) * 4 * sizeof(long)) != length_returned
            || format_returned != 32)
    {
        g_warning("Can not determine workarea, guessing at layout");
        peony_icon_container_set_margins (icon_container,
                                         0, 0, 0, 0);
    }
    else
    {
        screen = gdk_window_get_screen (window);

        icon_container_set_workarea (
            icon_container, screen, workareas, length_returned / sizeof (long));
    }

    if (nworkareas != NULL)
        g_free (nworkareas);

    if (workareas != NULL)
        g_free (workareas);
}

static GdkFilterReturn
desktop_icon_view_property_filter (GdkXEvent *gdk_xevent,
                                   GdkEvent *event,
                                   gpointer data)
{
    XEvent *xevent = gdk_xevent;
    FMDesktopIconView *icon_view;

    icon_view = FM_DESKTOP_ICON_VIEW (data);

    switch (xevent->type)
    {
    case PropertyNotify:
        if (xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_NET_WORKAREA"))
            net_workarea_changed (icon_view, event->any.window);
        break;
    default:
        break;
    }

    return GDK_FILTER_CONTINUE;
}

static void
fm_desktop_icon_view_dispose (GObject *object)
{
    FMDesktopIconView *icon_view;
    GtkUIManager *ui_manager;

    icon_view = FM_DESKTOP_ICON_VIEW (object);

    /* Remove desktop rescan timeout. */
    if (icon_view->details->reload_desktop_timeout != 0)
    {
        g_source_remove (icon_view->details->reload_desktop_timeout);
        icon_view->details->reload_desktop_timeout = 0;
    }

    ui_manager = fm_directory_view_get_ui_manager (FM_DIRECTORY_VIEW (icon_view));
    if (ui_manager != NULL)
    {
        peony_ui_unmerge_ui (ui_manager,
                            &icon_view->details->desktop_merge_id,
                            &icon_view->details->desktop_action_group);
    }

    g_signal_handlers_disconnect_by_func (peony_icon_view_preferences,
                                          default_zoom_level_changed,
                                          icon_view);
    g_signal_handlers_disconnect_by_func (peony_preferences,
                                          font_changed_callback,
                                          icon_view);

    g_signal_handlers_disconnect_by_func (ukui_lockdown_preferences,
                                          fm_directory_view_update_menus,
                                          icon_view);
    g_signal_handlers_disconnect_by_func (peony_preferences,
                                          desktop_directory_changed_callback,
                                          NULL);

    G_OBJECT_CLASS (fm_desktop_icon_view_parent_class)->dispose (object);
}

static PeonyZoomLevel
fm_desktop_view_get_zoom_level (FMDirectoryView *view)
{
    g_return_val_if_fail (FM_IS_ICON_VIEW (view), PEONY_ZOOM_LEVEL_STANDARD);

    return peony_icon_container_get_zoom_level (get_icon_container (FM_ICON_VIEW (view)));
}

static void
fm_desktop_view_bump_zoom_level (FMDirectoryView *view, int zoom_increment)
{
    PeonyZoomLevel new_level;

    g_return_if_fail (FM_IS_ICON_VIEW (view));

    new_level = fm_desktop_view_get_zoom_level (view) + zoom_increment;

    if (new_level >= PEONY_ZOOM_LEVEL_SMALLEST &&
            new_level <= PEONY_ZOOM_LEVEL_LARGEST)
    {
        fm_directory_view_zoom_to_level (view, new_level);
    }
}
static gboolean
fm_desktop_view_can_zoom_in (FMDirectoryView *view)
{
    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);

    return fm_desktop_view_get_zoom_level (view)
           < PEONY_ZOOM_LEVEL_LARGEST;
}

static gboolean
fm_desktop_view_can_zoom_out (FMDirectoryView *view)
{
    g_return_val_if_fail (FM_IS_ICON_VIEW (view), FALSE);

    return fm_desktop_view_get_zoom_level (view)
           > PEONY_ZOOM_LEVEL_SMALLEST;
}

static void
fm_desktop_view_set_zoom_level (FMIconView *view,
                             PeonyZoomLevel new_level,
                             gboolean always_emit)
{
    PeonyIconContainer *icon_container;

    g_return_if_fail (FM_IS_ICON_VIEW (view));
    g_return_if_fail (new_level >= PEONY_ZOOM_LEVEL_SMALLEST &&
                      new_level <= PEONY_ZOOM_LEVEL_LARGEST);

    icon_container = get_icon_container (view);
    if (peony_icon_container_get_zoom_level (icon_container) == new_level)
    {
        if (always_emit)
        {
            g_signal_emit_by_name (view, "zoom_level_changed");
        }
        return;
    }

    peony_file_set_integer_metadata
    (fm_directory_view_get_directory_as_file (FM_DIRECTORY_VIEW (view)),
     PEONY_METADATA_KEY_ICON_VIEW_ZOOM_LEVEL,
     get_default_zoom_level (),
     new_level);

    peony_icon_container_set_zoom_level (icon_container, new_level,TRUE);

    g_signal_emit_by_name (view, "zoom_level_changed");

    if (fm_directory_view_get_active (FM_DIRECTORY_VIEW (view)))
    {
        fm_directory_view_update_menus (FM_DIRECTORY_VIEW (view));
    }
}

static void
fm_desktop_view_zoom_to_level (FMDirectoryView *view,
                            PeonyZoomLevel zoom_level)
{
    FMIconView *icon_view;

    g_assert (FM_IS_ICON_VIEW (view));

    icon_view = FM_ICON_VIEW (view);
    fm_desktop_view_set_zoom_level (icon_view, zoom_level, FALSE);
}

static void
fm_desktop_icon_view_class_init (FMDesktopIconViewClass *class)
{
    G_OBJECT_CLASS (class)->dispose = fm_desktop_icon_view_dispose;

    FM_DIRECTORY_VIEW_CLASS (class)->merge_menus = real_merge_menus;
    FM_DIRECTORY_VIEW_CLASS (class)->update_menus = real_update_menus;
    FM_DIRECTORY_VIEW_CLASS (class)->bump_zoom_level = fm_desktop_view_bump_zoom_level;
    FM_DIRECTORY_VIEW_CLASS (class)->can_zoom_in = fm_desktop_view_can_zoom_in;
    FM_DIRECTORY_VIEW_CLASS (class)->can_zoom_out = fm_desktop_view_can_zoom_out;
    FM_DIRECTORY_VIEW_CLASS (class)->zoom_to_level = fm_desktop_view_zoom_to_level;
    FM_DIRECTORY_VIEW_CLASS (class)->get_zoom_level = fm_desktop_view_get_zoom_level;
    FM_ICON_VIEW_CLASS (class)->supports_auto_layout = real_supports_auto_layout;
    FM_ICON_VIEW_CLASS (class)->supports_scaling = real_supports_scaling;
    FM_ICON_VIEW_CLASS (class)->supports_keep_aligned = real_supports_keep_aligned;
    FM_ICON_VIEW_CLASS (class)->supports_labels_beside_icons = real_supports_labels_beside_icons;

    g_type_class_add_private (class, sizeof (FMDesktopIconViewDetails));
}

static void
fm_desktop_icon_view_handle_middle_click (PeonyIconContainer *icon_container,
        GdkEventButton *event,
        FMDesktopIconView *desktop_icon_view)
{
    XButtonEvent x_event;
#if GTK_CHECK_VERSION (3, 20, 0)
    GdkDevice *keyboard = NULL, *pointer = NULL, *cur;
    GdkSeat *seat;

    seat = gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET (icon_container)));
    pointer = gdk_seat_get_pointer (seat);
    keyboard = gdk_seat_get_keyboard (seat);

    {
        if (pointer == NULL && (gdk_device_get_source (cur) == GDK_SOURCE_MOUSE)) {
                pointer = cur;
        }

        if (keyboard == NULL && (gdk_device_get_source (cur) == GDK_SOURCE_KEYBOARD)) {
                keyboard = cur;
        }
    }

    /* During a mouse click we have the pointer and keyboard grab.
     * We will send a fake event to the root window which will cause it
     * to try to get the grab so we need to let go ourselves.
     */

    if (pointer != NULL) {
            gdk_seat_ungrab (seat);
    }

    if (keyboard != NULL) {
            gdk_seat_ungrab (seat);
    }
#else
    GdkDevice *keyboard = NULL, *pointer = NULL, *cur;
    GdkDeviceManager *manager;
    GList *list, *l;

    manager = gdk_display_get_device_manager (gtk_widget_get_display (GTK_WIDGET (icon_container)));
    list = gdk_device_manager_list_devices (manager, GDK_DEVICE_TYPE_MASTER);

    for (l = list; l != NULL; l = l->next) {
            cur = l->data;

            if (pointer == NULL && (gdk_device_get_source (cur) == GDK_SOURCE_MOUSE)) {
                    pointer = cur;
            }

            if (keyboard == NULL && (gdk_device_get_source (cur) == GDK_SOURCE_KEYBOARD)) {
                    keyboard = cur;
            }

            if (pointer != NULL && keyboard != NULL) {
                    break;
            }
    }

    g_list_free (list);

    /* During a mouse click we have the pointer and keyboard grab.
     * We will send a fake event to the root window which will cause it
     * to try to get the grab so we need to let go ourselves.
     */

    if (pointer != NULL) {
            gdk_device_ungrab (pointer, GDK_CURRENT_TIME);
    }

    if (keyboard != NULL) {
            gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
    }
#endif

    /* Stop the event because we don't want anyone else dealing with it. */
    gdk_flush ();
    g_signal_stop_emission_by_name (icon_container, "middle_click");

    /* build an X event to represent the middle click. */
    x_event.type = ButtonPress;
    x_event.send_event = True;
    x_event.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
    x_event.window = GDK_ROOT_WINDOW ();
    x_event.root = GDK_ROOT_WINDOW ();
    x_event.subwindow = 0;
    x_event.time = event->time;
    x_event.x = event->x;
    x_event.y = event->y;
    x_event.x_root = event->x_root;
    x_event.y_root = event->y_root;
    x_event.state = event->state;
    x_event.button = event->button;
    x_event.same_screen = True;

    /* Send it to the root window, the window manager will handle it. */
    XSendEvent (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_ROOT_WINDOW (), True,
                ButtonPressMask, (XEvent *) &x_event);
}

static void
unrealized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view)
{
    g_return_if_fail (desktop_icon_view->details->root_window != NULL);

    /* Remove the property filter */
    gdk_window_remove_filter (desktop_icon_view->details->root_window,
                              desktop_icon_view_property_filter,
                              desktop_icon_view);
    desktop_icon_view->details->root_window = NULL;
}

static void
realized_callback (GtkWidget *widget, FMDesktopIconView *desktop_icon_view)
{
    GdkWindow *root_window;
    GdkScreen *screen;
    GtkAllocation allocation;

    g_return_if_fail (desktop_icon_view->details->root_window == NULL);

    screen = gtk_widget_get_screen (widget);

    /* Ugly HACK for the problem that the views realize at the
     * wrong size and then get resized. (This is a problem with
     * UkuiComponentPlug.) This was leading to problems where initial
     * layout was done at 60x60 stacking all desktop icons in
     * the top left corner.
     */
    allocation.x = 0;
    allocation.y = 0;
    allocation.width = gdk_screen_get_width (screen);
    allocation.height = gdk_screen_get_height (screen);
    gtk_widget_size_allocate (GTK_WIDGET(get_icon_container(desktop_icon_view)),
                              &allocation);

    root_window = gdk_screen_get_root_window (screen);

    desktop_icon_view->details->root_window = root_window;

    /* Read out the workarea geometry and update the icon container accordingly */
    net_workarea_changed (desktop_icon_view, root_window);

    /* Setup the property filter */
    gdk_window_set_events (root_window, GDK_PROPERTY_CHANGE_MASK);
    gdk_window_add_filter (root_window,
                           desktop_icon_view_property_filter,
                           desktop_icon_view);

}

static PeonyZoomLevel
get_default_zoom_level (void)
{
    static gboolean auto_storage_added = FALSE;
    static PeonyZoomLevel default_zoom_level = PEONY_ZOOM_LEVEL_STANDARD;

    if (!auto_storage_added)
    {
        auto_storage_added = TRUE;
        eel_g_settings_add_auto_enum (peony_icon_view_preferences,
                                      PEONY_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
                                      (int *) &default_zoom_level);
    }

    return CLAMP (default_zoom_level, PEONY_ZOOM_LEVEL_SMALLEST, PEONY_ZOOM_LEVEL_LARGEST);
}

static void
default_zoom_level_changed (gpointer user_data)
{
    PeonyZoomLevel new_level;
    FMDesktopIconView *desktop_icon_view;

    desktop_icon_view = FM_DESKTOP_ICON_VIEW (user_data);
    new_level = get_default_zoom_level ();

    peony_icon_container_set_zoom_level (get_icon_container (desktop_icon_view),
                                        new_level,FALSE);
}

static gboolean
do_desktop_rescan (gpointer data)
{
    FMDesktopIconView *desktop_icon_view;
    struct stat buf;

    desktop_icon_view = FM_DESKTOP_ICON_VIEW (data);
    if (desktop_icon_view->details->pending_rescan)
    {
        return TRUE;
    }

    if (stat (desktop_directory, &buf) == -1)
    {
        return TRUE;
    }

    if (buf.st_ctime == desktop_dir_modify_time)
    {
        return TRUE;
    }

    desktop_icon_view->details->pending_rescan = TRUE;

    peony_directory_force_reload (
        fm_directory_view_get_model (
            FM_DIRECTORY_VIEW (desktop_icon_view)));
    return TRUE;
}

static void
done_loading (PeonyDirectory *model,
	      FMDesktopIconView *desktop_icon_view)
{
    struct stat buf;

    desktop_icon_view->details->pending_rescan = FALSE;
    if (stat (desktop_directory, &buf) == -1)
    {
        return;
    }

    desktop_dir_modify_time = buf.st_ctime;
}

/* This function is used because the PeonyDirectory model does not
 * exist always in the desktop_icon_view, so we wait until it has been
 * instantiated.
 */
static void
delayed_init (FMDesktopIconView *desktop_icon_view)
{
    /* Keep track of the load time. */
    g_signal_connect_object (fm_directory_view_get_model (FM_DIRECTORY_VIEW (desktop_icon_view)),
                             "done_loading",
                             G_CALLBACK (done_loading), desktop_icon_view, 0);

    /* Monitor desktop directory. */
    desktop_icon_view->details->reload_desktop_timeout =
        g_timeout_add_seconds (RESCAN_TIMEOUT, do_desktop_rescan, desktop_icon_view);

    g_signal_handler_disconnect (desktop_icon_view,
                                 desktop_icon_view->details->delayed_init_signal);

    desktop_icon_view->details->delayed_init_signal = 0;
}

static void
font_changed_callback (gpointer callback_data)
{
    g_return_if_fail (FM_IS_DESKTOP_ICON_VIEW (callback_data));

    fm_desktop_icon_view_update_icon_container_fonts (FM_DESKTOP_ICON_VIEW (callback_data));
}

static void
fm_desktop_icon_view_update_icon_container_fonts (FMDesktopIconView *icon_view)
{
    PeonyIconContainer *icon_container;
    char *font;

    icon_container = get_icon_container (icon_view);
    g_assert (icon_container != NULL);

    font = g_settings_get_string (peony_desktop_preferences, PEONY_PREFERENCES_DESKTOP_FONT);

    peony_icon_container_set_font (icon_container, font);

    g_free (font);
}

static void
fm_desktop_icon_view_init (FMDesktopIconView *desktop_icon_view)
{
    PeonyIconContainer *icon_container;
    GtkAllocation allocation;
    GtkAdjustment *hadj, *vadj;

    desktop_icon_view->details = G_TYPE_INSTANCE_GET_PRIVATE (desktop_icon_view,
    							      FM_TYPE_DESKTOP_ICON_VIEW,
    							      FMDesktopIconViewDetails);

    if (desktop_directory == NULL)
    {
        g_signal_connect_swapped (peony_preferences, "changed::" PEONY_PREFERENCES_DESKTOP_IS_HOME_DIR,
                                  G_CALLBACK(desktop_directory_changed_callback),
                                  NULL);
        desktop_directory_changed_callback (NULL);
    }

    fm_icon_view_filter_by_screen (FM_ICON_VIEW (desktop_icon_view), TRUE);
    icon_container = get_icon_container (desktop_icon_view);
    peony_icon_container_set_use_drop_shadows (icon_container, TRUE);
    fm_icon_container_set_sort_desktop (FM_ICON_CONTAINER (icon_container), TRUE);

    /* Do a reload on the desktop if we don't have FAM, a smarter
     * way to keep track of the items on the desktop.
     */
    if (!peony_monitor_active ())
    {
        desktop_icon_view->details->delayed_init_signal = g_signal_connect_object
                (desktop_icon_view, "begin_loading",
                 G_CALLBACK (delayed_init), desktop_icon_view, 0);
    }

    peony_icon_container_set_is_fixed_size (icon_container, TRUE);
    peony_icon_container_set_is_desktop (icon_container, TRUE);
    peony_icon_container_set_store_layout_timestamps (icon_container, TRUE);

    /* Set allocation to be at 0, 0 */
    gtk_widget_get_allocation (GTK_WIDGET (icon_container), &allocation);
    allocation.x = 0;
    allocation.y = 0;
    gtk_widget_set_allocation (GTK_WIDGET (icon_container), &allocation);

    gtk_widget_queue_resize (GTK_WIDGET (icon_container));

    hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (icon_container));
    vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (icon_container));

    gtk_adjustment_set_value (hadj, 0);
    gtk_adjustment_set_value (vadj, 0);

    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (desktop_icon_view),
                                         GTK_SHADOW_NONE);

    fm_directory_view_ignore_hidden_file_preferences
    (FM_DIRECTORY_VIEW (desktop_icon_view));

    fm_directory_view_set_show_foreign (FM_DIRECTORY_VIEW (desktop_icon_view),
                                        FALSE);

    /* Set our default layout mode */
    peony_icon_container_set_layout_mode (icon_container,
                                         gtk_widget_get_direction (GTK_WIDGET(icon_container)) == GTK_TEXT_DIR_RTL ?
                                         PEONY_ICON_LAYOUT_T_B_R_L :
                                         PEONY_ICON_LAYOUT_T_B_L_R);

    g_signal_connect_object (icon_container, "middle_click",
                             G_CALLBACK (fm_desktop_icon_view_handle_middle_click), desktop_icon_view, 0);
    g_signal_connect_object (desktop_icon_view, "realize",
                             G_CALLBACK (realized_callback), desktop_icon_view, 0);
    g_signal_connect_object (desktop_icon_view, "unrealize",
                             G_CALLBACK (unrealized_callback), desktop_icon_view, 0);

    default_zoom_level_changed (desktop_icon_view);

    g_signal_connect_swapped (peony_icon_view_preferences,
                              "changed::" PEONY_PREFERENCES_ICON_VIEW_DEFAULT_ZOOM_LEVEL,
                              G_CALLBACK (default_zoom_level_changed),
                              desktop_icon_view);

    g_signal_connect_swapped (peony_desktop_preferences,
                              "changed::" PEONY_PREFERENCES_DESKTOP_FONT,
                              G_CALLBACK (font_changed_callback),
                              desktop_icon_view);

    fm_desktop_icon_view_update_icon_container_fonts (desktop_icon_view);

    g_signal_connect_swapped (ukui_lockdown_preferences,
                              "changed::" PEONY_PREFERENCES_LOCKDOWN_COMMAND_LINE,
                              G_CALLBACK (fm_directory_view_update_menus),
                              desktop_icon_view);

}

static void
action_new_launcher_callback (GtkAction *action, gpointer data)
{
    char *desktop_directory;

    g_assert (FM_DIRECTORY_VIEW (data));

    desktop_directory = peony_get_desktop_directory ();

    peony_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)),
                                          "mate-desktop-item-edit",
                                          "mate-desktop-item-edit",
                                          FALSE,
                                          "--create-new", desktop_directory, NULL);
    g_free (desktop_directory);

}

static void
action_change_background_callback (GtkAction *action,
                                   gpointer data)
{
    g_assert (FM_DIRECTORY_VIEW (data));

    peony_launch_application_from_command (gtk_widget_get_screen (GTK_WIDGET (data)),
                                          _("Background"),
                                          "ukui-control-center",
                                          FALSE,
                                          "-a", NULL);
}

static void
action_empty_trash_conditional_callback (GtkAction *action,
        gpointer data)
{
    g_assert (FM_IS_DIRECTORY_VIEW (data));

    peony_file_operations_empty_trash (GTK_WIDGET (data));
}

static gboolean
trash_link_is_selection (FMDirectoryView *view)
{
    GList *selection;
    PeonyDesktopLink *link;
    gboolean result;

    result = FALSE;

    selection = fm_directory_view_get_selection (view);

    if (eel_g_list_exactly_one_item (selection) &&
            PEONY_IS_DESKTOP_ICON_FILE (selection->data))
    {
        link = peony_desktop_icon_file_get_link (PEONY_DESKTOP_ICON_FILE (selection->data));
        /* link may be NULL if the link was recently removed (unmounted) */
        if (link != NULL &&
                peony_desktop_link_get_link_type (link) == PEONY_DESKTOP_LINK_TRASH)
        {
            result = TRUE;
        }
        if (link)
        {
            g_object_unref (link);
        }
    }

    peony_file_list_free (selection);

    return result;
}

static void
real_update_menus (FMDirectoryView *view)
{
    FMDesktopIconView *desktop_view;
    char *label;
    gboolean disable_command_line;
    gboolean include_empty_trash;
    GtkAction *action;

    g_assert (FM_IS_DESKTOP_ICON_VIEW (view));

    FM_DIRECTORY_VIEW_CLASS (fm_desktop_icon_view_parent_class)->update_menus (view);

    desktop_view = FM_DESKTOP_ICON_VIEW (view);

    /* New Launcher */
    disable_command_line = g_settings_get_boolean (ukui_lockdown_preferences, PEONY_PREFERENCES_LOCKDOWN_COMMAND_LINE);
    action = gtk_action_group_get_action (desktop_view->details->desktop_action_group,
                                          FM_ACTION_NEW_LAUNCHER_DESKTOP);
    gtk_action_set_visible (action,
                            !disable_command_line);

    /* Empty Trash */
    include_empty_trash = trash_link_is_selection (view);
    action = gtk_action_group_get_action (desktop_view->details->desktop_action_group,
                                          FM_ACTION_EMPTY_TRASH_CONDITIONAL);
    gtk_action_set_visible (action,
                            include_empty_trash);
    if (include_empty_trash)
    {
        label = g_strdup (_("E_mpty Trash"));
        g_object_set (action , "label", label, NULL);
        gtk_action_set_sensitive (action,
                                  !peony_trash_monitor_is_empty ());
        g_free (label);
    }
}

static const GtkActionEntry desktop_view_entries[] =
{
    /* name, stock id */
    {
        "New Launcher Desktop", NULL,
        /* label, accelerator */
        N_("Create L_auncher..."), NULL,
        /* tooltip */
        N_("Create a new launcher"),
        G_CALLBACK (action_new_launcher_callback)
    },
    /* name, stock id */
    {
        "Change Background", NULL,
        /* label, accelerator */
        N_("Change Desktop _Background"), NULL,
        /* tooltip */
        N_("Show a window that lets you set your desktop background's pattern or color"),
        G_CALLBACK (action_change_background_callback)
    },
    /* name, stock id */
    {
        "Empty Trash Conditional", NULL,
        /* label, accelerator */
        N_("Empty Trash"), NULL,
        /* tooltip */
        N_("Delete all items in the Trash"),
        G_CALLBACK (action_empty_trash_conditional_callback)
    },
};

static void
real_merge_menus (FMDirectoryView *view)
{
    FMDesktopIconView *desktop_view;
    GtkUIManager *ui_manager;
    GtkActionGroup *action_group;
    const char *ui;

    FM_DIRECTORY_VIEW_CLASS (fm_desktop_icon_view_parent_class)->merge_menus (view);

    desktop_view = FM_DESKTOP_ICON_VIEW (view);

    ui_manager = fm_directory_view_get_ui_manager (view);

    action_group = gtk_action_group_new ("DesktopViewActions");
    gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
    desktop_view->details->desktop_action_group = action_group;
    gtk_action_group_add_actions (action_group,
                                  desktop_view_entries, G_N_ELEMENTS (desktop_view_entries),
                                  view);

    gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
    g_object_unref (action_group); /* owned by ui manager */

    ui = peony_ui_string_get ("peony-desktop-icon-view-ui.xml");
    desktop_view->details->desktop_merge_id =
        gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, NULL);
}

static gboolean
real_supports_auto_layout (FMIconView *view)
{
    /* Can't use auto-layout on the desktop, because doing so
     * would cause all sorts of complications involving the
     * fixed-size window.
     */
    return FALSE;
}

static gboolean
real_supports_scaling (FMIconView *view)
{
    return TRUE;
}

static gboolean
real_supports_keep_aligned (FMIconView *view)
{
    return TRUE;
}

static gboolean
real_supports_labels_beside_icons (FMIconView *view)
{
    return FALSE;
}

static gboolean
real_supports_zooming (FMDirectoryView *view)
{
    /* Can't zoom on the desktop, because doing so would cause all
     * sorts of complications involving the fixed-size window.
     */
    return FALSE;
}

static PeonyView *
fm_desktop_icon_view_create (PeonyWindowSlotInfo *slot)
{
    FMIconView *view;

    view = g_object_new (FM_TYPE_DESKTOP_ICON_VIEW,
                         "window-slot", slot,
                         NULL);
    return PEONY_VIEW (view);
}

static gboolean
fm_desktop_icon_view_supports_uri (const char *uri,
                                   GFileType file_type,
                                   const char *mime_type)
{
    if (g_str_has_prefix (uri, EEL_DESKTOP_URI))
    {
        return TRUE;
    }

    return FALSE;
}

static PeonyViewInfo fm_desktop_icon_view =
{
    FM_DESKTOP_ICON_VIEW_ID,
    "Desktop View",
    "_Desktop",
    N_("The desktop view encountered an error."),
    N_("The desktop view encountered an error while starting up."),
    "Display this location with the desktop view.",
    fm_desktop_icon_view_create,
    fm_desktop_icon_view_supports_uri
};

void
fm_desktop_icon_view_register (void)
{
    fm_desktop_icon_view.error_label = _(fm_desktop_icon_view.error_label);
    fm_desktop_icon_view.startup_error_label = _(fm_desktop_icon_view.startup_error_label);

    peony_view_factory_register (&fm_desktop_icon_view);
}
