thunar-usagebars/thunar/thunar-location-button.c
4aiman e969f94310 thunar 4.20.2 upstream
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.
2026-11-03 22:42:48 +03:00

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);
}