]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechooserentry.c
2717187492a1b845b6c7372b2171d7f710edfda2
[~andy/gtk] / gtk / gtkfilechooserentry.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechooserentry.c: Entry with filename completion
3  * Copyright (C) 2003, Red Hat, Inc.
4  *
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.
9  *
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.
14  *
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.
19  */
20
21 #include "gtkfilechooserentry.h"
22
23 #include <gtk/gtkentry.h>
24
25 #include <string.h>
26
27 typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass;
28
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))
32
33 struct _GtkFileChooserEntryClass
34 {
35   GtkEntryClass parent_class;
36 };
37
38 struct _GtkFileChooserEntry
39 {
40   GtkEntry parent_instance;
41
42   GtkFileSystem *file_system;
43   GtkFilePath *base_folder;
44   GtkFilePath *current_folder_path;
45   gchar *file_part;
46   GSource *completion_idle;
47
48   GtkFileFolder *current_folder;
49
50   guint in_change : 1;
51   guint has_completion : 1;
52 };
53
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);
57
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,
64                                                        gint              new_text_length,
65                                                        gint             *position);
66
67 static void clear_completion_callback (GtkFileChooserEntry *chooser_entry,
68                                        GParamSpec          *pspec);
69
70 GObjectClass *parent_class;
71 GtkEditableClass *parent_editable_iface;
72
73 GType
74 _gtk_file_chooser_entry_get_type (void)
75 {
76   static GType file_chooser_entry_type = 0;
77
78   if (!file_chooser_entry_type)
79     {
80       static const GTypeInfo file_chooser_entry_info =
81       {
82         sizeof (GtkFileChooserEntryClass),
83         NULL,           /* base_init */
84         NULL,           /* base_finalize */
85         (GClassInitFunc) gtk_file_chooser_entry_class_init,
86         NULL,           /* class_finalize */
87         NULL,           /* class_data */
88         sizeof (GtkFileChooserEntry),
89         0,              /* n_preallocs */
90         (GInstanceInitFunc) gtk_file_chooser_entry_init,
91       };
92
93       static const GInterfaceInfo editable_info =
94       {
95         (GInterfaceInitFunc) gtk_file_chooser_entry_iface_init, /* interface_init */
96         NULL,                                                 /* interface_finalize */
97         NULL                                                  /* interface_data */
98       };
99
100       
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,
104                                    GTK_TYPE_EDITABLE,
105                                    &editable_info);
106     }
107
108
109   return file_chooser_entry_type;
110 }
111
112 static void
113 gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class)
114 {
115   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
116   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
117
118   parent_class = g_type_class_peek_parent (class);
119
120   gobject_class->finalize = gtk_file_chooser_entry_finalize;
121
122   widget_class->focus = gtk_file_chooser_entry_focus;
123 }
124
125 static void
126 gtk_file_chooser_entry_iface_init (GtkEditableClass *iface)
127 {
128   parent_editable_iface = g_type_interface_peek_parent (iface);
129
130   iface->do_insert_text = gtk_file_chooser_entry_do_insert_text;
131   iface->changed = gtk_file_chooser_entry_changed;
132 }
133
134 static void
135 gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry)
136 {
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);
141 }
142
143 static void
144 gtk_file_chooser_entry_finalize (GObject *object)
145 {
146   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object);
147
148   if (chooser_entry->current_folder)
149     g_object_unref (chooser_entry->current_folder);
150   
151   if (chooser_entry->file_system)
152     g_object_unref (chooser_entry->file_system);
153   
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);
157
158   parent_class->finalize (object);
159 }
160
161 static gboolean
162 completion_idle_callback (GtkFileChooserEntry *chooser_entry)
163 {
164   GtkEditable *editable = GTK_EDITABLE (chooser_entry);
165   GSList *child_paths = NULL;
166   GSList *tmp_list;
167   gchar *common_prefix = NULL;
168   GtkFilePath *unique_path = NULL;
169
170   chooser_entry->completion_idle = NULL;
171
172   if (strcmp (chooser_entry->file_part, "") == 0)
173     return FALSE;
174
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,
184                                    &child_paths,
185                                    NULL); /* NULL-GError */
186
187   for (tmp_list = child_paths; tmp_list; tmp_list = tmp_list->next)
188     {
189       GtkFileInfo *info;
190       
191       info = gtk_file_folder_get_info (chooser_entry->current_folder,
192                                        tmp_list->data,
193                                        NULL); /* NULL-GError */
194       if (info)
195         {
196           const gchar *display_name = gtk_file_info_get_display_name (info);
197           
198           if (g_str_has_prefix (display_name, chooser_entry->file_part))
199             {
200               if (!common_prefix)
201                 {
202                   common_prefix = g_strdup (display_name);
203                   unique_path = gtk_file_path_copy (tmp_list->data);
204                 }
205               else
206                 {
207                   gchar *p = common_prefix;
208                   const gchar *q = display_name;
209                   
210                   while (*p && *p == *q)
211                     {
212                       p++;
213                       q++;
214                     }
215                   
216                   *p = '\0';
217
218                   gtk_file_path_free (unique_path);
219                   unique_path = NULL;
220                 }
221             }
222           
223           gtk_file_info_free (info);
224         }
225     }
226
227   if (unique_path)
228     {
229       GtkFileInfo *info;
230             
231       info = gtk_file_folder_get_info (chooser_entry->current_folder,
232                                        unique_path,
233                                        NULL); /* NULL-GError */
234
235       if (info)
236         {
237           if (gtk_file_info_get_is_folder (info))
238             {
239               gchar *tmp = common_prefix;
240               common_prefix = g_strconcat (tmp, "/", NULL);
241               g_free (tmp);
242             }
243           
244           gtk_file_info_free (info);
245         }
246
247       gtk_file_path_free (unique_path);
248     }
249   
250   gtk_file_paths_free (child_paths);
251
252   if (common_prefix)
253     {
254       gint total_len;
255       gint file_part_len;
256       gint common_prefix_len;
257       gint pos;
258
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);
262
263       if (common_prefix_len > file_part_len)
264         {
265           chooser_entry->in_change = TRUE;
266           
267           pos = total_len - file_part_len;
268           gtk_editable_delete_text (editable,
269                                     pos, -1);
270           gtk_editable_insert_text (editable,
271                                     common_prefix, -1, 
272                                     &pos);
273           gtk_editable_select_region (editable,
274                                       total_len,
275                                       total_len - file_part_len + common_prefix_len);
276           
277           chooser_entry->in_change = FALSE;
278           chooser_entry->has_completion = TRUE;
279         }
280           
281       g_free (common_prefix);
282     }
283   
284   return FALSE;
285 }
286
287 static void
288 gtk_file_chooser_entry_do_insert_text (GtkEditable *editable,
289                                        const gchar *new_text,
290                                        gint         new_text_length,
291                                        gint        *position)
292 {
293   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
294   
295   parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position);
296
297   if (!chooser_entry->in_change &&
298       *position == GTK_ENTRY (editable)->text_length &&
299       !chooser_entry->completion_idle)
300     {
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);
307     }
308 }
309
310 static gboolean
311 gtk_file_chooser_entry_focus (GtkWidget        *widget,
312                               GtkDirectionType  direction)
313 {
314   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget);
315   
316   if (direction == GTK_DIR_TAB_FORWARD &&
317       GTK_WIDGET_HAS_FOCUS (widget) &&
318       chooser_entry->has_completion)
319     {
320       gtk_editable_set_position (GTK_EDITABLE (widget),
321                                  GTK_ENTRY (widget)->text_length);
322       return TRUE;
323     }
324   else
325     return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction);
326 }
327
328 static void
329 gtk_file_chooser_entry_changed (GtkEditable *editable)
330 {
331   GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable);
332   const gchar *text;
333   GtkFilePath *folder_path;
334   gchar *file_part;
335
336   text = gtk_entry_get_text (GTK_ENTRY (editable));
337
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 */
343     {
344       folder_path = gtk_file_path_copy (chooser_entry->base_folder);
345       file_part = g_strdup ("");
346     }
347
348   if (chooser_entry->current_folder_path)
349     {
350       if (gtk_file_path_compare (folder_path, chooser_entry->current_folder_path) != 0)
351         {
352           if (chooser_entry->current_folder)
353             {
354               g_object_unref (chooser_entry->current_folder);
355               chooser_entry->current_folder = NULL;
356             }
357         }
358       
359       gtk_file_path_free (chooser_entry->current_folder_path);
360     }
361   
362   chooser_entry->current_folder_path = folder_path;
363
364   if (chooser_entry->file_part)
365     g_free (chooser_entry->file_part);
366
367   chooser_entry->file_part = file_part;
368 }
369
370 static void
371 clear_completion_callback (GtkFileChooserEntry *chooser_entry,
372                            GParamSpec          *pspec)
373 {
374   chooser_entry->has_completion = FALSE;
375 }
376
377 /**
378  * _gtk_file_chooser_entry_new:
379  * 
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.
384  * 
385  * Return value: the newly created #GtkFileChooserEntry
386  **/
387 GtkWidget *
388 _gtk_file_chooser_entry_new (void)
389 {
390   return g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL);
391 }
392
393 /**
394  * _gtk_file_chooser_entry_set_file_system:
395  * @chooser_entry: a #GtkFileChooser
396  * @file_system: an object implementing #GtkFileSystem
397  * 
398  * Sets the file system for @chooser_entry.
399  **/
400 void
401 _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry,
402                                          GtkFileSystem       *file_system)
403 {
404   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
405   g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system));
406   
407   if (file_system != chooser_entry->file_system)
408     {
409       if (chooser_entry->file_system)
410         g_object_unref (chooser_entry->file_system);
411
412       chooser_entry->file_system = g_object_ref (file_system);
413     }
414 }
415
416 /**
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.
420  *
421  * Sets the folder with respect to which completions occur.
422  **/
423 void
424 _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry,
425                                          const GtkFilePath   *path)
426 {
427   if (chooser_entry->base_folder)
428     gtk_file_path_free (chooser_entry->base_folder);
429
430   chooser_entry->base_folder = gtk_file_path_copy (path);
431   gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry));
432 }
433
434 /**
435  * _gtk_file_chooser_entry_get_current_folder:
436  * @chooser_entry: a #GtkFileChooserEntry
437  * 
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
444  * be %NULL.
445  * 
446  * Return value: the path of current folder - this value is owned by the
447  *  chooser entry and must not be modified or freed.
448  **/
449 const GtkFilePath *
450 _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry)
451 {
452   return chooser_entry->current_folder_path;
453 }
454
455 /**
456  * _gtk_file_chooser_entry_get_file_part:
457  * @chooser_entry: a #GtkFileChooserEntry
458  * 
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()
462  * must be used
463   * 
464  * Return value: the entered filename - this value is owned by the
465  *  chooser entry and must not be modified or freed.
466  **/
467 const gchar *
468 _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry)
469 {
470   return chooser_entry->file_part;
471 }
472
473 /**
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
477  * 
478  * Sets the current text shown in the file chooser entry.
479  **/
480 void
481 _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry,
482                                        const gchar         *file_part)
483 {
484   g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry));
485
486   chooser_entry->in_change = TRUE;
487   gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part);
488   chooser_entry->in_change = FALSE;
489 }