diff options
Diffstat (limited to 'src/caja-location-entry.c')
-rw-r--r-- | src/caja-location-entry.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/caja-location-entry.c b/src/caja-location-entry.c new file mode 100644 index 00000000..03557337 --- /dev/null +++ b/src/caja-location-entry.c @@ -0,0 +1,485 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Caja + * + * Copyright (C) 2000 Eazel, Inc. + * + * Caja 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. + * + * Caja 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; see the file COPYING. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Maciej Stachowiak <[email protected]> + * Ettore Perazzoli <[email protected]> + * Michael Meeks <[email protected]> + * Andy Hertzfeld <[email protected]> + * + */ + +/* caja-location-bar.c - Location bar for Caja + */ + +#include <config.h> +#include "caja-location-entry.h" + +#include "caja-window-private.h" +#include "caja-window.h" +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-entry.h> +#include <libcaja-private/caja-icon-dnd.h> +#include <libcaja-private/caja-clipboard.h> +#include <stdio.h> +#include <string.h> + +struct CajaLocationEntryDetails +{ + GtkLabel *label; + + char *current_directory; + GFilenameCompleter *completer; + + guint idle_id; + + gboolean has_special_text; + gboolean setting_special_text; + gchar *special_text; + CajaLocationEntryAction secondary_action; +}; + +static void caja_location_entry_class_init (CajaLocationEntryClass *class); +static void caja_location_entry_init (CajaLocationEntry *entry); + +EEL_CLASS_BOILERPLATE (CajaLocationEntry, + caja_location_entry, + CAJA_TYPE_ENTRY) + +/* routine that performs the tab expansion. Extract the directory name and + incomplete basename, then iterate through the directory trying to complete it. If we + find something, add it to the entry */ + +static gboolean +try_to_expand_path (gpointer callback_data) +{ + CajaLocationEntry *entry; + GtkEditable *editable; + char *suffix, *user_location, *absolute_location; + int user_location_length, pos; + + entry = CAJA_LOCATION_ENTRY (callback_data); + editable = GTK_EDITABLE (entry); + user_location = gtk_editable_get_chars (editable, 0, -1); + user_location_length = g_utf8_strlen (user_location, -1); + entry->details->idle_id = 0; + + if (!g_path_is_absolute (user_location)) + { + absolute_location = g_build_filename (entry->details->current_directory, user_location, NULL); + suffix = g_filename_completer_get_completion_suffix (entry->details->completer, + absolute_location); + g_free (absolute_location); + } + else + { + suffix = g_filename_completer_get_completion_suffix (entry->details->completer, + user_location); + g_free (user_location); + } + + /* if we've got something, add it to the entry */ + if (suffix != NULL) + { + pos = user_location_length; + gtk_editable_insert_text (editable, + suffix, -1, &pos); + pos = user_location_length; + gtk_editable_select_region (editable, pos, -1); + + g_free (suffix); + } + + return FALSE; +} + +/* Until we have a more elegant solution, this is how we figure out if + * the GtkEntry inserted characters, assuming that the return value is + * TRUE indicating that the GtkEntry consumed the key event for some + * reason. This is a clone of code from GtkEntry. + */ +static gboolean +entry_would_have_inserted_characters (const GdkEventKey *event) +{ + switch (event->keyval) + { + case GDK_BackSpace: + case GDK_Clear: + case GDK_Insert: + case GDK_Delete: + case GDK_Home: + case GDK_End: + case GDK_KP_Home: + case GDK_KP_End: + case GDK_Left: + case GDK_Right: + case GDK_KP_Left: + case GDK_KP_Right: + case GDK_Return: + return FALSE; + default: + if (event->keyval >= 0x20 && event->keyval <= 0xFF) + { + if ((event->state & GDK_CONTROL_MASK) != 0) + { + return FALSE; + } + if ((event->state & GDK_MOD1_MASK) != 0) + { + return FALSE; + } + } + return event->length > 0; + } +} + +static int +get_editable_number_of_chars (GtkEditable *editable) +{ + char *text; + int length; + + text = gtk_editable_get_chars (editable, 0, -1); + length = g_utf8_strlen (text, -1); + g_free (text); + return length; +} + +static void +set_position_and_selection_to_end (GtkEditable *editable) +{ + int end; + + end = get_editable_number_of_chars (editable); + gtk_editable_select_region (editable, end, end); + gtk_editable_set_position (editable, end); +} + +static gboolean +position_and_selection_are_at_end (GtkEditable *editable) +{ + int end; + int start_sel, end_sel; + + end = get_editable_number_of_chars (editable); + if (gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel)) + { + if (start_sel != end || end_sel != end) + { + return FALSE; + } + } + return gtk_editable_get_position (editable) == end; +} + +static void +got_completion_data_callback (GFilenameCompleter *completer, + CajaLocationEntry *entry) +{ + if (entry->details->idle_id) + { + g_source_remove (entry->details->idle_id); + entry->details->idle_id = 0; + } + try_to_expand_path (entry); +} + +static void +editable_event_after_callback (GtkEntry *entry, + GdkEvent *event, + CajaLocationEntry *location_entry) +{ + GtkEditable *editable; + GdkEventKey *keyevent; + + if (event->type != GDK_KEY_PRESS) + { + return; + } + + editable = GTK_EDITABLE (entry); + keyevent = (GdkEventKey *)event; + + /* After typing the right arrow key we move the selection to + * the end, if we have a valid selection - since this is most + * likely an auto-completion. We ignore shift / control since + * they can validly be used to extend the selection. + */ + if ((keyevent->keyval == GDK_Right || keyevent->keyval == GDK_End) && + !(keyevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && + gtk_editable_get_selection_bounds (editable, NULL, NULL)) + { + set_position_and_selection_to_end (editable); + } + + /* Only do expanding when we are typing at the end of the + * text. Do the expand at idle time to avoid slowing down + * typing when the directory is large. Only trigger the expand + * when we type a key that would have inserted characters. + */ + if (position_and_selection_are_at_end (editable)) + { + if (entry_would_have_inserted_characters (keyevent)) + { + if (location_entry->details->idle_id == 0) + { + location_entry->details->idle_id = g_idle_add (try_to_expand_path, location_entry); + } + } + } + else + { + /* FIXME: Also might be good to do this when you click + * to change the position or selection. + */ + if (location_entry->details->idle_id != 0) + { + g_source_remove (location_entry->details->idle_id); + location_entry->details->idle_id = 0; + } + } +} + +static void +finalize (GObject *object) +{ + CajaLocationEntry *entry; + + entry = CAJA_LOCATION_ENTRY (object); + + g_object_unref (entry->details->completer); + g_free (entry->details->special_text); + g_free (entry->details); + + EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void +destroy (GtkObject *object) +{ + CajaLocationEntry *entry; + + entry = CAJA_LOCATION_ENTRY (object); + + /* cancel the pending idle call, if any */ + if (entry->details->idle_id != 0) + { + g_source_remove (entry->details->idle_id); + entry->details->idle_id = 0; + } + + g_free (entry->details->current_directory); + entry->details->current_directory = NULL; + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static void +caja_location_entry_text_changed (CajaLocationEntry *entry, + GParamSpec *pspec) +{ + if (entry->details->setting_special_text) + { + return; + } + + entry->details->has_special_text = FALSE; +} + +static void +caja_location_entry_icon_release (GtkEntry *gentry, + GtkEntryIconPosition position, + GdkEvent *event, + gpointer unused) +{ + switch (CAJA_LOCATION_ENTRY (gentry)->details->secondary_action) + { + case CAJA_LOCATION_ENTRY_ACTION_GOTO: + g_signal_emit_by_name (gentry, "activate", gentry); + break; + case CAJA_LOCATION_ENTRY_ACTION_CLEAR: + gtk_entry_set_text (gentry, ""); + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +caja_location_entry_focus_in (GtkWidget *widget, + GdkEventFocus *event) +{ + CajaLocationEntry *entry = CAJA_LOCATION_ENTRY (widget); + + if (entry->details->has_special_text) + { + entry->details->setting_special_text = TRUE; + gtk_entry_set_text (GTK_ENTRY (entry), ""); + entry->details->setting_special_text = FALSE; + } + + return EEL_CALL_PARENT_WITH_RETURN_VALUE (GTK_WIDGET_CLASS, focus_in_event, (widget, event)); +} + +static void +caja_location_entry_activate (GtkEntry *entry) +{ + CajaLocationEntry *loc_entry; + const gchar *entry_text; + gchar *full_path, *uri_scheme = NULL; + + loc_entry = CAJA_LOCATION_ENTRY (entry); + entry_text = gtk_entry_get_text (entry); + + if (entry_text != NULL && *entry_text != '\0') + { + uri_scheme = g_uri_parse_scheme (entry_text); + + if (!g_path_is_absolute (entry_text) && uri_scheme == NULL) + { + /* Fix non absolute paths */ + full_path = g_build_filename (loc_entry->details->current_directory, entry_text, NULL); + gtk_entry_set_text (entry, full_path); + g_free (full_path); + } + + g_free (uri_scheme); + } + + EEL_CALL_PARENT (GTK_ENTRY_CLASS, activate, (entry)); +} + +static void +caja_location_entry_class_init (CajaLocationEntryClass *class) +{ + GtkWidgetClass *widget_class; + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkEntryClass *entry_class; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->focus_in_event = caja_location_entry_focus_in; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + object_class = GTK_OBJECT_CLASS (class); + object_class->destroy = destroy; + + entry_class = GTK_ENTRY_CLASS (class); + entry_class->activate = caja_location_entry_activate; +} + +void +caja_location_entry_update_current_location (CajaLocationEntry *entry, + const char *location) +{ + g_free (entry->details->current_directory); + entry->details->current_directory = g_strdup (location); + + caja_entry_set_text (CAJA_ENTRY (entry), location); + set_position_and_selection_to_end (GTK_EDITABLE (entry)); +} + +void +caja_location_entry_set_secondary_action (CajaLocationEntry *entry, + CajaLocationEntryAction secondary_action) +{ + if (entry->details->secondary_action == secondary_action) + { + return; + } + switch (secondary_action) + { + case CAJA_LOCATION_ENTRY_ACTION_CLEAR: + gtk_entry_set_icon_from_stock (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + GTK_STOCK_CLEAR); + break; + case CAJA_LOCATION_ENTRY_ACTION_GOTO: + gtk_entry_set_icon_from_stock (GTK_ENTRY (entry), + GTK_ENTRY_ICON_SECONDARY, + GTK_STOCK_GO_FORWARD); + break; + default: + g_assert_not_reached (); + } + entry->details->secondary_action = secondary_action; +} + +static void +caja_location_entry_init (CajaLocationEntry *entry) +{ + entry->details = g_new0 (CajaLocationEntryDetails, 1); + + entry->details->completer = g_filename_completer_new (); + g_filename_completer_set_dirs_only (entry->details->completer, TRUE); + + caja_location_entry_set_secondary_action (entry, + CAJA_LOCATION_ENTRY_ACTION_CLEAR); + + caja_entry_set_special_tab_handling (CAJA_ENTRY (entry), TRUE); + + g_signal_connect (entry, "event_after", + G_CALLBACK (editable_event_after_callback), entry); + + g_signal_connect (entry, "notify::text", + G_CALLBACK (caja_location_entry_text_changed), NULL); + + g_signal_connect (entry, "icon-release", + G_CALLBACK (caja_location_entry_icon_release), NULL); + + g_signal_connect (entry->details->completer, "got_completion_data", + G_CALLBACK (got_completion_data_callback), entry); +} + +GtkWidget * +caja_location_entry_new (void) +{ + GtkWidget *entry; + + entry = gtk_widget_new (CAJA_TYPE_LOCATION_ENTRY, NULL); + + return entry; +} + +void +caja_location_entry_set_special_text (CajaLocationEntry *entry, + const char *special_text) +{ + entry->details->has_special_text = TRUE; + + g_free (entry->details->special_text); + entry->details->special_text = g_strdup (special_text); + + entry->details->setting_special_text = TRUE; + gtk_entry_set_text (GTK_ENTRY (entry), special_text); + entry->details->setting_special_text = FALSE; +} + |