Tree of tag thunar-4.20.7 (commit df0e054) from https://gitlab.xfce.org/xfce/thunar. Upstream history is not included here to keep the repository small.
1430 lines
46 KiB
C
1430 lines
46 KiB
C
/* vi:set et ai sw=2 sts=2 ts=2: */
|
|
/*-
|
|
* Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
|
|
* Copyright (c) 2009-2010 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., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
* The icon code is based on ideas from SexyIconEntry, which was written by
|
|
* Christian Hammond <chipx86@chipx86.com>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_MEMORY_H
|
|
#include <memory.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "thunar/thunar-gobject-extensions.h"
|
|
#include "thunar/thunar-icon-factory.h"
|
|
#include "thunar/thunar-icon-renderer.h"
|
|
#include "thunar/thunar-list-model.h"
|
|
#include "thunar/thunar-path-entry.h"
|
|
#include "thunar/thunar-preferences.h"
|
|
#include "thunar/thunar-private.h"
|
|
#include "thunar/thunar-util.h"
|
|
#include "thunar/thunar-window.h"
|
|
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
|
|
|
|
#define ICON_MARGIN (2)
|
|
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CURRENT_FILE,
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_editable_init (GtkEditableInterface *iface);
|
|
static void
|
|
thunar_path_entry_finalize (GObject *object);
|
|
static void
|
|
thunar_path_entry_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void
|
|
thunar_path_entry_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static gboolean
|
|
thunar_path_entry_focus (GtkWidget *widget,
|
|
GtkDirectionType direction);
|
|
static gboolean
|
|
thunar_path_entry_icon_press_event (GtkEntry *entry,
|
|
GtkEntryIconPosition icon_pos,
|
|
GdkEventButton *event,
|
|
gpointer userdata);
|
|
static void
|
|
thunar_path_entry_scale_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
static gboolean
|
|
thunar_path_entry_key_press_event (GtkWidget *widget,
|
|
GdkEventKey *event);
|
|
static void
|
|
thunar_path_entry_drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp);
|
|
static void
|
|
thunar_path_entry_drag_end (GtkWidget *widget,
|
|
GdkDragContext *context);
|
|
static void
|
|
thunar_path_entry_activate (GtkEntry *entry);
|
|
static void
|
|
thunar_path_entry_changed (GtkEditable *editable);
|
|
static void
|
|
thunar_path_entry_update_icon (ThunarPathEntry *path_entry);
|
|
static void
|
|
thunar_path_entry_do_insert_text (GtkEditable *editable,
|
|
const gchar *new_text,
|
|
gint new_text_length,
|
|
gint *position);
|
|
static void
|
|
thunar_path_entry_clear_completion (ThunarPathEntry *path_entry);
|
|
static void
|
|
thunar_path_entry_common_prefix_append (ThunarPathEntry *path_entry,
|
|
gboolean highlight);
|
|
static void
|
|
thunar_path_entry_common_prefix_lookup (ThunarPathEntry *path_entry,
|
|
gchar **prefix_return,
|
|
ThunarFile **file_return);
|
|
static gboolean
|
|
thunar_path_entry_match_func (GtkEntryCompletion *completion,
|
|
const gchar *key,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data);
|
|
static gboolean
|
|
thunar_path_entry_match_selected (GtkEntryCompletion *completion,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data);
|
|
static gboolean
|
|
thunar_path_entry_parse (ThunarPathEntry *path_entry,
|
|
gchar **folder_part,
|
|
gchar **file_part,
|
|
GError **error);
|
|
static void
|
|
thunar_path_entry_queue_check_completion (ThunarPathEntry *path_entry);
|
|
static gboolean
|
|
thunar_path_entry_check_completion_idle (gpointer user_data);
|
|
static void
|
|
thunar_path_entry_check_completion_idle_destroy (gpointer user_data);
|
|
|
|
|
|
|
|
struct _ThunarPathEntryClass
|
|
{
|
|
GtkEntryClass __parent__;
|
|
};
|
|
|
|
struct _ThunarPathEntry
|
|
{
|
|
GtkEntry __parent__;
|
|
|
|
ThunarIconFactory *icon_factory;
|
|
ThunarFile *current_folder;
|
|
ThunarFile *current_file;
|
|
GFile *working_directory;
|
|
|
|
guint drag_button;
|
|
|
|
/* auto completion support */
|
|
guint in_change : 1;
|
|
guint has_completion : 1;
|
|
guint check_completion_idle_id;
|
|
|
|
gboolean search_mode;
|
|
};
|
|
|
|
|
|
|
|
static const GtkTargetEntry drag_targets[] = {
|
|
{
|
|
"text/uri-list",
|
|
0,
|
|
0,
|
|
},
|
|
};
|
|
|
|
|
|
|
|
static GtkEditableInterface *thunar_path_entry_editable_parent_iface;
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (ThunarPathEntry, thunar_path_entry, GTK_TYPE_ENTRY,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, thunar_path_entry_editable_init))
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_class_init (ThunarPathEntryClass *klass)
|
|
{
|
|
GtkWidgetClass *gtkwidget_class;
|
|
GtkEntryClass *gtkentry_class;
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
gobject_class->finalize = thunar_path_entry_finalize;
|
|
gobject_class->get_property = thunar_path_entry_get_property;
|
|
gobject_class->set_property = thunar_path_entry_set_property;
|
|
|
|
gtkwidget_class = GTK_WIDGET_CLASS (klass);
|
|
gtkwidget_class->focus = thunar_path_entry_focus;
|
|
gtkwidget_class->drag_data_get = thunar_path_entry_drag_data_get;
|
|
gtkwidget_class->drag_end = thunar_path_entry_drag_end;
|
|
|
|
gtkentry_class = GTK_ENTRY_CLASS (klass);
|
|
gtkentry_class->activate = thunar_path_entry_activate;
|
|
|
|
/**
|
|
* ThunarPathEntry:current-file:
|
|
*
|
|
* The #ThunarFile currently displayed by the path entry or %NULL.
|
|
**/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CURRENT_FILE,
|
|
g_param_spec_object ("current-file",
|
|
"current-file",
|
|
"current-file",
|
|
THUNAR_TYPE_FILE,
|
|
EXO_PARAM_READWRITE));
|
|
|
|
/**
|
|
* ThunarPathEntry:icon-size:
|
|
*
|
|
* The preferred size of the icon displayed in the path entry.
|
|
**/
|
|
gtk_widget_class_install_style_property (gtkwidget_class,
|
|
g_param_spec_int ("icon-size",
|
|
_("Icon size"),
|
|
_("The icon size for the path entry"),
|
|
1, G_MAXINT, 16, EXO_PARAM_READABLE));
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_editable_init (GtkEditableInterface *iface)
|
|
{
|
|
thunar_path_entry_editable_parent_iface = g_type_interface_peek_parent (iface);
|
|
|
|
iface->changed = thunar_path_entry_changed;
|
|
iface->do_insert_text = thunar_path_entry_do_insert_text;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_init (ThunarPathEntry *path_entry)
|
|
{
|
|
GtkEntryCompletion *completion;
|
|
GtkCellRenderer *renderer;
|
|
ThunarStandardViewModel *store;
|
|
|
|
path_entry->check_completion_idle_id = 0;
|
|
path_entry->working_directory = NULL;
|
|
|
|
/* allocate a new entry completion for the given model */
|
|
completion = gtk_entry_completion_new ();
|
|
gtk_entry_completion_set_popup_single_match (completion, FALSE);
|
|
gtk_entry_completion_set_match_func (completion, thunar_path_entry_match_func, path_entry, NULL);
|
|
g_signal_connect (G_OBJECT (completion), "match-selected", G_CALLBACK (thunar_path_entry_match_selected), path_entry);
|
|
|
|
/* add the icon renderer to the entry completion */
|
|
renderer = g_object_new (THUNAR_TYPE_ICON_RENDERER, "size", 16, NULL);
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, FALSE);
|
|
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), renderer, "file", THUNAR_COLUMN_FILE);
|
|
|
|
/* add the text renderer to the entry completion */
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
|
|
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), renderer, "text", THUNAR_COLUMN_NAME);
|
|
|
|
/* allocate a new list mode for the completion */
|
|
store = thunar_list_model_new ();
|
|
thunar_list_model_check_file_in_model_before_use (THUNAR_LIST_MODEL (store));
|
|
thunar_standard_view_model_set_show_hidden (store, TRUE);
|
|
thunar_standard_view_model_set_folders_first (store, TRUE);
|
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), THUNAR_COLUMN_FILE_NAME, GTK_SORT_ASCENDING);
|
|
gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
|
|
g_object_unref (G_OBJECT (store));
|
|
|
|
/* need to connect the "key-press-event" before the GtkEntry class connects the completion signals, so
|
|
* we get the Tab key before its handled as part of the completion stuff.
|
|
*/
|
|
g_signal_connect (G_OBJECT (path_entry), "key-press-event", G_CALLBACK (thunar_path_entry_key_press_event), NULL);
|
|
|
|
/* setup the new completion */
|
|
gtk_entry_set_completion (GTK_ENTRY (path_entry), completion);
|
|
|
|
/* cleanup */
|
|
g_object_unref (G_OBJECT (completion));
|
|
|
|
/* clear the auto completion whenever the cursor is moved manually or the selection is changed manually */
|
|
g_signal_connect (G_OBJECT (path_entry), "notify::cursor-position", G_CALLBACK (thunar_path_entry_clear_completion), NULL);
|
|
g_signal_connect (G_OBJECT (path_entry), "notify::selection-bound", G_CALLBACK (thunar_path_entry_clear_completion), NULL);
|
|
|
|
/* connect the icon signals */
|
|
g_signal_connect (G_OBJECT (path_entry), "icon-press", G_CALLBACK (thunar_path_entry_icon_press_event), NULL);
|
|
g_signal_connect (G_OBJECT (path_entry), "notify::scale-factor", G_CALLBACK (thunar_path_entry_scale_changed), NULL);
|
|
|
|
/* disabled initially */
|
|
path_entry->search_mode = FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_finalize (GObject *object)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
|
|
|
|
/* release factory */
|
|
if (path_entry->icon_factory != NULL)
|
|
g_object_unref (path_entry->icon_factory);
|
|
|
|
/* release the current-folder reference */
|
|
if (G_LIKELY (path_entry->current_folder != NULL))
|
|
g_object_unref (G_OBJECT (path_entry->current_folder));
|
|
|
|
/* release the current-file reference */
|
|
if (G_LIKELY (path_entry->current_file != NULL))
|
|
{
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (path_entry->current_file), thunar_path_entry_set_current_file, path_entry);
|
|
g_object_unref (G_OBJECT (path_entry->current_file));
|
|
}
|
|
|
|
/* release the working directory */
|
|
if (G_LIKELY (path_entry->working_directory != NULL))
|
|
g_object_unref (G_OBJECT (path_entry->working_directory));
|
|
|
|
/* drop the check_completion_idle source */
|
|
if (G_UNLIKELY (path_entry->check_completion_idle_id != 0))
|
|
g_source_remove (path_entry->check_completion_idle_id);
|
|
|
|
(*G_OBJECT_CLASS (thunar_path_entry_parent_class)->finalize) (object);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CURRENT_FILE:
|
|
g_value_set_object (value, thunar_path_entry_get_current_file (path_entry));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CURRENT_FILE:
|
|
thunar_path_entry_set_current_file (path_entry, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_focus (GtkWidget *widget,
|
|
GtkDirectionType direction)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
|
|
GdkModifierType state;
|
|
gboolean control_pressed;
|
|
|
|
/* determine whether control is pressed */
|
|
control_pressed = (gtk_get_current_event_state (&state) && (state & GDK_CONTROL_MASK) != 0);
|
|
|
|
/* evil hack, but works for GtkFileChooserEntry, so who cares :-) */
|
|
if ((direction == GTK_DIR_TAB_FORWARD) && (gtk_widget_has_focus (widget)) && !control_pressed)
|
|
{
|
|
/* if we don't have a completion and the cursor is at the end of the line, we just insert the common prefix */
|
|
if (!path_entry->has_completion && gtk_editable_get_position (GTK_EDITABLE (path_entry)) == gtk_entry_get_text_length (GTK_ENTRY (path_entry)))
|
|
thunar_path_entry_common_prefix_append (path_entry, FALSE);
|
|
|
|
/* place the cursor at the end */
|
|
gtk_editable_set_position (GTK_EDITABLE (path_entry), gtk_entry_get_text_length (GTK_ENTRY (path_entry)));
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
return (*GTK_WIDGET_CLASS (thunar_path_entry_parent_class)->focus) (widget, direction);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_scale_changed (GObject *object,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
gtk_widget_queue_draw (GTK_WIDGET (object));
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_icon_press_event (GtkEntry *entry,
|
|
GtkEntryIconPosition icon_pos,
|
|
GdkEventButton *event,
|
|
gpointer user_data)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (entry);
|
|
GdkDragContext *context;
|
|
GtkTargetList *target_list;
|
|
GdkPixbuf *icon;
|
|
cairo_surface_t *surface;
|
|
gint size;
|
|
gint scale_factor;
|
|
|
|
if (path_entry->current_file == NULL)
|
|
return FALSE;
|
|
|
|
if (event->button == 1 && icon_pos == GTK_ENTRY_ICON_PRIMARY)
|
|
{
|
|
/* save the drag button state */
|
|
path_entry->drag_button = event->button;
|
|
|
|
/* create a drag context */
|
|
target_list = gtk_target_list_new (drag_targets, G_N_ELEMENTS (drag_targets));
|
|
context = gtk_drag_begin_with_coordinates (GTK_WIDGET (entry), target_list,
|
|
GDK_ACTION_COPY | GDK_ACTION_LINK,
|
|
event->button,
|
|
|
|
(GdkEvent *) event, -1, -1);
|
|
gtk_target_list_unref (target_list);
|
|
|
|
/* setup the drag icon (atleast 24px) */
|
|
gtk_widget_style_get (GTK_WIDGET (entry), "icon-size", &size, NULL);
|
|
scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (entry));
|
|
icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
|
|
path_entry->current_file,
|
|
THUNAR_FILE_ICON_STATE_DEFAULT,
|
|
MAX (size, 16), scale_factor,
|
|
FALSE, NULL);
|
|
if (G_LIKELY (icon != NULL))
|
|
{
|
|
surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, gtk_widget_get_window (GTK_WIDGET (entry)));
|
|
g_object_unref (G_OBJECT (icon));
|
|
gtk_drag_set_icon_surface (context, surface);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_key_press_event (GtkWidget *widget,
|
|
GdkEventKey *event)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
|
|
|
|
/* check if we have a tab key press here and control is not pressed */
|
|
if (G_UNLIKELY (event->keyval == GDK_KEY_Tab && (event->state & GDK_CONTROL_MASK) == 0) && path_entry->search_mode == FALSE)
|
|
{
|
|
/* if we don't have a completion and the cursor is at the end of the line, we just insert the common prefix */
|
|
if (!path_entry->has_completion && gtk_editable_get_position (GTK_EDITABLE (path_entry)) == gtk_entry_get_text_length (GTK_ENTRY (path_entry)))
|
|
thunar_path_entry_common_prefix_append (path_entry, FALSE);
|
|
|
|
/* place the cursor at the end */
|
|
gtk_editable_set_position (GTK_EDITABLE (path_entry), gtk_entry_get_text_length (GTK_ENTRY (path_entry)));
|
|
|
|
/* emit "changed", so the completion window is popped up */
|
|
g_signal_emit_by_name (G_OBJECT (path_entry), "changed", 0);
|
|
|
|
/* we handled the event */
|
|
return TRUE;
|
|
}
|
|
/* cancel search with `Escape` */
|
|
if (G_UNLIKELY (path_entry->search_mode == TRUE && event->keyval == GDK_KEY_Escape && (event->state & GDK_CONTROL_MASK) == 0))
|
|
{
|
|
GtkWidget *window = gtk_widget_get_toplevel (widget);
|
|
thunar_window_action_cancel_search (THUNAR_WINDOW (window));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint timestamp)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
|
|
GList file_list;
|
|
gchar **uris;
|
|
|
|
/* check if the icon was clicked and verify that we actually display a path */
|
|
if (path_entry->drag_button > 0 && path_entry->current_file != NULL)
|
|
{
|
|
/* transform the path for the current file into an uri string list */
|
|
file_list.next = file_list.prev = NULL;
|
|
file_list.data = thunar_file_get_file (path_entry->current_file);
|
|
|
|
/* setup the uri list for the drag selection */
|
|
uris = thunar_g_file_list_to_stringv (&file_list);
|
|
gtk_selection_data_set_uris (selection_data, uris);
|
|
g_strfreev (uris);
|
|
|
|
return;
|
|
}
|
|
|
|
(*GTK_WIDGET_CLASS (thunar_path_entry_parent_class)->drag_data_get) (widget, context, selection_data, info, timestamp);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_drag_end (GtkWidget *widget,
|
|
GdkDragContext *context)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (widget);
|
|
|
|
/* reset the drag button state */
|
|
path_entry->drag_button = 0;
|
|
|
|
(*GTK_WIDGET_CLASS (thunar_path_entry_parent_class)->drag_end) (widget, context);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_activate (GtkEntry *entry)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (entry);
|
|
|
|
if (G_LIKELY (path_entry->has_completion))
|
|
{
|
|
/* place cursor at the end of the text if we have completion set */
|
|
gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
|
|
}
|
|
|
|
/* emit the "activate" signal */
|
|
(*GTK_ENTRY_CLASS (thunar_path_entry_parent_class)->activate) (entry);
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_file_exists (GFile *file)
|
|
{
|
|
gchar *path;
|
|
gboolean result = FALSE;
|
|
|
|
_thunar_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
|
|
/* support for locations like 'admin://' and 'network://' */
|
|
if (!g_file_is_native (file))
|
|
return TRUE;
|
|
|
|
/* check if native file exists */
|
|
path = g_file_get_path (file);
|
|
if (path != NULL)
|
|
{
|
|
if (g_file_test (path, G_FILE_TEST_EXISTS))
|
|
result = TRUE;
|
|
g_free (path);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_changed (GtkEditable *editable)
|
|
{
|
|
GtkEntryCompletion *completion;
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (editable);
|
|
ThunarFolder *folder;
|
|
GtkTreeModel *model;
|
|
const gchar *text;
|
|
gchar *scheme;
|
|
ThunarFile *current_folder = NULL;
|
|
ThunarFile *current_file = NULL;
|
|
GFile *folder_path = NULL;
|
|
GFile *file_path = NULL;
|
|
gchar *folder_part = NULL;
|
|
gchar *file_part = NULL;
|
|
gboolean update_icon = FALSE;
|
|
|
|
/* check if we should ignore this event */
|
|
if (G_UNLIKELY (path_entry->in_change))
|
|
return;
|
|
|
|
/* parse the entered string (handling URIs properly) */
|
|
text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
|
|
if (G_UNLIKELY (text != NULL && thunar_util_is_a_search_query (text)) == TRUE)
|
|
{
|
|
GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (editable));
|
|
path_entry->search_mode = TRUE;
|
|
update_icon = TRUE;
|
|
if (GTK_IS_WINDOW (window))
|
|
thunar_window_update_search (THUNAR_WINDOW (window));
|
|
}
|
|
else
|
|
{
|
|
/* cancel search when part of 'Search: ' is deleted */
|
|
if (path_entry->search_mode == TRUE)
|
|
{
|
|
GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (editable));
|
|
thunar_window_action_cancel_search (THUNAR_WINDOW (window));
|
|
}
|
|
|
|
/* location/folder-path code */
|
|
scheme = g_uri_parse_scheme (text);
|
|
if (G_UNLIKELY (scheme != NULL))
|
|
{
|
|
/* Text is URI */
|
|
g_free (scheme);
|
|
file_path = g_file_new_for_uri (text);
|
|
|
|
/* use the same file if the text assumes we're in a directory */
|
|
if (g_str_has_suffix (text, "/"))
|
|
folder_path = G_FILE (g_object_ref (G_OBJECT (file_path)));
|
|
else
|
|
folder_path = g_file_get_parent (file_path);
|
|
}
|
|
else if (thunar_path_entry_parse (path_entry, &folder_part, &file_part, NULL))
|
|
{
|
|
/* determine the folder path */
|
|
folder_path = g_file_new_for_path (folder_part);
|
|
|
|
/* determine the relative file path */
|
|
if (G_LIKELY (*file_part != '\0'))
|
|
file_path = g_file_resolve_relative_path (folder_path, file_part);
|
|
else
|
|
file_path = g_object_ref (folder_path);
|
|
|
|
/* cleanup the part strings */
|
|
g_free (folder_part);
|
|
g_free (file_part);
|
|
}
|
|
}
|
|
|
|
/* determine new current file/folder from the paths */
|
|
if (folder_path != NULL && thunar_path_entry_file_exists (folder_path))
|
|
current_folder = thunar_file_get (folder_path, NULL);
|
|
if (file_path != NULL && thunar_path_entry_file_exists (file_path))
|
|
current_file = thunar_file_get (file_path, NULL);
|
|
|
|
/* determine the entry completion */
|
|
completion = gtk_entry_get_completion (GTK_ENTRY (path_entry));
|
|
|
|
/* update the current folder if required */
|
|
if (current_folder != path_entry->current_folder)
|
|
{
|
|
/* take a reference on the current folder */
|
|
if (G_LIKELY (path_entry->current_folder != NULL))
|
|
g_object_unref (G_OBJECT (path_entry->current_folder));
|
|
path_entry->current_folder = current_folder;
|
|
if (G_LIKELY (current_folder != NULL))
|
|
g_object_ref (G_OBJECT (current_folder));
|
|
|
|
/* try to open the current-folder file as folder */
|
|
if (current_folder != NULL && thunar_file_is_directory (current_folder))
|
|
folder = thunar_folder_get_for_file (current_folder);
|
|
else
|
|
folder = NULL;
|
|
|
|
/* set the new folder for the completion model, but disconnect the model from the
|
|
* completion first, because GtkEntryCompletion has become very slow recently when
|
|
* updating the model being used (https://bugzilla.xfce.org/show_bug.cgi?id=1681).
|
|
*/
|
|
model = gtk_entry_completion_get_model (completion);
|
|
g_object_ref (G_OBJECT (model));
|
|
gtk_entry_completion_set_model (completion, NULL);
|
|
thunar_standard_view_model_set_folder (THUNAR_STANDARD_VIEW_MODEL (model), folder, NULL);
|
|
gtk_entry_completion_set_model (completion, model);
|
|
g_object_unref (G_OBJECT (model));
|
|
|
|
/* cleanup */
|
|
if (G_LIKELY (folder != NULL))
|
|
g_object_unref (G_OBJECT (folder));
|
|
|
|
/* we most likely need a new icon */
|
|
update_icon = TRUE;
|
|
}
|
|
|
|
/* update the current file if required */
|
|
if (current_file != path_entry->current_file)
|
|
{
|
|
if (G_UNLIKELY (path_entry->current_file != NULL))
|
|
{
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (path_entry->current_file), thunar_path_entry_set_current_file, path_entry);
|
|
g_object_unref (G_OBJECT (path_entry->current_file));
|
|
}
|
|
path_entry->current_file = current_file;
|
|
if (G_UNLIKELY (current_file != NULL))
|
|
{
|
|
g_object_ref (G_OBJECT (current_file));
|
|
g_signal_connect_swapped (G_OBJECT (current_file), "changed", G_CALLBACK (thunar_path_entry_set_current_file), path_entry);
|
|
}
|
|
g_object_notify (G_OBJECT (path_entry), "current-file");
|
|
|
|
/* we most likely need a new icon */
|
|
update_icon = TRUE;
|
|
}
|
|
|
|
if (update_icon)
|
|
thunar_path_entry_update_icon (path_entry);
|
|
|
|
/* cleanup */
|
|
if (G_LIKELY (current_folder != NULL))
|
|
g_object_unref (G_OBJECT (current_folder));
|
|
if (G_LIKELY (current_file != NULL))
|
|
g_object_unref (G_OBJECT (current_file));
|
|
if (G_LIKELY (folder_path != NULL))
|
|
g_object_unref (folder_path);
|
|
if (G_LIKELY (file_path != NULL))
|
|
g_object_unref (file_path);
|
|
}
|
|
|
|
|
|
static void
|
|
thunar_path_entry_update_icon (ThunarPathEntry *path_entry)
|
|
{
|
|
ThunarPreferences *preferences;
|
|
GdkPixbuf *icon = NULL;
|
|
GtkIconTheme *icon_theme;
|
|
gint icon_size;
|
|
gint scale_factor;
|
|
gboolean use_symbolic_icons;
|
|
|
|
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));
|
|
|
|
if (path_entry->search_mode == TRUE)
|
|
{
|
|
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (path_entry), GTK_ENTRY_ICON_PRIMARY,
|
|
use_symbolic_icons ? "system-search-symbolic" : "system-search");
|
|
return;
|
|
}
|
|
|
|
if (path_entry->icon_factory == NULL)
|
|
{
|
|
icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_entry)));
|
|
path_entry->icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
|
|
}
|
|
|
|
gtk_widget_style_get (GTK_WIDGET (path_entry), "icon-size", &icon_size, NULL);
|
|
scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (path_entry));
|
|
|
|
if (G_UNLIKELY (path_entry->current_file != NULL))
|
|
{
|
|
icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
|
|
path_entry->current_file,
|
|
THUNAR_FILE_ICON_STATE_DEFAULT,
|
|
icon_size, scale_factor,
|
|
FALSE, NULL);
|
|
}
|
|
else if (G_LIKELY (path_entry->current_folder != NULL))
|
|
{
|
|
icon = thunar_icon_factory_load_file_icon (path_entry->icon_factory,
|
|
path_entry->current_folder,
|
|
THUNAR_FILE_ICON_STATE_DEFAULT,
|
|
icon_size, scale_factor,
|
|
FALSE, NULL);
|
|
}
|
|
|
|
if (icon != NULL)
|
|
{
|
|
gtk_entry_set_icon_from_pixbuf (GTK_ENTRY (path_entry),
|
|
GTK_ENTRY_ICON_PRIMARY,
|
|
icon);
|
|
g_object_unref (icon);
|
|
}
|
|
else
|
|
{
|
|
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (path_entry), GTK_ENTRY_ICON_PRIMARY,
|
|
use_symbolic_icons ? "dialog-error-symbolic" : "dialog-error");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_do_insert_text (GtkEditable *editable,
|
|
const gchar *new_text,
|
|
gint new_text_length,
|
|
gint *position)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (editable);
|
|
|
|
/* let the GtkEntry class handle the insert */
|
|
(*thunar_path_entry_editable_parent_iface->do_insert_text) (editable, new_text, new_text_length, position);
|
|
|
|
/* queue a completion check if this insert operation was triggered by the user */
|
|
if (G_LIKELY (!path_entry->in_change))
|
|
thunar_path_entry_queue_check_completion (path_entry);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_clear_completion (ThunarPathEntry *path_entry)
|
|
{
|
|
/* reset the completion and apply the new text */
|
|
if (G_UNLIKELY (path_entry->has_completion))
|
|
{
|
|
path_entry->has_completion = FALSE;
|
|
thunar_path_entry_changed (GTK_EDITABLE (path_entry));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_common_prefix_append (ThunarPathEntry *path_entry,
|
|
gboolean highlight)
|
|
{
|
|
const gchar *last_slash;
|
|
const gchar *text;
|
|
ThunarFile *file;
|
|
gchar *prefix;
|
|
gchar *tmp;
|
|
gint prefix_length;
|
|
gint text_length;
|
|
gint offset;
|
|
gint base;
|
|
|
|
/* determine the common prefix */
|
|
thunar_path_entry_common_prefix_lookup (path_entry, &prefix, &file);
|
|
|
|
/* check if we should append a slash to the prefix */
|
|
if (G_LIKELY (file != NULL))
|
|
{
|
|
/* we only append slashes for directories */
|
|
if (thunar_file_is_directory (file) && file != path_entry->current_file)
|
|
{
|
|
tmp = g_strconcat (prefix, G_DIR_SEPARATOR_S, NULL);
|
|
g_free (prefix);
|
|
prefix = tmp;
|
|
}
|
|
|
|
/* release the file */
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
|
|
/* check if we have a common prefix */
|
|
if (G_LIKELY (prefix != NULL))
|
|
{
|
|
/* determine the UTF-8 length of the entry text */
|
|
text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
last_slash = g_utf8_strrchr (text, -1, G_DIR_SEPARATOR);
|
|
if (G_LIKELY (last_slash != NULL))
|
|
offset = g_utf8_strlen (text, last_slash - text) + 1;
|
|
else
|
|
offset = 0;
|
|
text_length = g_utf8_strlen (text, -1) - offset;
|
|
|
|
/* determine the UTF-8 length of the prefix */
|
|
prefix_length = g_utf8_strlen (prefix, -1);
|
|
|
|
/* append only if the prefix is longer than the already entered text */
|
|
if (G_LIKELY (prefix_length > text_length))
|
|
{
|
|
/* remember the base offset */
|
|
base = offset;
|
|
|
|
/* insert the prefix */
|
|
path_entry->in_change = TRUE;
|
|
gtk_editable_delete_text (GTK_EDITABLE (path_entry), offset, -1);
|
|
gtk_editable_insert_text (GTK_EDITABLE (path_entry), prefix, -1, &offset);
|
|
path_entry->in_change = FALSE;
|
|
|
|
/* highlight the prefix if requested */
|
|
if (G_LIKELY (highlight))
|
|
{
|
|
gtk_editable_select_region (GTK_EDITABLE (path_entry), base + text_length, base + prefix_length);
|
|
path_entry->has_completion = TRUE;
|
|
}
|
|
}
|
|
|
|
/* cleanup */
|
|
g_free (prefix);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_has_prefix_casefolded (const gchar *string,
|
|
const gchar *prefix)
|
|
{
|
|
gchar *string_casefolded;
|
|
gchar *prefix_casefolded;
|
|
gboolean has_prefix;
|
|
|
|
if (string == NULL || prefix == NULL)
|
|
return FALSE;
|
|
|
|
string_casefolded = g_utf8_casefold (string, -1);
|
|
prefix_casefolded = g_utf8_casefold (prefix, -1);
|
|
|
|
has_prefix = g_str_has_prefix (string_casefolded, prefix_casefolded);
|
|
|
|
g_free (string_casefolded);
|
|
g_free (prefix_casefolded);
|
|
|
|
return has_prefix;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_common_prefix_lookup (ThunarPathEntry *path_entry,
|
|
gchar **prefix_return,
|
|
ThunarFile **file_return)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
const gchar *text;
|
|
const gchar *s;
|
|
gchar *name;
|
|
gchar *t;
|
|
|
|
*prefix_return = NULL;
|
|
*file_return = NULL;
|
|
|
|
/* lookup the last slash character in the entry text */
|
|
text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
s = strrchr (text, G_DIR_SEPARATOR);
|
|
if (G_UNLIKELY (s != NULL && s[1] == '\0'))
|
|
return;
|
|
else if (G_LIKELY (s != NULL))
|
|
text = s + 1;
|
|
|
|
/* check all items in the model */
|
|
model = gtk_entry_completion_get_model (gtk_entry_get_completion (GTK_ENTRY (path_entry)));
|
|
if (gtk_tree_model_get_iter_first (model, &iter))
|
|
{
|
|
do
|
|
{
|
|
/* determine the real file name for the iter */
|
|
gtk_tree_model_get (model, &iter, THUNAR_COLUMN_FILE_NAME, &name, -1);
|
|
|
|
/* check if we have a valid prefix here */
|
|
if (thunar_path_entry_has_prefix_casefolded (name, text))
|
|
{
|
|
/* check if we're the first to match */
|
|
if (*prefix_return == NULL)
|
|
{
|
|
/* remember the prefix */
|
|
*prefix_return = g_strdup (name);
|
|
|
|
/* determine the file for the iter */
|
|
gtk_tree_model_get (model, &iter, THUNAR_COLUMN_FILE, file_return, -1);
|
|
}
|
|
else
|
|
{
|
|
/* we already have another prefix, so determine the common part */
|
|
for (s = name, t = *prefix_return; *s != '\0' && *s == *t; ++s, ++t)
|
|
;
|
|
*t = '\0';
|
|
|
|
/* release the file, since it's not a unique match */
|
|
if (G_LIKELY (*file_return != NULL))
|
|
{
|
|
g_object_unref (G_OBJECT (*file_return));
|
|
*file_return = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup */
|
|
g_free (name);
|
|
}
|
|
while (gtk_tree_model_iter_next (model, &iter));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_match_func (GtkEntryCompletion *completion,
|
|
const gchar *key,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
GtkTreeModel *model;
|
|
ThunarPathEntry *path_entry;
|
|
const gchar *last_slash;
|
|
ThunarFile *file;
|
|
gboolean matched;
|
|
gchar *text_normalized;
|
|
gchar *name_normalized;
|
|
gchar *name;
|
|
|
|
/* determine the model from the completion */
|
|
model = gtk_entry_completion_get_model (completion);
|
|
|
|
/* leave if the model is null, we do this in thunar_path_entry_changed() to speed
|
|
* things up, but that causes https://bugzilla.xfce.org/show_bug.cgi?id=4847. */
|
|
if (G_UNLIKELY (model == NULL))
|
|
return FALSE;
|
|
|
|
/* leave if the auto completion highlight was not cleared yet, to prevent
|
|
* https://bugzilla.xfce.org/show_bug.cgi?id=16267. */
|
|
path_entry = THUNAR_PATH_ENTRY (user_data);
|
|
if (G_UNLIKELY (path_entry->has_completion))
|
|
return FALSE;
|
|
|
|
/* determine the current text (UTF-8 normalized) */
|
|
text_normalized = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (user_data)), -1, G_NORMALIZE_ALL);
|
|
|
|
/* lookup the last slash character in the key */
|
|
last_slash = strrchr (text_normalized, G_DIR_SEPARATOR);
|
|
if (G_UNLIKELY (last_slash != NULL && last_slash[1] == '\0'))
|
|
{
|
|
/* check if the file is hidden */
|
|
gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE, &file, -1);
|
|
matched = !thunar_file_is_hidden (file);
|
|
g_object_unref (G_OBJECT (file));
|
|
}
|
|
else
|
|
{
|
|
if (G_UNLIKELY (last_slash == NULL))
|
|
last_slash = text_normalized;
|
|
else
|
|
last_slash += 1;
|
|
|
|
/* determine the real file name for the iter */
|
|
gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE_NAME, &name, -1);
|
|
name_normalized = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
|
|
if (G_LIKELY (name_normalized != NULL))
|
|
g_free (name);
|
|
else
|
|
name_normalized = name;
|
|
|
|
/* check if we have a match here */
|
|
if (name_normalized != NULL)
|
|
matched = thunar_path_entry_has_prefix_casefolded (name_normalized, last_slash);
|
|
else
|
|
matched = FALSE;
|
|
|
|
/* cleanup */
|
|
g_free (name_normalized);
|
|
}
|
|
|
|
/* cleanup */
|
|
g_free (text_normalized);
|
|
|
|
return matched;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_match_selected (GtkEntryCompletion *completion,
|
|
GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (user_data);
|
|
const gchar *last_slash;
|
|
const gchar *text;
|
|
ThunarFile *file;
|
|
gchar *real_name;
|
|
gchar *tmp;
|
|
gint offset;
|
|
|
|
/* determine the file for the iterator */
|
|
gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE, &file, -1);
|
|
|
|
/* determine the real name for the file */
|
|
gtk_tree_model_get (model, iter, THUNAR_COLUMN_FILE_NAME, &real_name, -1);
|
|
|
|
/* append a slash if we have a folder here */
|
|
if (G_LIKELY (thunar_file_is_directory (file)))
|
|
{
|
|
tmp = g_strconcat (real_name, G_DIR_SEPARATOR_S, NULL);
|
|
g_free (real_name);
|
|
real_name = tmp;
|
|
}
|
|
|
|
/* determine the UTF-8 offset of the last slash on the entry text */
|
|
text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
last_slash = g_utf8_strrchr (text, -1, G_DIR_SEPARATOR);
|
|
if (G_LIKELY (last_slash != NULL))
|
|
offset = g_utf8_strlen (text, last_slash - text) + 1;
|
|
else
|
|
offset = 0;
|
|
|
|
/* delete the previous text at the specified offset */
|
|
gtk_editable_delete_text (GTK_EDITABLE (path_entry), offset, -1);
|
|
|
|
/* insert the new file/folder name */
|
|
gtk_editable_insert_text (GTK_EDITABLE (path_entry), real_name, -1, &offset);
|
|
|
|
/* move the cursor to the end of the text entry */
|
|
gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
|
|
|
|
/* cleanup */
|
|
g_object_unref (G_OBJECT (file));
|
|
g_free (real_name);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_parse (ThunarPathEntry *path_entry,
|
|
gchar **folder_part,
|
|
gchar **file_part,
|
|
GError **error)
|
|
{
|
|
const gchar *last_slash;
|
|
gchar *filename;
|
|
gchar *path;
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_PATH_ENTRY (path_entry), FALSE);
|
|
_thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
_thunar_return_val_if_fail (folder_part != NULL, FALSE);
|
|
_thunar_return_val_if_fail (file_part != NULL, FALSE);
|
|
|
|
/* expand the filename */
|
|
filename = thunar_util_expand_filename (gtk_entry_get_text (GTK_ENTRY (path_entry)),
|
|
path_entry->working_directory,
|
|
error);
|
|
if (G_UNLIKELY (filename == NULL))
|
|
return FALSE;
|
|
|
|
/* lookup the last slash character in the filename */
|
|
last_slash = strrchr (filename, G_DIR_SEPARATOR);
|
|
if (G_UNLIKELY (last_slash == NULL))
|
|
{
|
|
/* no slash character, it's relative to the home dir */
|
|
*file_part = g_filename_from_utf8 (filename, -1, NULL, NULL, error);
|
|
if (G_LIKELY (*file_part != NULL))
|
|
*folder_part = g_strdup (xfce_get_homedir ());
|
|
}
|
|
else
|
|
{
|
|
if (G_LIKELY (last_slash != filename))
|
|
*folder_part = g_filename_from_utf8 (filename, last_slash - filename, NULL, NULL, error);
|
|
else
|
|
*folder_part = g_strdup ("/");
|
|
|
|
if (G_LIKELY (*folder_part != NULL))
|
|
{
|
|
/* if folder_part doesn't start with '/', it's relative to the home dir */
|
|
if (G_UNLIKELY (**folder_part != G_DIR_SEPARATOR))
|
|
{
|
|
path = xfce_get_homefile (*folder_part, NULL);
|
|
g_free (*folder_part);
|
|
*folder_part = path;
|
|
}
|
|
|
|
/* determine the file part */
|
|
*file_part = g_filename_from_utf8 (last_slash + 1, -1, NULL, NULL, error);
|
|
if (G_UNLIKELY (*file_part == NULL))
|
|
{
|
|
g_free (*folder_part);
|
|
*folder_part = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "%s", g_strerror (EINVAL));
|
|
}
|
|
}
|
|
|
|
/* release the filename */
|
|
g_free (filename);
|
|
|
|
return (*folder_part != NULL);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_queue_check_completion (ThunarPathEntry *path_entry)
|
|
{
|
|
if (G_LIKELY (path_entry->check_completion_idle_id == 0))
|
|
{
|
|
path_entry->check_completion_idle_id = g_idle_add_full (G_PRIORITY_HIGH, thunar_path_entry_check_completion_idle,
|
|
path_entry, thunar_path_entry_check_completion_idle_destroy);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static gboolean
|
|
thunar_path_entry_check_completion_idle (gpointer user_data)
|
|
{
|
|
ThunarPathEntry *path_entry = THUNAR_PATH_ENTRY (user_data);
|
|
const gchar *text;
|
|
|
|
/* check if the user entered at least part of a filename */
|
|
text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
if (*text != '\0' && text[strlen (text) - 1] != '/')
|
|
{
|
|
/* automatically insert the common prefix */
|
|
thunar_path_entry_common_prefix_append (path_entry, TRUE);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
thunar_path_entry_check_completion_idle_destroy (gpointer user_data)
|
|
{
|
|
THUNAR_PATH_ENTRY (user_data)->check_completion_idle_id = 0;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_path_entry_new:
|
|
*
|
|
* Allocates a new #ThunarPathEntry instance.
|
|
*
|
|
* Return value: the newly allocated #ThunarPathEntry.
|
|
**/
|
|
GtkWidget *
|
|
thunar_path_entry_new (void)
|
|
{
|
|
return g_object_new (THUNAR_TYPE_PATH_ENTRY, NULL);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_path_entry_get_current_file:
|
|
* @path_entry : a #ThunarPathEntry.
|
|
*
|
|
* Returns the #ThunarFile currently being displayed by
|
|
* @path_entry or %NULL if @path_entry doesn't contain
|
|
* a valid #ThunarFile.
|
|
*
|
|
* Return value: the #ThunarFile for @path_entry or %NULL.
|
|
**/
|
|
ThunarFile *
|
|
thunar_path_entry_get_current_file (ThunarPathEntry *path_entry)
|
|
{
|
|
_thunar_return_val_if_fail (THUNAR_IS_PATH_ENTRY (path_entry), NULL);
|
|
return path_entry->current_file;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_path_entry_set_current_file:
|
|
* @path_entry : a #ThunarPathEntry.
|
|
* @current_file : a #ThunarFile or %NULL.
|
|
*
|
|
* Sets the #ThunarFile that should be displayed by
|
|
* @path_entry to @current_file.
|
|
**/
|
|
void
|
|
thunar_path_entry_set_current_file (ThunarPathEntry *path_entry,
|
|
ThunarFile *current_file)
|
|
{
|
|
GFile *file;
|
|
gchar *text;
|
|
gchar *unescaped;
|
|
gchar *tmp;
|
|
gboolean is_uri = FALSE;
|
|
|
|
_thunar_return_if_fail (THUNAR_IS_PATH_ENTRY (path_entry));
|
|
_thunar_return_if_fail (current_file == NULL || THUNAR_IS_FILE (current_file));
|
|
|
|
file = (current_file != NULL) ? thunar_file_get_file (current_file) : NULL;
|
|
if (G_UNLIKELY (file == NULL))
|
|
{
|
|
/* invalid file */
|
|
text = g_strdup ("");
|
|
}
|
|
else
|
|
{
|
|
/* check if the file is native to the platform */
|
|
if (g_file_is_native (file))
|
|
{
|
|
/* it is, try the local path first */
|
|
text = g_file_get_path (file);
|
|
|
|
/* if there is no local path, use the URI (which always works) */
|
|
if (text == NULL)
|
|
{
|
|
text = g_file_get_uri (file);
|
|
is_uri = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* not a native file, use the URI */
|
|
text = g_file_get_uri (file);
|
|
is_uri = TRUE;
|
|
}
|
|
|
|
/* if the file is a directory, end with a / to avoid loading the parent
|
|
* directory which is probably not something the user wants */
|
|
if (thunar_file_is_directory (current_file)
|
|
&& !g_str_has_suffix (text, "/"))
|
|
{
|
|
tmp = g_strconcat (text, "/", NULL);
|
|
g_free (text);
|
|
text = tmp;
|
|
}
|
|
|
|
// convert filename into valid UTF-8 string for display
|
|
tmp = text;
|
|
text = g_filename_display_name (tmp);
|
|
g_free (tmp);
|
|
}
|
|
|
|
if (is_uri)
|
|
unescaped = g_uri_unescape_string (text, NULL);
|
|
else
|
|
unescaped = g_strdup (text);
|
|
g_free (text);
|
|
|
|
/* setup the entry text */
|
|
gtk_entry_set_text (GTK_ENTRY (path_entry), unescaped);
|
|
g_free (unescaped);
|
|
|
|
/* update the icon */
|
|
thunar_path_entry_update_icon (path_entry);
|
|
|
|
gtk_editable_set_position (GTK_EDITABLE (path_entry), -1);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (path_entry));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_path_entry_set_working_directory:
|
|
* @path_entry : a #ThunarPathEntry.
|
|
* @working_directory : a #ThunarFile or %NULL.
|
|
*
|
|
* Sets the #ThunarFile that should be used as the
|
|
* working directory for @path_entry.
|
|
**/
|
|
void
|
|
thunar_path_entry_set_working_directory (ThunarPathEntry *path_entry,
|
|
ThunarFile *working_directory)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_PATH_ENTRY (path_entry));
|
|
_thunar_return_if_fail (working_directory == NULL || THUNAR_IS_FILE (working_directory));
|
|
|
|
if (G_LIKELY (path_entry->working_directory != NULL))
|
|
g_object_unref (path_entry->working_directory);
|
|
|
|
path_entry->working_directory = NULL;
|
|
|
|
if (THUNAR_IS_FILE (working_directory))
|
|
path_entry->working_directory = g_object_ref (thunar_file_get_file (working_directory));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* thunar_path_entry_get_search_query:
|
|
* @path_entry : a #ThunarPathEntry.
|
|
*
|
|
* Returns a copy of the search query in the text field of the @path_entry or "" if the path_entry doesn't contain
|
|
* a search query.
|
|
*
|
|
* It's the responsibility of the caller to free the returned string using `g_free`.
|
|
**/
|
|
gchar *
|
|
thunar_path_entry_get_search_query (ThunarPathEntry *path_entry)
|
|
{
|
|
unsigned long search_prefix_length;
|
|
const gchar *text = gtk_entry_get_text (GTK_ENTRY (path_entry));
|
|
search_prefix_length = strlen (thunar_util_get_search_prefix ());
|
|
|
|
_thunar_return_val_if_fail (THUNAR_IS_PATH_ENTRY (path_entry), NULL);
|
|
_thunar_return_val_if_fail (strncmp (text, thunar_util_get_search_prefix (), search_prefix_length) == 0, NULL);
|
|
|
|
return strlen (text) > search_prefix_length ? g_strdup (&text[search_prefix_length]) : g_strdup ("");
|
|
}
|
|
|
|
|
|
|
|
void
|
|
thunar_path_entry_cancel_search (ThunarPathEntry *path_entry)
|
|
{
|
|
_thunar_return_if_fail (THUNAR_IS_PATH_ENTRY (path_entry));
|
|
|
|
path_entry->search_mode = FALSE;
|
|
|
|
gtk_widget_grab_focus (gtk_widget_get_parent (GTK_WIDGET (path_entry)));
|
|
}
|