diff options
-rw-r--r-- | ChangeLog | 13 | ||||
-rw-r--r-- | po/POTFILES.in | 6 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/nautilus-location-bar.c | 355 | ||||
-rw-r--r-- | src/nautilus-location-dialog.c | 191 | ||||
-rw-r--r-- | src/nautilus-location-dialog.h | 51 | ||||
-rw-r--r-- | src/nautilus-location-entry.c | 512 | ||||
-rw-r--r-- | src/nautilus-location-entry.h | 54 | ||||
-rw-r--r-- | src/nautilus-spatial-window.c | 13 |
9 files changed, 848 insertions, 351 deletions
@@ -1,3 +1,16 @@ +2003-10-11 Dave Camp <dave@ximian.com> + + * src/Makefile.am: Add nautilus-location-dialog.[ch] and + nautilus-location-entry.[ch] + * src/nautilus-location-bar.c: (editable_event_after_callback), + (nautilus_location_bar_init): + * src/nautilus-location-entry.c: Moved the tab-completing entry + into its own widget. + * src/nautilus-location-dialog.c + * src/nautilus-spatial-window.c: (real_prompt_for_location), + (nautilus_spatial_window_class_init): Add an Open Location + dialog for the spatial windows. + 2003-10-10 Christophe Fergeau <teuf@gnome.org> * components/tree/nautilus-tree-view.c : expand rows in the diff --git a/po/POTFILES.in b/po/POTFILES.in index 15dd0def5..23e8f4463 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -98,6 +98,8 @@ src/nautilus-file-management-properties.glade src/nautilus-first-time-druid.c src/nautilus-information-panel.c src/nautilus-location-bar.c +src/nautilus-location-dialog.c +src/nautilus-location-entry.c src/nautilus-main.c src/nautilus-navigation-window-menus.c src/nautilus-navigation-window-ui.xml @@ -113,8 +115,12 @@ src/nautilus-shell.c src/nautilus-side-pane.c src/nautilus-sidebar-title.c src/nautilus-simple-search-bar.c +<<<<<<< POTFILES.in +src/nautilus-spatial-window.c +======= src/nautilus-spatial-window-ui.xml src/nautilus-spatial-window.c +>>>>>>> 1.132 src/nautilus-switchable-search-bar.c src/nautilus-view-frame.c src/nautilus-window-manage-views.c diff --git a/src/Makefile.am b/src/Makefile.am index 08f523c84..c17871e5f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -69,6 +69,8 @@ nautilus_SOURCES = \ nautilus-first-time-druid.c \ nautilus-information-panel.c \ nautilus-location-bar.c \ + nautilus-location-dialog.c \ + nautilus-location-entry.c \ nautilus-main.c \ nautilus-navigation-bar.c \ nautilus-navigation-window.c \ @@ -104,6 +106,8 @@ nautilus_SOURCES = \ nautilus-first-time-druid.h \ nautilus-information-panel.h \ nautilus-location-bar.h \ + nautilus-location-dialog.h \ + nautilus-location-entry.h \ nautilus-main.h \ nautilus-navigation-bar.h \ nautilus-navigation-window.h \ diff --git a/src/nautilus-location-bar.c b/src/nautilus-location-bar.c index 8efbd45c5..fd4092dfd 100644 --- a/src/nautilus-location-bar.c +++ b/src/nautilus-location-bar.c @@ -33,14 +33,15 @@ #include <config.h> #include "nautilus-location-bar.h" +#include "nautilus-location-entry.h" #include "nautilus-window-private.h" #include "nautilus-window.h" #include <eel/eel-accessibility.h> #include <eel/eel-glib-extensions.h> #include <eel/eel-gtk-macros.h> +#include <eel/eel-input-event-box.h> #include <eel/eel-stock-dialogs.h> #include <eel/eel-string.h> -#include <eel/eel-input-event-box.h> #include <eel/eel-vfs-extensions.h> #include <gtk/gtkdnd.h> #include <gtk/gtksignal.h> @@ -48,7 +49,6 @@ #include <libgnomeui/gnome-stock-icons.h> #include <libgnomeui/gnome-uidefs.h> #include <libgnomevfs/gnome-vfs.h> -#include <libnautilus-private/nautilus-entry.h> #include <libnautilus-private/nautilus-icon-dnd.h> #include <libnautilus/nautilus-clipboard.h> #include <stdio.h> @@ -250,290 +250,6 @@ style_set_handler (GtkWidget *widget, GtkStyle *previous_style) gtk_widget_set_size_request (widget, width, -1); } -static gboolean -have_broken_filenames (void) -{ - static gboolean initialized = FALSE; - static gboolean broken; - - if (initialized) { - return broken; - } - - broken = g_getenv ("G_BROKEN_FILENAMES") != NULL; - - initialized = TRUE; - - return broken; -} - - -/* utility routine to determine the string to expand to. If we don't have anything yet, accept - the whole string, otherwise accept the largest part common to both */ - -static char * -accumulate_name_utf8 (char *full_name, char *candidate_name) -{ - char *result_name, *str1, *str2; - - if (!g_utf8_validate (candidate_name, -1, NULL)) { - return full_name; - } - - if (full_name == NULL) { - result_name = g_strdup (candidate_name); - } else { - result_name = full_name; - if (!eel_str_has_prefix (full_name, candidate_name)) { - str1 = full_name; - str2 = candidate_name; - - while ((g_utf8_get_char (str1) == g_utf8_get_char (str2))) { - str1 = g_utf8_next_char (str1); - str2 = g_utf8_next_char (str2); - } - *str1 = '\0'; - } - } - - return result_name; -} - -static char * -accumulate_name_locale (char *full_name, char *candidate_name) -{ - char *result_name, *str1, *str2; - - if (full_name == NULL) - result_name = g_strdup (candidate_name); - else { - result_name = full_name; - if (!eel_str_has_prefix (full_name, candidate_name)) { - str1 = full_name; - str2 = candidate_name; - - while (*str1 == *str2) { - str1++; - str2++; - } - *str1 = '\0'; - } - } - - return result_name; -} - -/* utility routine to load the file info list for the current directory, if necessary */ -static void -get_file_info_list (NautilusLocationBar *bar, const char* dir_name) -{ - GnomeVFSResult result; - - if (eel_strcmp (bar->details->current_directory, dir_name) != 0) { - g_free (bar->details->current_directory); - if (bar->details->file_info_list) { - gnome_vfs_file_info_list_free (bar->details->file_info_list); - bar->details->file_info_list = NULL; - } - - bar->details->current_directory = g_strdup (dir_name); - result = gnome_vfs_directory_list_load (&bar->details->file_info_list, dir_name, - GNOME_VFS_FILE_INFO_DEFAULT); - if (result != GNOME_VFS_OK) { - if (bar->details->file_info_list) { - gnome_vfs_file_info_list_free (bar->details->file_info_list); - bar->details->file_info_list = NULL; - } - } - } -} - -/* routine that performs the tab expansion using gnome-vfs. 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) -{ - NautilusLocationBar *bar; - - GnomeVFSFileInfo *current_file_info; - GList *element; - GnomeVFSURI *uri; - GtkEditable *editable; - - char *base_name_uri_escaped; - char *base_name; - char *base_name_utf8; - char *user_location; - char *current_path; - char *dir_name; - char *expand_text; - char *expand_text_utf8; - char *expand_name; - char *insert_text; - - int base_name_length; - int user_location_length; - int expand_text_length; - int pos; - - bar = NAUTILUS_LOCATION_BAR (callback_data); - editable = GTK_EDITABLE (bar->details->entry); - user_location = gtk_editable_get_chars (editable, 0, -1); - bar->details->idle_id = 0; - - /* if it's just '~' don't expand because slash shouldn't be appended */ - if (eel_strcmp (user_location, "~") == 0) { - g_free (user_location); - return FALSE; - } - - /* Trailing whitespace is OK here since the cursor is known to - be at the end of the text and therefor after the whitespace. */ - current_path = eel_make_uri_from_input_with_trailing_ws (user_location); - if (!eel_istr_has_prefix (current_path, "file://")) { - g_free (user_location); - g_free (current_path); - return FALSE; - } - - /* We already completed if we have a trailing '/' */ - if (current_path[strlen (current_path) - 1] == GNOME_VFS_URI_PATH_CHR) { - g_free (user_location); - g_free (current_path); - return FALSE; - } - - user_location_length = g_utf8_strlen (user_location, -1); - - g_free (user_location); - - uri = gnome_vfs_uri_new (current_path); - - base_name_uri_escaped = gnome_vfs_uri_extract_short_name (uri); - if (base_name_uri_escaped == NULL) { - base_name = NULL; - } else { - base_name = gnome_vfs_unescape_string (base_name_uri_escaped, NULL); - } - g_free (base_name_uri_escaped); - - if (base_name == NULL) { - gnome_vfs_uri_unref (uri); - g_free (current_path); - return FALSE; - } - - dir_name = gnome_vfs_uri_extract_dirname (uri); - - gnome_vfs_uri_unref (uri); - uri = NULL; - - /* get file info for the directory, if it hasn't changed since last time */ - get_file_info_list (bar, dir_name); - if (bar->details->file_info_list == NULL) { - g_free (dir_name); - g_free (base_name); - g_free (current_path); - return FALSE; - } - - /* iterate through the directory, keeping the intersection of all the names that - have the current basename as a prefix. */ - expand_text = NULL; - for (element = bar->details->file_info_list; element != NULL; element = element->next) { - current_file_info = element->data; - if (eel_str_has_prefix (current_file_info->name, base_name)) { - if (current_file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) { - expand_name = g_strconcat (current_file_info->name, "/", NULL); - } else { - expand_name = g_strdup (current_file_info->name); - } - if (have_broken_filenames()) { - expand_text = accumulate_name_locale (expand_text, expand_name); - } else { - expand_text = accumulate_name_utf8 (expand_text, expand_name); - } - g_free (expand_name); - } - } - - if (have_broken_filenames ()) { - if (expand_text) { - expand_text_utf8 = g_locale_to_utf8 (expand_text, -1, NULL, NULL, NULL); - g_free (expand_text); - expand_text = expand_text_utf8; - } - - base_name_utf8 = g_locale_to_utf8 (base_name, -1, NULL, NULL, NULL); - g_free (base_name); - base_name = base_name_utf8; - } - - /* if we've got something, add it to the entry */ - if (expand_text != NULL && base_name != NULL) { - expand_text_length = g_utf8_strlen (expand_text, -1); - base_name_length = g_utf8_strlen (base_name, -1); - - if (!eel_str_has_suffix (base_name, expand_text) - && base_name_length < expand_text_length) { - insert_text = g_utf8_offset_to_pointer (expand_text, base_name_length); - pos = user_location_length; - gtk_editable_insert_text (editable, - insert_text, - g_utf8_strlen (insert_text, -1), - &pos); - - pos = user_location_length; - gtk_editable_select_region (editable, pos, -1); - } - } - g_free (expand_text); - - g_free (dir_name); - g_free (base_name); - g_free (current_path); - - 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) { @@ -556,71 +272,12 @@ set_position_and_selection_to_end (GtkEditable *editable) 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 editable_event_after_callback (GtkEntry *entry, GdkEvent *event, gpointer user_data) { - GtkEditable *editable; - GdkEventKey *keyevent; - NautilusLocationBar *bar; - - if (event->type != GDK_KEY_PRESS) { - return; - } - - editable = GTK_EDITABLE (entry); - keyevent = (GdkEventKey *)event; - bar = NAUTILUS_LOCATION_BAR (user_data); - - /* 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 (bar->details->idle_id == 0) { - bar->details->idle_id = g_idle_add (try_to_expand_path, bar); - } - } - } else { - /* FIXME: Also might be good to do this when you click - * to change the position or selection. - */ - if (bar->details->idle_id != 0) { - g_source_remove (bar->details->idle_id); - bar->details->idle_id = 0; - } - } - - nautilus_location_bar_update_label (bar); + nautilus_location_bar_update_label (NAUTILUS_LOCATION_BAR (user_data)); } static void @@ -721,15 +378,13 @@ nautilus_location_bar_init (NautilusLocationBar *bar) gtk_box_pack_start (GTK_BOX (hbox), event_box, FALSE, TRUE, GNOME_PAD_SMALL); - entry = nautilus_entry_new (); - - nautilus_entry_set_special_tab_handling (NAUTILUS_ENTRY (entry), TRUE); + entry = nautilus_location_entry_new (); g_signal_connect_object (entry, "activate", G_CALLBACK (nautilus_navigation_bar_location_changed), bar, G_CONNECT_SWAPPED); g_signal_connect_object (entry, "event_after", - G_CALLBACK (editable_event_after_callback), bar, 0); + G_CALLBACK (editable_event_after_callback), bar, G_CONNECT_AFTER); gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); diff --git a/src/nautilus-location-dialog.c b/src/nautilus-location-dialog.c new file mode 100644 index 000000000..ebbe30f6f --- /dev/null +++ b/src/nautilus-location-dialog.c @@ -0,0 +1,191 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Nautilus + * + * Copyright (C) 2003 Ximian, Inc. + * + * Nautilus 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. + * + * Nautilus 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. + */ + +#include <config.h> +#include "nautilus-location-dialog.h" + +#include <eel/eel-gtk-macros.h> +#include <eel/eel-vfs-extensions.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkstock.h> +#include "nautilus-location-entry.h" + +struct _NautilusLocationDialogDetails { + GtkWidget *entry; + NautilusWindow *window; +}; + +static void nautilus_location_dialog_class_init (NautilusLocationDialogClass *class); +static void nautilus_location_dialog_init (NautilusLocationDialog *dialog); + +EEL_CLASS_BOILERPLATE (NautilusLocationDialog, + nautilus_location_dialog, + GTK_TYPE_DIALOG) +enum { + RESPONSE_OPEN, + RESPONSE_CANCEL +}; + +static void +nautilus_location_dialog_finalize (GObject *object) +{ + NautilusLocationDialog *dialog; + + dialog = NAUTILUS_LOCATION_DIALOG (object); + + g_free (dialog->details); + + EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void +nautilus_location_dialog_destroy (GtkObject *object) +{ + NautilusLocationDialog *dialog; + + dialog = NAUTILUS_LOCATION_DIALOG (object); + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static void +open_current_location (NautilusLocationDialog *dialog) +{ + char *uri; + char *user_location; + + user_location = gtk_editable_get_chars (GTK_EDITABLE (dialog->details->entry), 0, -1); + uri = eel_make_uri_from_input (user_location); + g_free (user_location); + + nautilus_window_go_to (dialog->details->window, uri); + + g_free (uri); +} + +static void +response_callback (NautilusLocationDialog *dialog, + int response_id, + gpointer data) +{ + switch (response_id) { + case RESPONSE_OPEN : + open_current_location (dialog); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_NONE : + case GTK_RESPONSE_DELETE_EVENT : + case RESPONSE_CANCEL : + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + default : + g_assert_not_reached (); + } +} + +static void +entry_activate_callback (GtkEntry *entry, + gpointer user_data) +{ + NautilusLocationDialog *dialog; + + dialog = NAUTILUS_LOCATION_DIALOG (user_data); + gtk_dialog_response (GTK_DIALOG (dialog), RESPONSE_OPEN); +} + +static void +nautilus_location_dialog_class_init (NautilusLocationDialogClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = nautilus_location_dialog_finalize; + + object_class = GTK_OBJECT_CLASS (class); + object_class->destroy = nautilus_location_dialog_destroy; +} + +static void +nautilus_location_dialog_init (NautilusLocationDialog *dialog) +{ + GtkWidget *box; + GtkWidget *label; + + dialog->details = g_new0 (NautilusLocationDialogDetails, 1); + + gtk_window_set_title (GTK_WINDOW (dialog), _("Open Location")); + gtk_window_set_default_size (GTK_WINDOW (dialog), 300, -1); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + box = gtk_hbox_new (FALSE, 6); + gtk_widget_show (box); + + label = gtk_label_new (_("Location:")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 6); + + dialog->details->entry = nautilus_location_entry_new (); + g_signal_connect (dialog->details->entry, + "activate", + G_CALLBACK (entry_activate_callback), + dialog); + + gtk_widget_show (dialog->details->entry); + + gtk_box_pack_start (GTK_BOX (box), dialog->details->entry, + TRUE, TRUE, 6); + + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + box, TRUE, TRUE, 12); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, + RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_OPEN, + RESPONSE_OPEN); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + RESPONSE_OPEN); + + g_signal_connect (dialog, "response", + G_CALLBACK (response_callback), + dialog); +} + +GtkWidget * +nautilus_location_dialog_new (NautilusWindow *window) +{ + GtkWidget *dialog; + + dialog = gtk_widget_new (NAUTILUS_TYPE_LOCATION_DIALOG, NULL); + + if (window) { + gtk_window_set_screen (GTK_WINDOW (dialog), + gtk_window_get_screen (GTK_WINDOW (window))); + NAUTILUS_LOCATION_DIALOG (dialog)->details->window = window; + } + + return dialog; +} diff --git a/src/nautilus-location-dialog.h b/src/nautilus-location-dialog.h new file mode 100644 index 000000000..5368f4087 --- /dev/null +++ b/src/nautilus-location-dialog.h @@ -0,0 +1,51 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Nautilus + * + * Copyright (C) 2003 Ximian, Inc. + * + * Nautilus 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. + * + * Nautilus 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. + */ + +#ifndef NAUTILUS_LOCATION_DIALOG_H +#define NAUTILUS_LOCATION_DIALOG_H + +#include <gtk/gtkdialog.h> +#include "nautilus-window.h" + +#define NAUTILUS_TYPE_LOCATION_DIALOG (nautilus_location_dialog_get_type ()) +#define NAUTILUS_LOCATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NAUTILUS_TYPE_LOCATION_DIALOG, NautilusLocationDialog)) +#define NAUTILUS_LOCATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_LOCATION_DIALOG, NautilusLocationDialogClass)) +#define NAUTILUS_IS_LOCATION_DIALOG(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), NAUTILUS_TYPE_LOCATION_DIALOG) + +typedef struct _NautilusLocationDialog NautilusLocationDialog; +typedef struct _NautilusLocationDialogClass NautilusLocationDialogClass; +typedef struct _NautilusLocationDialogDetails NautilusLocationDialogDetails; + +struct _NautilusLocationDialog { + GtkDialog parent; + NautilusLocationDialogDetails *details; +}; + +struct _NautilusLocationDialogClass { + GtkDialogClass parent_class; +}; + +GType nautilus_location_dialog_get_type (void); +GtkWidget* nautilus_location_dialog_new (NautilusWindow *window); + +#endif /* NAUTILUS_LOCATION_DIALOG_H */ diff --git a/src/nautilus-location-entry.c b/src/nautilus-location-entry.c new file mode 100644 index 000000000..623f2fa99 --- /dev/null +++ b/src/nautilus-location-entry.c @@ -0,0 +1,512 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Nautilus + * + * Copyright (C) 2000 Eazel, Inc. + * + * Nautilus 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. + * + * Nautilus 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 <mjs@eazel.com> + * Ettore Perazzoli <ettore@gnu.org> + * Michael Meeks <michael@nuclecu.unam.mx> + * Andy Hertzfeld <andy@eazel.com> + * + */ + +/* nautilus-location-bar.c - Location bar for Nautilus + */ + +#include <config.h> +#include "nautilus-location-entry.h" + +#include "nautilus-window-private.h" +#include "nautilus-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 <eel/eel-input-event-box.h> +#include <eel/eel-vfs-extensions.h> +#include <gtk/gtkdnd.h> +#include <gtk/gtksignal.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-stock-icons.h> +#include <libgnomeui/gnome-uidefs.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libnautilus-private/nautilus-entry.h> +#include <libnautilus-private/nautilus-icon-dnd.h> +#include <libnautilus/nautilus-clipboard.h> +#include <stdio.h> +#include <string.h> + +struct NautilusLocationEntryDetails { + GtkLabel *label; + + char *current_directory; + GList *file_info_list; + + guint idle_id; +}; + +static void nautilus_location_entry_class_init (NautilusLocationEntryClass *class); +static void nautilus_location_entry_init (NautilusLocationEntry *entry); + +EEL_CLASS_BOILERPLATE (NautilusLocationEntry, + nautilus_location_entry, + NAUTILUS_TYPE_ENTRY) + +static gboolean +have_broken_filenames (void) +{ + static gboolean initialized = FALSE; + static gboolean broken; + + if (initialized) { + return broken; + } + + broken = g_getenv ("G_BROKEN_FILENAMES") != NULL; + + initialized = TRUE; + + return broken; +} + + +/* utility routine to determine the string to expand to. If we don't have anything yet, accept + the whole string, otherwise accept the largest part common to both */ + +static char * +accumulate_name_utf8 (char *full_name, char *candidate_name) +{ + char *result_name, *str1, *str2; + + if (!g_utf8_validate (candidate_name, -1, NULL)) { + return full_name; + } + + if (full_name == NULL) { + result_name = g_strdup (candidate_name); + } else { + result_name = full_name; + if (!eel_str_has_prefix (full_name, candidate_name)) { + str1 = full_name; + str2 = candidate_name; + + while ((g_utf8_get_char (str1) == g_utf8_get_char (str2))) { + str1 = g_utf8_next_char (str1); + str2 = g_utf8_next_char (str2); + } + *str1 = '\0'; + } + } + + return result_name; +} + +static char * +accumulate_name_locale (char *full_name, char *candidate_name) +{ + char *result_name, *str1, *str2; + + if (full_name == NULL) + result_name = g_strdup (candidate_name); + else { + result_name = full_name; + if (!eel_str_has_prefix (full_name, candidate_name)) { + str1 = full_name; + str2 = candidate_name; + + while (*str1 == *str2) { + str1++; + str2++; + } + *str1 = '\0'; + } + } + + return result_name; +} + +/* utility routine to load the file info list for the current directory, if necessary */ +static void +get_file_info_list (NautilusLocationEntry *entry, const char* dir_name) +{ + GnomeVFSResult result; + + if (eel_strcmp (entry->details->current_directory, dir_name) != 0) { + g_free (entry->details->current_directory); + if (entry->details->file_info_list) { + gnome_vfs_file_info_list_free (entry->details->file_info_list); + entry->details->file_info_list = NULL; + } + + entry->details->current_directory = g_strdup (dir_name); + result = gnome_vfs_directory_list_load (&entry->details->file_info_list, dir_name, + GNOME_VFS_FILE_INFO_DEFAULT); + if (result != GNOME_VFS_OK) { + if (entry->details->file_info_list) { + gnome_vfs_file_info_list_free (entry->details->file_info_list); + entry->details->file_info_list = NULL; + } + } + } +} + +/* routine that performs the tab expansion using gnome-vfs. 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) +{ + NautilusLocationEntry *entry; + + GnomeVFSFileInfo *current_file_info; + GList *element; + GnomeVFSURI *uri; + GtkEditable *editable; + + char *base_name_uri_escaped; + char *base_name; + char *base_name_utf8; + char *user_location; + char *current_path; + char *dir_name; + char *expand_text; + char *expand_text_utf8; + char *expand_name; + char *insert_text; + + int base_name_length; + int user_location_length; + int expand_text_length; + int pos; + + entry = NAUTILUS_LOCATION_ENTRY (callback_data); + editable = GTK_EDITABLE (entry); + user_location = gtk_editable_get_chars (editable, 0, -1); + entry->details->idle_id = 0; + + /* if it's just '~' don't expand because slash shouldn't be appended */ + if (eel_strcmp (user_location, "~") == 0) { + g_free (user_location); + return FALSE; + } + + /* Trailing whitespace is OK here since the cursor is known to + be at the end of the text and therefor after the whitespace. */ + current_path = eel_make_uri_from_input_with_trailing_ws (user_location); + if (!eel_istr_has_prefix (current_path, "file://")) { + g_free (user_location); + g_free (current_path); + return FALSE; + } + + /* We already completed if we have a trailing '/' */ + if (current_path[strlen (current_path) - 1] == GNOME_VFS_URI_PATH_CHR) { + g_free (user_location); + g_free (current_path); + return FALSE; + } + + user_location_length = g_utf8_strlen (user_location, -1); + + g_free (user_location); + + uri = gnome_vfs_uri_new (current_path); + + base_name_uri_escaped = gnome_vfs_uri_extract_short_name (uri); + if (base_name_uri_escaped == NULL) { + base_name = NULL; + } else { + base_name = gnome_vfs_unescape_string (base_name_uri_escaped, NULL); + } + g_free (base_name_uri_escaped); + + if (base_name == NULL) { + gnome_vfs_uri_unref (uri); + g_free (current_path); + return FALSE; + } + + dir_name = gnome_vfs_uri_extract_dirname (uri); + + gnome_vfs_uri_unref (uri); + uri = NULL; + + /* get file info for the directory, if it hasn't changed since last time */ + get_file_info_list (entry, dir_name); + if (entry->details->file_info_list == NULL) { + g_free (dir_name); + g_free (base_name); + g_free (current_path); + return FALSE; + } + + /* iterate through the directory, keeping the intersection of all the names that + have the current basename as a prefix. */ + expand_text = NULL; + for (element = entry->details->file_info_list; element != NULL; element = element->next) { + current_file_info = element->data; + if (eel_str_has_prefix (current_file_info->name, base_name)) { + if (current_file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) { + expand_name = g_strconcat (current_file_info->name, "/", NULL); + } else { + expand_name = g_strdup (current_file_info->name); + } + if (have_broken_filenames()) { + expand_text = accumulate_name_locale (expand_text, expand_name); + } else { + expand_text = accumulate_name_utf8 (expand_text, expand_name); + } + g_free (expand_name); + } + } + + if (have_broken_filenames ()) { + if (expand_text) { + expand_text_utf8 = g_locale_to_utf8 (expand_text, -1, NULL, NULL, NULL); + g_free (expand_text); + expand_text = expand_text_utf8; + } + + base_name_utf8 = g_locale_to_utf8 (base_name, -1, NULL, NULL, NULL); + g_free (base_name); + base_name = base_name_utf8; + } + + /* if we've got something, add it to the entry */ + if (expand_text != NULL && base_name != NULL) { + expand_text_length = g_utf8_strlen (expand_text, -1); + base_name_length = g_utf8_strlen (base_name, -1); + + if (!eel_str_has_suffix (base_name, expand_text) + && base_name_length < expand_text_length) { + insert_text = g_utf8_offset_to_pointer (expand_text, base_name_length); + pos = user_location_length; + gtk_editable_insert_text (editable, + insert_text, + g_utf8_strlen (insert_text, -1), + &pos); + + pos = user_location_length; + gtk_editable_select_region (editable, pos, -1); + } + } + g_free (expand_text); + + g_free (dir_name); + g_free (base_name); + g_free (current_path); + + 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 +editable_event_after_callback (GtkEntry *entry, + GdkEvent *event, + gpointer user_data) +{ + GtkEditable *editable; + GdkEventKey *keyevent; + NautilusLocationEntry *location_entry; + + if (event->type != GDK_KEY_PRESS) { + return; + } + + editable = GTK_EDITABLE (entry); + keyevent = (GdkEventKey *)event; + location_entry = NAUTILUS_LOCATION_ENTRY (user_data); + + /* 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) +{ + NautilusLocationEntry *entry; + + entry = NAUTILUS_LOCATION_ENTRY (object); + + g_free (entry->details); + + EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); +} + +static void +destroy (GtkObject *object) +{ + NautilusLocationEntry *entry; + + entry = NAUTILUS_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; + } + + if (entry->details->file_info_list) { + gnome_vfs_file_info_list_free (entry->details->file_info_list); + entry->details->file_info_list = NULL; + } + + g_free (entry->details->current_directory); + entry->details->current_directory = NULL; + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static void +nautilus_location_entry_class_init (NautilusLocationEntryClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + + gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = finalize; + + object_class = GTK_OBJECT_CLASS (class); + object_class->destroy = destroy; +} + +static void +nautilus_location_entry_init (NautilusLocationEntry *entry) +{ + entry->details = g_new0 (NautilusLocationEntryDetails, 1); + + nautilus_entry_set_special_tab_handling (NAUTILUS_ENTRY (entry), TRUE); + + g_signal_connect_object (entry, "event_after", + G_CALLBACK (editable_event_after_callback), entry, 0); + +} + +GtkWidget * +nautilus_location_entry_new (void) +{ + GtkWidget *entry; + + entry = gtk_widget_new (NAUTILUS_TYPE_LOCATION_ENTRY, NULL); + + return entry; +} diff --git a/src/nautilus-location-entry.h b/src/nautilus-location-entry.h new file mode 100644 index 000000000..596f18a33 --- /dev/null +++ b/src/nautilus-location-entry.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Nautilus + * + * Copyright (C) 2000 Eazel, Inc. + * + * Nautilus 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. + * + * Nautilus 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 <mjs@eazel.com> + * Ettore Perazzoli <ettore@gnu.org> + */ + +#ifndef NAUTILUS_LOCATION_ENTRY_H +#define NAUTILUS_LOCATION_ENTRY_H + +#include <libnautilus-private/nautilus-entry.h> + +#define NAUTILUS_TYPE_LOCATION_ENTRY (nautilus_location_entry_get_type ()) +#define NAUTILUS_LOCATION_ENTRY(obj) \ + GTK_CHECK_CAST (obj, NAUTILUS_TYPE_LOCATION_ENTRY, NautilusLocationEntry) +#define NAUTILUS_LOCATION_ENTRY_CLASS(klass) \ + GTK_CHECK_CLASS_CAST (klass, NAUTILUS_TYPE_LOCATION_ENTRY, NautilusLocationEntryClass) +#define NAUTILUS_IS_LOCATION_ENTRY(obj) \ + GTK_CHECK_TYPE (obj, NAUTILUS_TYPE_LOCATION_ENTRY) + +typedef struct NautilusLocationEntryDetails NautilusLocationEntryDetails; + +typedef struct NautilusLocationEntry { + NautilusEntry parent; + NautilusLocationEntryDetails *details; +} NautilusLocationEntry; + +typedef struct { + NautilusEntryClass parent_class; +} NautilusLocationEntryClass; + +GType nautilus_location_entry_get_type (void); +GtkWidget* nautilus_location_entry_new (void); + +#endif /* NAUTILUS_LOCATION_ENTRY_H */ diff --git a/src/nautilus-spatial-window.c b/src/nautilus-spatial-window.c index 04d753143..cf608a71b 100644 --- a/src/nautilus-spatial-window.c +++ b/src/nautilus-spatial-window.c @@ -34,7 +34,7 @@ #include "nautilus-application.h" #include "nautilus-desktop-window.h" #include "nautilus-bookmarks-window.h" -#include "nautilus-information-panel.h" +#include "nautilus-location-dialog.h" #include "nautilus-main.h" #include "nautilus-signaller.h" #include "nautilus-switchable-navigation-bar.h" @@ -247,6 +247,15 @@ file_menu_close_with_parent_windows_callback (BonoboUIComponent *component, } static void +real_prompt_for_location (NautilusWindow *window) +{ + GtkWidget *dialog; + + dialog = nautilus_location_dialog_new (window); + gtk_widget_show (dialog); +} + +static void real_set_title (NautilusWindow *window, const char *title) { @@ -327,6 +336,8 @@ nautilus_spatial_window_class_init (NautilusSpatialWindowClass *class) GTK_WIDGET_CLASS (class)->configure_event = nautilus_spatial_window_configure_event; GTK_WIDGET_CLASS (class)->unrealize = nautilus_spatial_window_unrealize; + NAUTILUS_WINDOW_CLASS (class)->prompt_for_location = + real_prompt_for_location; NAUTILUS_WINDOW_CLASS (class)->set_title = real_set_title; NAUTILUS_WINDOW_CLASS (class)->merge_menus = |