summaryrefslogtreecommitdiffstats
path: root/src/nautilus-dnd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nautilus-dnd.c')
-rw-r--r--src/nautilus-dnd.c317
1 files changed, 317 insertions, 0 deletions
diff --git a/src/nautilus-dnd.c b/src/nautilus-dnd.c
new file mode 100644
index 0000000..9663e01
--- /dev/null
+++ b/src/nautilus-dnd.c
@@ -0,0 +1,317 @@
+/* nautilus-dnd.h - Common Drag & drop handling code
+ *
+ * Authors: Pavel Cisler <pavel@eazel.com>,
+ * Ettore Perazzoli <ettore@gnu.org>
+ * Copyright (C) 2000, 2001 Eazel, Inc.
+ * Copyright (C) 2022 The GNOME project contributors
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "nautilus-directory.h"
+#include "nautilus-dnd.h"
+#include "nautilus-file-utilities.h"
+#include "nautilus-tag-manager.h"
+
+static gboolean
+check_same_fs (NautilusFile *file1,
+ NautilusFile *file2)
+{
+ char *id1, *id2;
+ gboolean result;
+
+ result = FALSE;
+
+ if (file1 != NULL && file2 != NULL)
+ {
+ id1 = nautilus_file_get_filesystem_id (file1);
+ id2 = nautilus_file_get_filesystem_id (file2);
+
+ if (id1 != NULL && id2 != NULL)
+ {
+ result = (strcmp (id1, id2) == 0);
+ }
+
+ g_free (id1);
+ g_free (id2);
+ }
+
+ return result;
+}
+
+static gboolean
+source_is_deletable (GFile *file)
+{
+ NautilusFile *naut_file;
+ gboolean ret;
+
+ /* if there's no a cached NautilusFile, it returns NULL */
+ naut_file = nautilus_file_get (file);
+ if (naut_file == NULL)
+ {
+ return FALSE;
+ }
+
+ ret = nautilus_file_can_delete (naut_file);
+ nautilus_file_unref (naut_file);
+
+ return ret;
+}
+
+#if 0 && NAUTILUS_DND_NEEDS_GTK4_REIMPLEMENTATION
+static void
+append_drop_action_menu_item (GtkWidget *menu,
+ const char *text,
+ GdkDragAction action,
+ gboolean sensitive,
+ DropActionMenuData *damd)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_button_new_with_mnemonic (text);
+ gtk_widget_set_sensitive (menu_item, sensitive);
+ gtk_box_append (GTK_BOX (menu), menu_item);
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (menu_item), "flat");
+
+ g_object_set_data (G_OBJECT (menu_item),
+ "action",
+ GINT_TO_POINTER (action));
+
+ g_signal_connect (menu_item, "clicked",
+ G_CALLBACK (drop_action_activated_callback),
+ damd);
+
+ gtk_widget_show (menu_item);
+}
+#endif
+/* Pops up a menu of actions to perform on dropped files */
+GdkDragAction
+nautilus_drag_drop_action_ask (GtkWidget *widget,
+ GdkDragAction actions)
+{
+#if 0
+ GtkWidget *popover;
+ GtkWidget *menu;
+ GtkWidget *menu_item;
+ DropActionMenuData damd;
+
+ /* Create the menu and set the sensitivity of the items based on the
+ * allowed actions.
+ */
+ popover = gtk_popover_new (widget);
+ gtk_popover_set_position (GTK_POPOVER (popover), GTK_POS_TOP);
+
+ menu = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_margin_top (menu, 6);
+ gtk_widget_set_margin_bottom (menu, 6);
+ gtk_widget_set_margin_start (menu, 6);
+ gtk_widget_set_margin_end (menu, 6);
+ gtk_popover_set_child (GTK_POPOVER (popover), menu);
+ gtk_widget_show (menu);
+
+ append_drop_action_menu_item (menu, _("_Move Here"),
+ GDK_ACTION_MOVE,
+ (actions & GDK_ACTION_MOVE) != 0,
+ &damd);
+
+ append_drop_action_menu_item (menu, _("_Copy Here"),
+ GDK_ACTION_COPY,
+ (actions & GDK_ACTION_COPY) != 0,
+ &damd);
+
+ append_drop_action_menu_item (menu, _("_Link Here"),
+ GDK_ACTION_LINK,
+ (actions & GDK_ACTION_LINK) != 0,
+ &damd);
+
+ menu_item = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+ gtk_box_append (GTK_BOX (menu), menu_item);
+ gtk_widget_show (menu_item);
+
+ append_drop_action_menu_item (menu, _("Cancel"), 0, TRUE, &damd);
+
+ damd.chosen = 0;
+ damd.loop = g_main_loop_new (NULL, FALSE);
+
+ g_signal_connect (popover, "closed",
+ G_CALLBACK (menu_deactivate_callback),
+ &damd);
+
+ gtk_grab_add (popover);
+
+ /* We don't have pointer coords here. Just pick the center of the widget. */
+ gtk_popover_set_pointing_to (GTK_POPOVER (popover),
+ &(GdkRectangle){ .x = 0.5 * gtk_widget_get_allocated_width (widget),
+ .y = 0.5 * gtk_widget_get_allocated_height (widget),
+ .width = 0, .height = 0 });
+
+ gtk_popover_popup (GTK_POPOVER (popover));
+
+ g_main_loop_run (damd.loop);
+
+ gtk_grab_remove (popover);
+
+ g_main_loop_unref (damd.loop);
+
+ g_object_ref_sink (popover);
+ g_object_unref (popover);
+
+ return damd.chosen;
+#endif
+ return 0;
+}
+
+GdkDragAction
+nautilus_dnd_get_preferred_action (NautilusFile *target_file,
+ GFile *dropped)
+{
+ g_autoptr (NautilusDirectory) directory = NULL;
+ g_autoptr (GFile) target_location = NULL;
+ g_autoptr (NautilusFile) dropped_file = NULL;
+ gboolean same_fs;
+ gboolean source_deletable;
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (target_file), 0);
+ g_return_val_if_fail (dropped == NULL || G_IS_FILE (dropped), 0);
+
+ target_location = nautilus_file_get_location (target_file);
+ if (g_file_equal (target_location, dropped))
+ {
+ return 0;
+ }
+
+ /* First check target imperatives */
+ directory = nautilus_directory_get_for_file (target_file);
+
+ if (nautilus_is_file_roller_installed () &&
+ nautilus_file_is_archive (target_file))
+ {
+ return GDK_ACTION_COPY;
+ }
+ else if (nautilus_file_is_starred_location (target_file))
+ {
+ if (nautilus_tag_manager_can_star_contents (nautilus_tag_manager_get (), dropped))
+ {
+ return GDK_ACTION_COPY;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else if (!nautilus_file_is_directory (target_file) ||
+ !nautilus_file_can_write (target_file) ||
+ !nautilus_directory_is_editable (directory))
+ {
+ /* No other file type other than archives and directories currently
+ * accepts drops */
+ return 0;
+ }
+ else if (nautilus_file_is_in_trash (target_file))
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ if (g_file_has_uri_scheme (dropped, "trash"))
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ dropped_file = nautilus_file_get (dropped);
+ same_fs = check_same_fs (target_file, dropped_file);
+ source_deletable = source_is_deletable (dropped);
+ if (same_fs && source_deletable)
+ {
+ return GDK_ACTION_MOVE;
+ }
+
+ return GDK_ACTION_COPY;
+}
+
+#define MAX_DRAWN_DRAG_ICONS 10
+
+GdkPaintable *
+get_paintable_for_drag_selection (GList *selection,
+ int scale)
+{
+ g_autoqueue (GdkPaintable) icons = g_queue_new ();
+ g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new ();
+ NautilusFileIconFlags flags;
+ GdkPaintable *icon;
+ guint n_icons;
+ guint icon_size = NAUTILUS_DRAG_SURFACE_ICON_SIZE;
+ float dx;
+ float dy;
+ /* A wide shadow for the pile of icons gives a sense of floating. */
+ GskShadow stack_shadow = {.color = {0, 0, 0, .alpha = 0.15}, .dx = 0, .dy = 2, .radius = 10 };
+ /* A slight shadow swhich makes each icon in the stack look separate. */
+ GskShadow icon_shadow = {.color = {0, 0, 0, .alpha = 0.30}, .dx = 0, .dy = 1, .radius = 1 };
+
+ g_return_val_if_fail (NAUTILUS_IS_FILE (selection->data), NULL);
+
+ /* The selection list is reversed compared to what the user sees. Get the
+ * first items by starting from the end of the list. */
+ flags = NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS;
+ for (GList *l = g_list_last (selection);
+ l != NULL && g_queue_get_length (icons) <= MAX_DRAWN_DRAG_ICONS;
+ l = l->prev)
+ {
+ icon = nautilus_file_get_icon_paintable (l->data, icon_size, scale, flags);
+ g_queue_push_tail (icons, icon);
+ }
+
+ /* When there are 2 or 3 identical icons, we need to space them more,
+ * otherwise it would be hard to tell there is more than one icon at all.
+ * The more icons we have, the easier it is to notice multiple icons are
+ * stacked, and the more compact we want to be.
+ *
+ * 1 icon 2 icons 3 icons 4+ icons
+ * .--------. .--------. .--------. .--------.
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * '--------' |--------| |--------| |--------|
+ * | | | | |--------|
+ * | | |--------| |--------|
+ * '--------' | | |--------|
+ * '--------' '--------'
+ */
+ n_icons = g_queue_get_length (icons);
+ dx = (n_icons % 2 == 1) ? 6 : -6;
+ dy = (n_icons == 2) ? 10 : (n_icons == 3) ? 6 : (n_icons >= 4) ? 4 : 0;
+
+ /* We want the first icon on top of every other. So we need to start drawing
+ * the stack from the bottom, that is, from the last icon. This requires us
+ * to jump to the last position and then move upwards one step at a time.
+ * Also, add 10px horizontal offset, for shadow, to workaround this GTK bug:
+ * https://gitlab.gnome.org/GNOME/gtk/-/issues/2341
+ */
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (10 + (dx / 2), dy * n_icons));
+ gtk_snapshot_push_shadow (snapshot, &stack_shadow, 1);
+ for (GList *l = g_queue_peek_tail_link (icons); l != NULL; l = l->prev)
+ {
+ double w = gdk_paintable_get_intrinsic_width (l->data);
+ double h = gdk_paintable_get_intrinsic_height (l->data);
+ /* Offsets needed to center thumbnails. Floored to keep images sharp. */
+ float x = floor ((icon_size - w) / 2);
+ float y = floor ((icon_size - h) / 2);
+
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-dx, -dy));
+
+ /* Alternate horizontal offset direction to give a rough pile look. */
+ dx = -dx;
+
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
+ gtk_snapshot_push_shadow (snapshot, &icon_shadow, 1);
+
+ gdk_paintable_snapshot (l->data, snapshot, w, h);
+
+ gtk_snapshot_pop (snapshot); /* End of icon shadow */
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (-x, -y));
+ }
+ gtk_snapshot_pop (snapshot); /* End of stack shadow */
+
+ return gtk_snapshot_to_paintable (snapshot, NULL);
+}