Tree of tag thunar-4.20.2 (commit 11a519f) from https://gitlab.xfce.org/xfce/thunar. Upstream history is not included here to keep the repository small.
1046 lines
35 KiB
C
1046 lines
35 KiB
C
/* vi:set et ai sw=2 sts=2 ts=2: */
|
|
/*-
|
|
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
|
|
* Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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., 59 Temple
|
|
* Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_MEMORY_H
|
|
#include <memory.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "thunar/thunar-application.h"
|
|
#include "thunar/thunar-dnd.h"
|
|
#include "thunar/thunar-gio-extensions.h"
|
|
#include "thunar/thunar-icon-factory.h"
|
|
#include "thunar/thunar-location-button.h"
|
|
#include "thunar/thunar-pango-extensions.h"
|
|
#include "thunar/thunar-preferences.h"
|
|
#include "thunar/thunar-private.h"
|
|
|
|
#include <libxfce4util/libxfce4util.h>
|
|
|
|
|
|
|
|
/* Property identifiers */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_FILE,
|
|
PROP_MAX_WIDTH,
|
|
};
|
|
|
|
/* Signal identifiers */
|
|
enum
|
|
{
|
|
CLICKED,
|
|
GONE,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_finalize (GObject *object);
|
|
static void
|
|
thunar_location_button_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void
|
|
thunar_location_button_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static GdkDragAction
|
|
thunar_location_button_get_dest_actions (ThunarLocationButton *location_button,
|
|
GdkDragContext *context,
|
|
GtkWidget *button,
|
|
guint timestamp);
|
|
static void
|
|
thunar_location_button_active_changed (ThunarLocationButton *location_button);
|
|
static void
|
|
thunar_location_button_file_changed (ThunarLocationButton *location_button,
|
|
ThunarFile *file);
|
|
static void
|
|
thunar_location_button_file_destroy (ThunarLocationButton *location_button,
|
|
ThunarFile *file);
|
|
static void
|
|
thunar_location_button_apply_label_size (ThunarLocationButton *location_button);
|
|
static gboolean
|
|
thunar_location_button_button_press_event (GtkWidget *button,
|
|
GdkEventButton *event);
|
|
static gboolean
|
|
thunar_location_button_button_release_event (GtkWidget *button,
|
|
GdkEventButton *event);
|
|
static void
|
|
thunar_location_button_map (GtkWidget *button);
|
|
static void
|
|
thunar_location_button_style_updated (GtkWidget *button);
|
|
static gboolean
|
|
thunar_location_button_drag_drop (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp);
|
|
static void
|
|
thunar_location_button_drag_data_get (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp);
|
|
static void
|
|
thunar_location_button_drag_data_received (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp);
|
|
static void
|
|
thunar_location_button_drag_leave (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
guint timestamp);
|
|
static gboolean
|
|
thunar_location_button_drag_motion (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp);
|
|
static gboolean
|
|
thunar_location_button_enter_timeout (gpointer user_data);
|
|
static void
|
|
thunar_location_button_enter_timeout_destroy (gpointer user_data);
|
|
static void
|
|
thunar_location_button_clicked (ThunarLocationButton *button);
|
|
|
|
|
|
|
|
struct _ThunarLocationButtonClass
|
|
{
|
|
GtkToggleButtonClass __parent__;
|
|
};
|
|
|
|
struct _ThunarLocationButton
|
|
{
|
|
GtkToggleButton __parent__;
|
|
|
|
GtkWidget *image;
|
|
GtkWidget *label;
|
|
GtkWidget *bold_label;
|
|
|
|
/* the current icon state (i.e. accepting drops) */
|
|
ThunarFileIconState file_icon_state;
|
|
|
|
/* enter folders using DnD */
|
|
guint enter_timeout_id;
|
|
|
|
/* drop support for the button */
|
|
GList *drop_file_list;
|
|
guint drop_data_ready : 1;
|
|
guint drop_occurred : 1;
|
|
|
|
/* public properties */
|
|
ThunarFile *file;
|
|
gint max_width;
|
|
};
|
|
|
|
|
|
|
|
static const GtkTargetEntry drag_targets[] = {
|
|
{ "text/uri-list", 0, 0 },
|
|
};
|
|
|
|
static guint location_button_signals[LAST_SIGNAL];
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (ThunarLocationButton, thunar_location_button, GTK_TYPE_TOGGLE_BUTTON)
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_class_init (ThunarLocationButtonClass *klass)
|
|
{
|
|
GtkWidgetClass *gtkwidget_class;
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->finalize = thunar_location_button_finalize;
|
|
gobject_class->get_property = thunar_location_button_get_property;
|
|
gobject_class->set_property = thunar_location_button_set_property;
|
|
|
|
gtkwidget_class = GTK_WIDGET_CLASS (klass);
|
|
gtkwidget_class->map = thunar_location_button_map;
|
|
gtkwidget_class->style_updated = thunar_location_button_style_updated;
|
|
|
|
/**
|
|
* ThunarLocationButton:file:
|
|
*
|
|
* The #ThunarFile represented by this location button.
|
|
**/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_FILE,
|
|
g_param_spec_object ("file",
|
|
"file",
|
|
"file",
|
|
THUNAR_TYPE_FILE,
|
|
EXO_PARAM_READWRITE));
|
|
|
|
/**
|
|
* ThunarLocationButton:max-width:
|
|
*
|
|
* The desired maximum width of the button, in pixels. If this property
|
|
* is set to -1, the width will be calculated automatically.
|
|
**/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_MAX_WIDTH,
|
|
g_param_spec_int ("max-width",
|
|
"max-width",
|
|
"max-width",
|
|
-1, G_MAXINT,
|
|
-1,
|
|
EXO_PARAM_READWRITE));
|
|
|
|
/**
|
|
* ThunarLocationButton::location-button-clicked:
|
|
* @location_button : a #ThunarLocationButton.
|
|
*
|
|
* Emitted by @location_button when the user clicks on the
|
|
* @location_button or gtk_button_clicked() is called.
|
|
**/
|
|
location_button_signals[CLICKED] =
|
|
g_signal_new (I_ ("location-button-clicked"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL,
|
|
g_cclosure_marshal_VOID__BOOLEAN,
|
|
G_TYPE_NONE, 1,
|
|
G_TYPE_BOOLEAN);
|
|
|
|
/**
|
|
* ThunarLocationButton::gone:
|
|
* @location_button : a #ThunarLocationButton.
|
|
*
|
|
* Emitted by @location_button when the file associated with
|
|
* the button is deleted.
|
|
**/
|
|
location_button_signals[GONE] =
|
|
g_signal_new (I_ ("gone"),
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_init (ThunarLocationButton *button)
|
|
{
|
|
GtkWidget *hbox;
|
|
|
|
/* initialize the toggle button */
|
|
g_signal_connect (button, "notify::active", G_CALLBACK (thunar_location_button_active_changed), NULL);
|
|
g_signal_connect (button, "clicked", G_CALLBACK (thunar_location_button_clicked), NULL);
|
|
|
|
/* setup drag support for the button */
|
|
gtk_drag_source_set (GTK_WIDGET (button), GDK_BUTTON1_MASK, drag_targets, G_N_ELEMENTS (drag_targets), GDK_ACTION_LINK);
|
|
gtk_drag_dest_set (GTK_WIDGET (button), GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_MOTION, drag_targets,
|
|
G_N_ELEMENTS (drag_targets), GDK_ACTION_ASK | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_MOVE);
|
|
g_signal_connect (G_OBJECT (button), "button-press-event", G_CALLBACK (thunar_location_button_button_press_event), NULL);
|
|
g_signal_connect (G_OBJECT (button), "button-release-event", G_CALLBACK (thunar_location_button_button_release_event), NULL);
|
|
g_signal_connect (G_OBJECT (button), "drag-drop", G_CALLBACK (thunar_location_button_drag_drop), NULL);
|
|
g_signal_connect (G_OBJECT (button), "drag-data-get", G_CALLBACK (thunar_location_button_drag_data_get), NULL);
|
|
g_signal_connect (G_OBJECT (button), "drag-data-received", G_CALLBACK (thunar_location_button_drag_data_received), NULL);
|
|
g_signal_connect (G_OBJECT (button), "drag-leave", G_CALLBACK (thunar_location_button_drag_leave), NULL);
|
|
g_signal_connect (G_OBJECT (button), "drag-motion", G_CALLBACK (thunar_location_button_drag_motion), NULL);
|
|
|
|
/* create the horizontal box */
|
|
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
|
|
gtk_container_add (GTK_CONTAINER (button), hbox);
|
|
gtk_widget_show (hbox);
|
|
|
|
/* create the button image */
|
|
button->image = gtk_image_new ();
|
|
gtk_box_pack_start (GTK_BOX (hbox), button->image, TRUE, FALSE, 0);
|
|
gtk_widget_show (button->image);
|
|
|
|
/* create the button label */
|
|
button->label = g_object_new (GTK_TYPE_LABEL, "xalign", 0.5f, "yalign", 0.5f, NULL);
|
|
gtk_box_pack_start (GTK_BOX (hbox), button->label, TRUE, TRUE, 0);
|
|
gtk_widget_show (button->label);
|
|
|
|
/* create the bold label */
|
|
button->bold_label = g_object_new (GTK_TYPE_LABEL, "xalign", 0.5f, "yalign", 0.5f, NULL);
|
|
gtk_box_pack_start (GTK_BOX (hbox), button->bold_label, TRUE, TRUE, 0);
|
|
gtk_label_set_attributes (GTK_LABEL (button->bold_label), thunar_pango_attr_list_bold ());
|
|
/* but don't show it, as it is only a fake to retrieve the bold size */
|
|
|
|
/* add widget to css class which matches all buttons in the path-bar */
|
|
/* keep as well the old name 'path-bar-button'. It might be used by themes */
|
|
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (button)), "location-button");
|
|
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (button)), "path-bar-button");
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_finalize (GObject *object)
|
|
{
|
|
ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);
|
|
|
|
/* release the drop path list (just in case the drag-leave wasn't fired before) */
|
|
thunar_g_list_free_full (location_button->drop_file_list);
|
|
|
|
/* be sure to cancel any pending enter timeout */
|
|
if (G_UNLIKELY (location_button->enter_timeout_id != 0))
|
|
g_source_remove (location_button->enter_timeout_id);
|
|
|
|
/* disconnect from the file */
|
|
thunar_location_button_set_file (location_button, NULL);
|
|
|
|
(*G_OBJECT_CLASS (thunar_location_button_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FILE:
|
|
g_value_set_object (value, thunar_location_button_get_file (location_button));
|
|
break;
|
|
|
|
case PROP_MAX_WIDTH:
|
|
g_value_set_int (value, thunar_location_button_get_max_width (location_button));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FILE:
|
|
thunar_location_button_set_file (location_button, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_MAX_WIDTH:
|
|
thunar_location_button_set_max_width (location_button, g_value_get_int (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_map (GtkWidget *button)
|
|
{
|
|
GTK_WIDGET_CLASS (thunar_location_button_parent_class)->map (button);
|
|
|
|
thunar_location_button_apply_label_size (THUNAR_LOCATION_BUTTON (button));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_style_updated (GtkWidget *button)
|
|
{
|
|
GTK_WIDGET_CLASS (thunar_location_button_parent_class)->style_updated (button);
|
|
|
|
thunar_location_button_apply_label_size (THUNAR_LOCATION_BUTTON (button));
|
|
}
|
|
|
|
|
|
|
|
static GdkDragAction
|
|
thunar_location_button_get_dest_actions (ThunarLocationButton *location_button,
|
|
GdkDragContext *context,
|
|
GtkWidget *button,
|
|
guint timestamp)
|
|
{
|
|
GdkDragAction actions = 0;
|
|
GdkDragAction action = 0;
|
|
|
|
/* check if we can drop here */
|
|
if (G_LIKELY (location_button->file != NULL))
|
|
{
|
|
/* determine the possible drop actions for the file (and the suggested action if any) */
|
|
actions = thunar_file_accepts_drop (location_button->file, location_button->drop_file_list, context, &action);
|
|
}
|
|
|
|
/* tell Gdk whether we can drop here */
|
|
gdk_drag_status (context, action, timestamp);
|
|
|
|
return actions;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_file_changed (ThunarLocationButton *location_button,
|
|
ThunarFile *file)
|
|
{
|
|
ThunarPreferences *preferences;
|
|
GtkIconTheme *icon_theme;
|
|
gchar *icon_name;
|
|
const gchar *dnd_icon_name;
|
|
gboolean use_symbolic_icons;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
_thunar_return_if_fail (location_button->file == file);
|
|
_thunar_return_if_fail (THUNAR_IS_FILE (file));
|
|
|
|
/* Retrieve the icon theme */
|
|
icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (location_button)));
|
|
/* TODO: listen for icon theme changes */
|
|
|
|
/* update and show the label widget (hide for the local root folder) */
|
|
if (thunar_file_is_local (file) && thunar_file_is_root (file))
|
|
{
|
|
/* hide the label would otherwise show up */
|
|
gtk_widget_hide (location_button->label);
|
|
}
|
|
else
|
|
{
|
|
const gchar *diplay_name;
|
|
|
|
/* set label to the file's display name and show the label */
|
|
diplay_name = thunar_file_get_display_name (file);
|
|
if (diplay_name != NULL)
|
|
gtk_label_set_text (GTK_LABEL (location_button->label), diplay_name);
|
|
|
|
/* reset the size request before applying the new label size so that the button can shrink */
|
|
gtk_widget_set_size_request (location_button->label, -1, -1);
|
|
|
|
/* set the label's size request in such a way that a bold label will not change the button size */
|
|
if (gtk_widget_get_mapped (GTK_WIDGET (location_button)))
|
|
thunar_location_button_apply_label_size (location_button);
|
|
|
|
gtk_widget_show (location_button->label);
|
|
}
|
|
|
|
/* the image is only visible for certain special paths */
|
|
if (thunar_file_is_home (file) || thunar_file_is_desktop (file) || thunar_file_is_root (file))
|
|
{
|
|
preferences = thunar_preferences_get ();
|
|
g_object_get (G_OBJECT (preferences), "misc-symbolic-icons-in-toolbar", &use_symbolic_icons, NULL);
|
|
g_object_unref (G_OBJECT (preferences));
|
|
|
|
icon_name = g_strdup_printf ("%s%s", thunar_file_get_icon_name (file, location_button->file_icon_state, icon_theme),
|
|
use_symbolic_icons ? "-symbolic" : "");
|
|
|
|
/* update the icon for the image */
|
|
gtk_image_set_from_icon_name (GTK_IMAGE (location_button->image), icon_name, GTK_ICON_SIZE_BUTTON);
|
|
|
|
/* show the image widget */
|
|
gtk_widget_show (location_button->image);
|
|
|
|
g_free (icon_name);
|
|
}
|
|
else
|
|
{
|
|
/* hide the image widget */
|
|
gtk_widget_hide (location_button->image);
|
|
}
|
|
|
|
/* setup the DnD icon for the button */
|
|
dnd_icon_name = thunar_file_get_custom_icon (file);
|
|
if (dnd_icon_name != NULL)
|
|
{
|
|
gtk_drag_source_set_icon_name (GTK_WIDGET (location_button), dnd_icon_name);
|
|
}
|
|
else
|
|
{
|
|
dnd_icon_name = thunar_file_get_icon_name (file, location_button->file_icon_state, icon_theme);
|
|
gtk_drag_source_set_icon_name (GTK_WIDGET (location_button), dnd_icon_name);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_file_destroy (ThunarLocationButton *location_button,
|
|
ThunarFile *file)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
_thunar_return_if_fail (location_button->file == file);
|
|
_thunar_return_if_fail (THUNAR_IS_FILE (file));
|
|
|
|
/* the file is gone, emit the "gone" signal */
|
|
g_signal_emit (G_OBJECT (location_button), location_button_signals[GONE], 0);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_apply_label_size (ThunarLocationButton *location_button)
|
|
{
|
|
GtkRequisition normal_size;
|
|
GtkRequisition bold_size;
|
|
gint width, height;
|
|
const gchar *text;
|
|
|
|
text = gtk_label_get_text (GTK_LABEL (location_button->label));
|
|
|
|
/* determine the normal size */
|
|
gtk_widget_get_preferred_size (location_button->label, NULL, &normal_size);
|
|
|
|
/* determine the bold size */
|
|
gtk_label_set_text (GTK_LABEL (location_button->bold_label), text);
|
|
gtk_widget_get_preferred_size (location_button->bold_label, NULL, &bold_size);
|
|
|
|
/* request the maximum of the pixel sizes, to make sure
|
|
* the button always requests the same size, no matter if
|
|
* the label is bold or not.
|
|
*/
|
|
width = MAX (normal_size.width, bold_size.width);
|
|
height = MAX (normal_size.height, bold_size.height);
|
|
|
|
if (width > location_button->max_width)
|
|
{
|
|
/* if the size is too big, enable ellipsizing */
|
|
gtk_label_set_ellipsize (GTK_LABEL (location_button->label), PANGO_ELLIPSIZE_MIDDLE);
|
|
gtk_widget_set_size_request (location_button->label, location_button->max_width, height);
|
|
|
|
/* and set a tooltip */
|
|
gtk_widget_set_tooltip_text (location_button->label, text);
|
|
}
|
|
else
|
|
{
|
|
/* don't enable ellipsizing: In some borderline cases, the size calculated
|
|
* is actually off by 1 or 2 pixels and the label will ellipsize unnecessarily
|
|
* TODO: check what we did wrong */
|
|
gtk_widget_set_size_request (location_button->label, width, height);
|
|
gtk_widget_set_tooltip_text (location_button->label, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_location_button_button_press_event (GtkWidget *button,
|
|
GdkEventButton *event)
|
|
{
|
|
gboolean popup_menu_return;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
|
|
|
|
/* check if we can handle the button event */
|
|
if (G_UNLIKELY (event->button == 2))
|
|
{
|
|
/* set button state to inconsistent while holding down the middle-mouse-button */
|
|
gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), TRUE);
|
|
}
|
|
else if (event->button == 3)
|
|
{
|
|
/* emit the "popup-menu" signal and let the surrounding ThunarLocationButtons popup a menu */
|
|
g_signal_emit_by_name (G_OBJECT (button), "popup-menu", &popup_menu_return);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_location_button_button_release_event (GtkWidget *button,
|
|
GdkEventButton *event)
|
|
{
|
|
ThunarApplication *application;
|
|
ThunarPreferences *preferences;
|
|
gboolean open_in_tab;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
|
|
|
|
/* reset inconsistent button state after releasing the middle-mouse-button */
|
|
if (G_UNLIKELY (event->button == 2))
|
|
{
|
|
/* reset button state */
|
|
gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), FALSE);
|
|
|
|
/* verify that we still have a valid file */
|
|
if (G_LIKELY (THUNAR_LOCATION_BUTTON (button)->file != NULL))
|
|
{
|
|
preferences = thunar_preferences_get ();
|
|
g_object_get (preferences, "misc-middle-click-in-tab", &open_in_tab, NULL);
|
|
g_object_unref (preferences);
|
|
|
|
if (open_in_tab)
|
|
{
|
|
/* open in tab */
|
|
g_signal_emit_by_name (G_OBJECT (button), "location-button-clicked", TRUE);
|
|
}
|
|
else
|
|
{
|
|
/* open a new window for the folder */
|
|
application = thunar_application_get ();
|
|
thunar_application_open_window (application, THUNAR_LOCATION_BUTTON (button)->file, gtk_widget_get_screen (button), NULL, TRUE);
|
|
g_object_unref (G_OBJECT (application));
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_location_button_drag_drop (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp)
|
|
{
|
|
GdkAtom target;
|
|
|
|
_thunar_return_val_if_fail (GTK_IS_WIDGET (button), FALSE);
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
|
|
|
|
/* determine the DnD target and see if we can handle it */
|
|
target = gtk_drag_dest_find_target (button, context, NULL);
|
|
if (G_UNLIKELY (target != gdk_atom_intern_static_string ("text/uri-list")))
|
|
return FALSE;
|
|
|
|
/* set state so drag-data-received knows that
|
|
* this is really a drop this time.
|
|
*/
|
|
THUNAR_LOCATION_BUTTON (button)->drop_occurred = TRUE;
|
|
|
|
/* request the drag data from the source */
|
|
gtk_drag_get_data (button, context, target, timestamp);
|
|
|
|
/* we'll call gtk_drag_finish() later */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_drag_data_get (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp)
|
|
{
|
|
gchar **uris;
|
|
GList path_list;
|
|
|
|
_thunar_return_if_fail (GTK_IS_WIDGET (button));
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button));
|
|
|
|
/* verify that we have a valid file */
|
|
if (G_LIKELY (THUNAR_LOCATION_BUTTON (button)->file != NULL))
|
|
{
|
|
/* transform the path into an uri list string */
|
|
path_list.next = path_list.prev = NULL;
|
|
path_list.data = thunar_file_get_file (THUNAR_LOCATION_BUTTON (button)->file);
|
|
|
|
/* set the uri list for the drag selection */
|
|
uris = thunar_g_file_list_to_stringv (&path_list);
|
|
gtk_selection_data_set_uris (selection_data, uris);
|
|
g_strfreev (uris);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_drag_data_received (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp)
|
|
{
|
|
GdkDragAction actions;
|
|
GdkDragAction action;
|
|
gboolean succeed = FALSE;
|
|
ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (button);
|
|
|
|
/* check if we don't already know the drop data */
|
|
if (G_LIKELY (!location_button->drop_data_ready))
|
|
{
|
|
/* extract the URI list from the selection data (if valid) */
|
|
if (gtk_selection_data_get_format (selection_data) == 8 && gtk_selection_data_get_length (selection_data) > 0)
|
|
location_button->drop_file_list = thunar_g_file_list_new_from_string ((const gchar *) gtk_selection_data_get_data (selection_data));
|
|
|
|
/* reset the state */
|
|
location_button->drop_data_ready = TRUE;
|
|
}
|
|
|
|
/* check if the data was dropped */
|
|
if (G_UNLIKELY (location_button->drop_occurred))
|
|
{
|
|
/* reset the state */
|
|
location_button->drop_occurred = FALSE;
|
|
|
|
/* determine the drop dest actions */
|
|
actions = thunar_location_button_get_dest_actions (location_button, context, button, timestamp);
|
|
if (G_LIKELY ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)) != 0))
|
|
{
|
|
/* as the user what to do with the drop data */
|
|
action = (gdk_drag_context_get_selected_action (context) == GDK_ACTION_ASK)
|
|
? thunar_dnd_ask (button, location_button->file, location_button->drop_file_list, actions)
|
|
: gdk_drag_context_get_selected_action (context);
|
|
|
|
/* perform the requested action */
|
|
if (G_LIKELY (action != 0))
|
|
succeed = thunar_dnd_perform (button, location_button->file, location_button->drop_file_list, action, NULL);
|
|
}
|
|
|
|
/* tell the peer that we handled the drop */
|
|
gtk_drag_finish (context, succeed, FALSE, timestamp);
|
|
|
|
/* disable the highlighting and release the drag data */
|
|
thunar_location_button_drag_leave (button, context, timestamp);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_drag_leave (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
guint timestamp)
|
|
{
|
|
ThunarLocationButton *location_button;
|
|
|
|
_thunar_return_if_fail (GTK_IS_BUTTON (button));
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button));
|
|
|
|
location_button = THUNAR_LOCATION_BUTTON (button);
|
|
|
|
/* reset the file icon state to default appearance */
|
|
if (G_LIKELY (location_button->file_icon_state != THUNAR_FILE_ICON_STATE_DEFAULT))
|
|
{
|
|
/* we may switch to "drop icon state" during drag motion */
|
|
location_button->file_icon_state = THUNAR_FILE_ICON_STATE_DEFAULT;
|
|
|
|
/* update the user interface if we still have a file */
|
|
if (G_LIKELY (location_button->file != NULL))
|
|
thunar_location_button_file_changed (location_button, location_button->file);
|
|
}
|
|
|
|
/* reset the "drop data ready" status and free the path list */
|
|
if (G_LIKELY (location_button->drop_data_ready))
|
|
{
|
|
thunar_g_list_free_full (location_button->drop_file_list);
|
|
location_button->drop_data_ready = FALSE;
|
|
location_button->drop_file_list = NULL;
|
|
}
|
|
|
|
/* be sure to cancel any running enter timeout */
|
|
if (G_LIKELY (location_button->enter_timeout_id != 0))
|
|
g_source_remove (location_button->enter_timeout_id);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_location_button_drag_motion (GtkWidget *button,
|
|
GdkDragContext *context,
|
|
gint x,
|
|
gint y,
|
|
guint timestamp)
|
|
{
|
|
ThunarFileIconState file_icon_state;
|
|
GdkDragAction actions;
|
|
GdkAtom target;
|
|
gint delay;
|
|
ThunarLocationButton *location_button;
|
|
|
|
_thunar_return_val_if_fail (GTK_IS_BUTTON (button), FALSE);
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
|
|
|
|
location_button = THUNAR_LOCATION_BUTTON (button);
|
|
|
|
/* schedule the enter timeout if not already done */
|
|
if (G_UNLIKELY (location_button->enter_timeout_id == 0))
|
|
{
|
|
/* we used to use gtk-menu-popdown-delay here, but this was never right and is now deprecated */
|
|
/* therefore, we hardcode the value instead. */
|
|
delay = 1000;
|
|
|
|
/* schedule the timeout */
|
|
location_button->enter_timeout_id = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, delay,
|
|
thunar_location_button_enter_timeout,
|
|
location_button,
|
|
thunar_location_button_enter_timeout_destroy);
|
|
}
|
|
|
|
/* request the drop on-demand (if we don't have it already) */
|
|
if (G_UNLIKELY (!location_button->drop_data_ready))
|
|
{
|
|
/* check if we can handle that drag data (can only drop text/uri-list) */
|
|
target = gtk_drag_dest_find_target (button, context, NULL);
|
|
if (G_LIKELY (target == gdk_atom_intern_static_string ("text/uri-list")))
|
|
{
|
|
/* request the drop data from the source */
|
|
gtk_drag_get_data (button, context, target, timestamp);
|
|
}
|
|
|
|
/* tell GDK that it cannot drop here (yet!) */
|
|
gdk_drag_status (context, 0, timestamp);
|
|
}
|
|
else
|
|
{
|
|
/* check whether we can drop into the buttons' folder */
|
|
actions = thunar_location_button_get_dest_actions (location_button, context, button, timestamp);
|
|
|
|
/* determine the new file icon state */
|
|
file_icon_state = (actions == 0) ? THUNAR_FILE_ICON_STATE_DEFAULT : THUNAR_FILE_ICON_STATE_DROP;
|
|
|
|
/* check if we need to update the user interface */
|
|
if (G_UNLIKELY (location_button->file_icon_state != file_icon_state))
|
|
{
|
|
/* apply the new icon state */
|
|
location_button->file_icon_state = file_icon_state;
|
|
|
|
/* update the user interface if we have a file */
|
|
if (G_LIKELY (location_button->file != NULL))
|
|
thunar_location_button_file_changed (location_button, location_button->file);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_location_button_enter_timeout (gpointer user_data)
|
|
{
|
|
ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (user_data);
|
|
|
|
/* We emulate a "clicked" event here, because else the buttons
|
|
* would be destroyed and replaced by new buttons, which causes
|
|
* the Gtk DND code to dump core once the mouse leaves the area
|
|
* of the new button.
|
|
*
|
|
* Besides that, handling this as "clicked" event allows the user
|
|
* to go back to the initial directory.
|
|
*/
|
|
gtk_button_clicked (GTK_BUTTON (location_button));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_enter_timeout_destroy (gpointer user_data)
|
|
{
|
|
THUNAR_LOCATION_BUTTON (user_data)->enter_timeout_id = 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_new:
|
|
*
|
|
* Allocates a new #ThunarLocationButton instance.
|
|
*
|
|
* Return value: the newly allocated #ThunarLocationButton.
|
|
**/
|
|
GtkWidget *
|
|
thunar_location_button_new (void)
|
|
{
|
|
return g_object_new (THUNAR_TYPE_LOCATION_BUTTON, NULL);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_location_button_active_changed (ThunarLocationButton *location_button)
|
|
{
|
|
gboolean active;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
|
|
active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (location_button));
|
|
|
|
/* use a bold label for active location buttons */
|
|
gtk_label_set_attributes (GTK_LABEL (location_button->label), active ? thunar_pango_attr_list_bold () : NULL);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_get_file:
|
|
* @location_button : a #ThunarLocationButton.
|
|
*
|
|
* Returns the #ThunarFile for @location_button.
|
|
*
|
|
* Return value: the #ThunarFile for @location_button.
|
|
**/
|
|
ThunarFile *
|
|
thunar_location_button_get_file (ThunarLocationButton *location_button)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button), NULL);
|
|
return location_button->file;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_set_file:
|
|
* @location_button : a #ThunarLocationButton.
|
|
* @file : a #ThunarFile or %NULL.
|
|
*
|
|
* Sets the file for @location_button to @file.
|
|
**/
|
|
void
|
|
thunar_location_button_set_file (ThunarLocationButton *location_button,
|
|
ThunarFile *file)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
_thunar_return_if_fail (file == NULL || THUNAR_IS_FILE (file));
|
|
|
|
/* check if we already use that file */
|
|
if (G_UNLIKELY (location_button->file == file))
|
|
return;
|
|
|
|
/* disconnect from the previous file */
|
|
if (location_button->file != NULL)
|
|
{
|
|
/* stop watching the file */
|
|
thunar_file_unwatch (location_button->file);
|
|
|
|
/* disconnect signals and release reference */
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (location_button->file), thunar_location_button_file_destroy, location_button);
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (location_button->file), thunar_location_button_file_changed, location_button);
|
|
g_object_unref (G_OBJECT (location_button->file));
|
|
}
|
|
|
|
/* activate the new file */
|
|
location_button->file = file;
|
|
|
|
/* connect to the new file */
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* take a reference on the new file */
|
|
g_object_ref (G_OBJECT (file));
|
|
|
|
/* watch the file for changes */
|
|
thunar_file_watch (file);
|
|
|
|
/* stay informed about changes to the file */
|
|
g_signal_connect_swapped (G_OBJECT (file), "changed", G_CALLBACK (thunar_location_button_file_changed), location_button);
|
|
g_signal_connect_swapped (G_OBJECT (file), "destroy", G_CALLBACK (thunar_location_button_file_destroy), location_button);
|
|
|
|
/* update our internal state for the new file */
|
|
thunar_location_button_file_changed (location_button, file);
|
|
}
|
|
|
|
/* notify listeners */
|
|
g_object_notify (G_OBJECT (location_button), "file");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_get_max_width:
|
|
* @location_button : a #ThunarLocationButton.
|
|
*
|
|
* Retrieves the maximum width of @location_button, in pixels.
|
|
*
|
|
* Return value: the maximum width of @location_button in pixels.
|
|
**/
|
|
gint
|
|
thunar_location_button_get_max_width (ThunarLocationButton *location_button)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button), -1);
|
|
|
|
return location_button->max_width;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_set_max_width:
|
|
* @location_button : a #ThunarLocationButton.
|
|
* @max_width : the new maximum width, in pixels.
|
|
*
|
|
* Sets the maximum width in pixels of @location_button to @max_width.
|
|
**/
|
|
void
|
|
thunar_location_button_set_max_width (ThunarLocationButton *location_button,
|
|
gint max_width)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
|
|
if (location_button->max_width != max_width)
|
|
{
|
|
location_button->max_width = max_width;
|
|
|
|
/* update the label size */
|
|
thunar_location_button_apply_label_size (location_button);
|
|
|
|
/* notify listeners */
|
|
g_object_notify (G_OBJECT (location_button), "max-width");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_location_button_clicked:
|
|
* @location_button : a #ThunarLocationButton.
|
|
*
|
|
* Emits the ::location-button-clicked signal on @location_button.
|
|
**/
|
|
void
|
|
thunar_location_button_clicked (ThunarLocationButton *location_button)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
|
|
g_signal_emit (G_OBJECT (location_button), location_button_signals[CLICKED], 0, FALSE);
|
|
}
|