1 /* GTK - The GIMP Toolkit
2 * gtkfilechooserentry.c: Entry with filename completion
3 * Copyright (C) 2003, Red Hat, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include "gtkfilechooserentry.h"
23 #include <gtk/gtkentry.h>
27 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
29 #define GTK_FILE_CHOOSER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
30 #define GTK_IS_FILE_CHOOSER_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FILE_CHOOSER_ENTRY))
31 #define GTK_FILE_CHOOSER_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FILE_CHOOSER_ENTRY, GtkFileChooserEntryClass))
33 struct _GtkFileChooserEntryClass
35 GtkEntryClass parent_class;
38 struct _GtkFileChooserEntry
40 GtkEntry parent_instance;
42 GtkFileSystem *file_system;
43 GtkFilePath *base_folder;
44 GtkFilePath *current_folder_path;
46 GSource *completion_idle;
48 GtkFileFolder *current_folder;
51 guint has_completion : 1;
54 static void gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class);
55 static void gtk_file_chooser_entry_iface_init (GtkEditableClass *iface);
56 static void gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry);
58 static void gtk_file_chooser_entry_finalize (GObject *object);
59 static gboolean gtk_file_chooser_entry_focus (GtkWidget *widget,
60 GtkDirectionType direction);
61 static void gtk_file_chooser_entry_changed (GtkEditable *editable);
62 static void gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
63 const gchar *new_text,
67 static void clear_completion_callback (GtkFileChooserEntry *chooser_entry,
70 GObjectClass *parent_class;
71 GtkEditableClass *parent_editable_iface;
74 _gtk_file_chooser_entry_get_type (void)
76 static GType file_chooser_entry_type = 0;
78 if (!file_chooser_entry_type)
80 static const GTypeInfo file_chooser_entry_info =
82 sizeof (GtkFileChooserEntryClass),
84 NULL, /* base_finalize */
85 (GClassInitFunc) gtk_file_chooser_entry_class_init,
86 NULL, /* class_finalize */
87 NULL, /* class_data */
88 sizeof (GtkFileChooserEntry),
90 (GInstanceInitFunc) gtk_file_chooser_entry_init,
93 static const GInterfaceInfo editable_info =
95 (GInterfaceInitFunc) gtk_file_chooser_entry_iface_init, /* interface_init */
96 NULL, /* interface_finalize */
97 NULL /* interface_data */
101 file_chooser_entry_type = g_type_register_static (GTK_TYPE_ENTRY, "GtkFileChooserEntry",
102 &file_chooser_entry_info, 0);
103 g_type_add_interface_static (file_chooser_entry_type,
109 return file_chooser_entry_type;
113 gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
115 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
116 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
118 parent_class = g_type_class_peek_parent (class);
120 gobject_class->finalize = gtk_file_chooser_entry_finalize;
122 widget_class->focus = gtk_file_chooser_entry_focus;
126 gtk_file_chooser_entry_iface_init (GtkEditableClass *iface)
128 parent_editable_iface = g_type_interface_peek_parent (iface);
130 iface->do_insert_text = gtk_file_chooser_entry_do_insert_text;
131 iface->changed = gtk_file_chooser_entry_changed;
135 gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
137 g_signal_connect (chooser_entry, "notify::cursor-position",
138 G_CALLBACK (clear_completion_callback), NULL);
139 g_signal_connect (chooser_entry, "notify::selection-bound",
140 G_CALLBACK (clear_completion_callback), NULL);
144 gtk_file_chooser_entry_finalize (GObject *object)
146 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
148 if (chooser_entry->current_folder)
149 g_object_unref (chooser_entry->current_folder);
151 if (chooser_entry->file_system)
152 g_object_unref (chooser_entry->file_system);
154 gtk_file_path_free (chooser_entry->base_folder);
155 gtk_file_path_free (chooser_entry->current_folder_path);
156 g_free (chooser_entry->file_part);
158 parent_class->finalize (object);
162 completion_idle_callback (GtkFileChooserEntry *chooser_entry)
164 GtkEditable *editable = GTK_EDITABLE (chooser_entry);
165 GSList *child_paths = NULL;
167 gchar *common_prefix = NULL;
168 GtkFilePath *unique_path = NULL;
170 chooser_entry->completion_idle = NULL;
172 if (strcmp (chooser_entry->file_part, "") == 0)
175 if (!chooser_entry->current_folder &&
176 chooser_entry->file_system &&
177 chooser_entry->current_folder_path)
178 chooser_entry->current_folder = gtk_file_system_get_folder (chooser_entry->file_system,
179 chooser_entry->current_folder_path,
180 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER,
181 NULL); /* NULL-GError */
182 if (chooser_entry->current_folder)
183 gtk_file_folder_list_children (chooser_entry->current_folder,
185 NULL); /* NULL-GError */
187 for (tmp_list = child_paths; tmp_list; tmp_list = tmp_list->next)
191 info = gtk_file_folder_get_info (chooser_entry->current_folder,
193 NULL); /* NULL-GError */
196 const gchar *display_name = gtk_file_info_get_display_name (info);
198 if (g_str_has_prefix (display_name, chooser_entry->file_part))
202 common_prefix = g_strdup (display_name);
203 unique_path = gtk_file_path_copy (tmp_list->data);
207 gchar *p = common_prefix;
208 const gchar *q = display_name;
210 while (*p && *p == *q)
218 gtk_file_path_free (unique_path);
223 gtk_file_info_free (info);
231 info = gtk_file_folder_get_info (chooser_entry->current_folder,
233 NULL); /* NULL-GError */
237 if (gtk_file_info_get_is_folder (info))
239 gchar *tmp = common_prefix;
240 common_prefix = g_strconcat (tmp, "/", NULL);
244 gtk_file_info_free (info);
247 gtk_file_path_free (unique_path);
250 gtk_file_paths_free (child_paths);
256 gint common_prefix_len;
259 total_len = g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (chooser_entry)), -1);
260 file_part_len = g_utf8_strlen (chooser_entry->file_part, -1);
261 common_prefix_len = g_utf8_strlen (common_prefix, -1);
263 if (common_prefix_len > file_part_len)
265 chooser_entry->in_change = TRUE;
267 pos = total_len - file_part_len;
268 gtk_editable_delete_text (editable,
270 gtk_editable_insert_text (editable,
273 gtk_editable_select_region (editable,
275 total_len - file_part_len + common_prefix_len);
277 chooser_entry->in_change = FALSE;
278 chooser_entry->has_completion = TRUE;
281 g_free (common_prefix);
288 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
289 const gchar *new_text,
290 gint new_text_length,
293 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
295 parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
297 if (!chooser_entry->in_change &&
298 *position == GTK_ENTRY (editable)->text_length &&
299 !chooser_entry->completion_idle)
301 chooser_entry->completion_idle = g_idle_source_new ();
302 g_source_set_priority (chooser_entry->completion_idle, G_PRIORITY_HIGH);
303 g_source_set_closure (chooser_entry->completion_idle,
304 g_cclosure_new_object (G_CALLBACK (completion_idle_callback),
305 G_OBJECT (chooser_entry)));
306 g_source_attach (chooser_entry->completion_idle, NULL);
311 gtk_file_chooser_entry_focus (GtkWidget *widget,
312 GtkDirectionType direction)
314 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
316 if (direction == GTK_DIR_TAB_FORWARD &&
317 GTK_WIDGET_HAS_FOCUS (widget) &&
318 chooser_entry->has_completion)
320 gtk_editable_set_position (GTK_EDITABLE (widget),
321 GTK_ENTRY (widget)->text_length);
325 return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction);
329 gtk_file_chooser_entry_changed (GtkEditable *editable)
331 GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
333 GtkFilePath *folder_path;
336 text = gtk_entry_get_text (GTK_ENTRY (editable));
338 if (!chooser_entry->file_system ||
339 !chooser_entry->base_folder ||
340 !gtk_file_system_parse (chooser_entry->file_system,
341 chooser_entry->base_folder, text,
342 &folder_path, &file_part, NULL)) /* NULL-GError */
344 folder_path = gtk_file_path_copy (chooser_entry->base_folder);
345 file_part = g_strdup ("");
348 if (chooser_entry->current_folder_path)
350 if (gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
352 if (chooser_entry->current_folder)
354 g_object_unref (chooser_entry->current_folder);
355 chooser_entry->current_folder = NULL;
359 gtk_file_path_free (chooser_entry->current_folder_path);
362 chooser_entry->current_folder_path = folder_path;
364 if (chooser_entry->file_part)
365 g_free (chooser_entry->file_part);
367 chooser_entry->file_part = file_part;
371 clear_completion_callback (GtkFileChooserEntry *chooser_entry,
374 chooser_entry->has_completion = FALSE;
378 * _gtk_file_chooser_entry_new:
380 * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry
381 * is an internal implementation widget for the GTK+ file chooser
382 * which is an entry with completion with respect to a
383 * #GtkFileSystem object.
385 * Return value: the newly created #GtkFileChooserEntry
388 _gtk_file_chooser_entry_new (void)
390 return g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
394 * _gtk_file_chooser_entry_set_file_system:
395 * @chooser_entry: a #GtkFileChooser
396 * @file_system: an object implementing #GtkFileSystem
398 * Sets the file system for @chooser_entry.
401 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
402 GtkFileSystem *file_system)
404 g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
405 g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
407 if (file_system != chooser_entry->file_system)
409 if (chooser_entry->file_system)
410 g_object_unref (chooser_entry->file_system);
412 chooser_entry->file_system = g_object_ref (file_system);
417 * _gtk_file_chooser_entry_set_base_folder:
418 * @chooser_entry: a #GtkFileChooserEntry
419 * @path: path of a folder in the chooser entries current file system.
421 * Sets the folder with respect to which completions occur.
424 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
425 const GtkFilePath *path)
427 if (chooser_entry->base_folder)
428 gtk_file_path_free (chooser_entry->base_folder);
430 chooser_entry->base_folder = gtk_file_path_copy (path);
431 gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
435 * _gtk_file_chooser_entry_get_current_folder:
436 * @chooser_entry: a #GtkFileChooserEntry
438 * Gets the current folder for the #GtkFileChooserEntry. If the
439 * user has only entered a filename, this will be the base folder
440 * (see _gtk_file_chooser_entry_set_base_folder()), but if the
441 * user has entered a relative or absolute path, then it will
442 * be different. If the user has entered a relative or absolute
443 * path that doesn't point to a folder in the file system, it will
446 * Return value: the path of current folder - this value is owned by the
447 * chooser entry and must not be modified or freed.
450 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
452 return chooser_entry->current_folder_path;
456 * _gtk_file_chooser_entry_get_file_part:
457 * @chooser_entry: a #GtkFileChooserEntry
459 * Gets the non-folder portion of whatever the user has entered
460 * into the file selector. What is returned is a UTF-8 string,
461 * and if a filename path is needed, gtk_file_system_make_path()
464 * Return value: the entered filename - this value is owned by the
465 * chooser entry and must not be modified or freed.
468 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
470 return chooser_entry->file_part;
474 * _gtk_file_chooser_entry_set_file_part:
475 * @chooser_entry: a #GtkFileChooserEntry
476 * @file_part: text to display in the entry, in UTF-8
478 * Sets the current text shown in the file chooser entry.
481 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
482 const gchar *file_part)
484 g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
486 chooser_entry->in_change = TRUE;
487 gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
488 chooser_entry->in_change = FALSE;