76e6276a69e4fd2cc703ae685069fc4ce9a6bcd8
[platform/upstream/glib.git] / gio / gfilenamecompleter.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 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
16  * Public 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  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22
23 #include <config.h>
24 #include "gfilenamecompleter.h"
25 #include "gurifuncs.h"
26 #include "gfile.h"
27 #include <string.h>
28 #include "glibintl.h"
29
30 /**
31  * SECTION:gfilenamecompleter
32  * @short_description: Filename Completer
33  * 
34  * Completes partial file and directory names given a partial string by
35  * looking in the file system for clues. Can return a list of possible 
36  * completion strings for widget implementation.
37  * 
38  **/
39
40 enum {
41   GOT_COMPLETION_DATA,
42   LAST_SIGNAL
43 };
44
45 static guint signals[LAST_SIGNAL] = { 0 };
46
47 typedef struct {
48   GFilenameCompleter *completer;
49   GFileEnumerator *enumerator;
50   GCancellable *cancellable;
51   gboolean should_escape;
52   GFile *dir;
53   GList *basenames;
54   gboolean dirs_only;
55 } LoadBasenamesData;
56
57 struct _GFilenameCompleter {
58   GObject parent;
59
60   GFile *basenames_dir;
61   gboolean basenames_are_escaped;
62   GList *basenames;
63   gboolean dirs_only;
64
65   LoadBasenamesData *basename_loader;
66 };
67
68 G_DEFINE_TYPE (GFilenameCompleter, g_filename_completer, G_TYPE_OBJECT);
69
70 static void cancel_load_basenames (GFilenameCompleter *completer);
71
72 static void
73 g_filename_completer_finalize (GObject *object)
74 {
75   GFilenameCompleter *completer;
76
77   completer = G_FILENAME_COMPLETER (object);
78
79   cancel_load_basenames (completer);
80
81   if (completer->basenames_dir)
82     g_object_unref (completer->basenames_dir);
83
84   g_list_foreach (completer->basenames, (GFunc)g_free, NULL);
85   g_list_free (completer->basenames);
86   
87   if (G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize)
88     (*G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize) (object);
89 }
90
91 static void
92 g_filename_completer_class_init (GFilenameCompleterClass *klass)
93 {
94   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
95   
96   gobject_class->finalize = g_filename_completer_finalize;
97   /**
98    * GFilenameCompleter::got-completion-data:
99    * 
100    * Emitted when the file name completion information comes available.
101    **/
102   signals[GOT_COMPLETION_DATA] = g_signal_new (I_("got_completion_data"),
103                                           G_TYPE_FILENAME_COMPLETER,
104                                           G_SIGNAL_RUN_LAST,
105                                           G_STRUCT_OFFSET (GFilenameCompleterClass, got_completion_data),
106                                           NULL, NULL,
107                                           g_cclosure_marshal_VOID__VOID,
108                                           G_TYPE_NONE, 0);
109 }
110
111 static void
112 g_filename_completer_init (GFilenameCompleter *completer)
113 {
114 }
115
116 /**
117  * g_filename_completer_new:
118  * 
119  * Creates a new filename completer.
120  * 
121  * Returns: a #GFilenameCompleter.
122  **/
123 GFilenameCompleter *
124 g_filename_completer_new (void)
125 {
126   return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL);
127 }
128
129 static char *
130 longest_common_prefix (char *a, char *b)
131 {
132   char *start;
133
134   start = a;
135
136   while (g_utf8_get_char (a) == g_utf8_get_char (b))
137     {
138       a = g_utf8_next_char (a);
139       b = g_utf8_next_char (b);
140     }
141
142   return g_strndup (start, a - start);
143 }
144
145 static void
146 load_basenames_data_free (LoadBasenamesData *data)
147 {
148   if (data->enumerator)
149     g_object_unref (data->enumerator);
150   
151   g_object_unref (data->cancellable);
152   g_object_unref (data->dir);
153   
154   g_list_foreach (data->basenames, (GFunc)g_free, NULL);
155   g_list_free (data->basenames);
156   
157   g_free (data);
158 }
159
160 static void
161 got_more_files (GObject *source_object,
162                 GAsyncResult *res,
163                 gpointer user_data)
164 {
165   LoadBasenamesData *data = user_data;
166   GList *infos, *l;
167   GFileInfo *info;
168   const char *name;
169   gboolean append_slash;
170   char *t;
171   char *basename;
172
173   if (data->completer == NULL)
174     {
175       /* Was cancelled */
176       load_basenames_data_free (data);
177       return;
178     }
179
180   infos = g_file_enumerator_next_files_finish (data->enumerator, res, NULL);
181
182   for (l = infos; l != NULL; l = l->next)
183     {
184       info = l->data;
185
186       if (data->dirs_only &&
187           g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
188         {
189           g_object_unref (info);
190           continue;
191         }
192       
193       append_slash = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
194       name = g_file_info_get_name (info);
195       if (name == NULL)
196         {
197           g_object_unref (info);
198           continue;
199         }
200
201       
202       if (data->should_escape)
203         basename = g_uri_escape_string (name,
204                                         G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
205                                         TRUE);
206       else
207         /* If not should_escape, must be a local filename, convert to utf8 */
208         basename = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
209       
210       if (basename)
211         {
212           if (append_slash)
213             {
214               t = basename;
215               basename = g_strconcat (basename, "/", NULL);
216               g_free (t);
217             }
218           
219           data->basenames = g_list_prepend (data->basenames, basename);
220         }
221       
222       g_object_unref (info);
223     }
224   
225   g_list_free (infos);
226   
227   if (infos)
228     {
229       /* Not last, get more files */
230       g_file_enumerator_next_files_async (data->enumerator,
231                                           100,
232                                           0,
233                                           data->cancellable,
234                                           got_more_files, data);
235     }
236   else
237     {
238       data->completer->basename_loader = NULL;
239       
240       if (data->completer->basenames_dir)
241         g_object_unref (data->completer->basenames_dir);
242       g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL);
243       g_list_free (data->completer->basenames);
244       
245       data->completer->basenames_dir = g_object_ref (data->dir);
246       data->completer->basenames = data->basenames;
247       data->completer->basenames_are_escaped = data->should_escape;
248       data->basenames = NULL;
249       
250       g_file_enumerator_close_async (data->enumerator, 0, NULL, NULL, NULL);
251
252       g_signal_emit (data->completer, signals[GOT_COMPLETION_DATA], 0);
253       load_basenames_data_free (data);
254     }
255 }
256
257
258 static void
259 got_enum (GObject *source_object,
260           GAsyncResult *res,
261           gpointer user_data)
262 {
263   LoadBasenamesData *data = user_data;
264
265   if (data->completer == NULL)
266     {
267       /* Was cancelled */
268       load_basenames_data_free (data);
269       return;
270     }
271   
272   data->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL);
273   
274   if (data->enumerator == NULL)
275     {
276       data->completer->basename_loader = NULL;
277
278       if (data->completer->basenames_dir)
279         g_object_unref (data->completer->basenames_dir);
280       g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL);
281       g_list_free (data->completer->basenames);
282
283       /* Mark uptodate with no basenames */
284       data->completer->basenames_dir = g_object_ref (data->dir);
285       data->completer->basenames = NULL;
286       data->completer->basenames_are_escaped = data->should_escape;
287       
288       load_basenames_data_free (data);
289       return;
290     }
291   
292   g_file_enumerator_next_files_async (data->enumerator,
293                                       100,
294                                       0,
295                                       data->cancellable,
296                                       got_more_files, data);
297 }
298
299 static void
300 schedule_load_basenames (GFilenameCompleter *completer,
301                          GFile *dir,
302                          gboolean should_escape)
303 {
304   LoadBasenamesData *data;
305
306   cancel_load_basenames (completer);
307
308   data = g_new0 (LoadBasenamesData, 1);
309   data->completer = completer;
310   data->cancellable = g_cancellable_new ();
311   data->dir = g_object_ref (dir);
312   data->should_escape = should_escape;
313   data->dirs_only = completer->dirs_only;
314
315   completer->basename_loader = data;
316   
317   g_file_enumerate_children_async (dir,
318                                    G_FILE_ATTRIBUTE_STD_NAME "," G_FILE_ATTRIBUTE_STD_TYPE,
319                                    0, 0,
320                                    data->cancellable,
321                                    got_enum, data);
322 }
323
324 static void
325 cancel_load_basenames (GFilenameCompleter *completer)
326 {
327   LoadBasenamesData *loader;
328   
329   if (completer->basename_loader)
330     {
331       loader = completer->basename_loader; 
332       loader->completer = NULL;
333       
334       g_cancellable_cancel (loader->cancellable);
335       
336       completer->basename_loader = NULL;
337     }
338 }
339
340
341 /* Returns a list of possible matches and the basename to use for it */
342 static GList *
343 init_completion (GFilenameCompleter *completer,
344                  const char *initial_text,
345                  char **basename_out)
346 {
347   gboolean should_escape;
348   GFile *file, *parent;
349   char *basename;
350   char *t;
351   int len;
352
353   *basename_out = NULL;
354   
355   should_escape = ! (g_path_is_absolute (initial_text) || *initial_text == '~');
356
357   len = strlen (initial_text);
358   
359   if (len > 0 &&
360       initial_text[len - 1] == '/')
361     return NULL;
362   
363   file = g_file_parse_name (initial_text);
364   parent = g_file_get_parent (file);
365   if (parent == NULL)
366     {
367       g_object_unref (file);
368       return NULL;
369     }
370
371   if (completer->basenames_dir == NULL ||
372       completer->basenames_are_escaped != should_escape ||
373       !g_file_equal (parent, completer->basenames_dir))
374     {
375       schedule_load_basenames (completer, parent, should_escape);
376       g_object_unref (file);
377       return NULL;
378     }
379   
380   basename = g_file_get_basename (file);
381   if (should_escape)
382     {
383       t = basename;
384       basename = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
385       g_free (t);
386     }
387   else
388     {
389       t = basename;
390       basename = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
391       g_free (t);
392       
393       if (basename == NULL)
394         return NULL;
395     }
396
397   *basename_out = basename;
398
399   return completer->basenames;
400 }
401
402 /**
403  * g_filename_completer_get_completion_suffix:
404  * @completer: the filename completer.
405  * @initial_text: text to be completed.
406  *
407  * Obtains a completion for @initial_text from @completer.
408  *  
409  * Returns: a completed string, or %NULL if no completion exists. 
410  *     This string is not owned by GIO, so remember to g_free() it 
411  *     when finished.
412  **/
413 char *
414 g_filename_completer_get_completion_suffix (GFilenameCompleter *completer,
415                                             const char *initial_text)
416 {
417   GList *possible_matches, *l;
418   char *prefix;
419   char *suffix;
420   char *possible_match;
421   char *lcp;
422
423   g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
424   g_return_val_if_fail (initial_text != NULL, NULL);
425
426   possible_matches = init_completion (completer, initial_text, &prefix);
427
428   suffix = NULL;
429   
430   for (l = possible_matches; l != NULL; l = l->next)
431     {
432       possible_match = l->data;
433       
434       if (g_str_has_prefix (possible_match, prefix))
435         {
436           if (suffix == NULL)
437             suffix = g_strdup (possible_match + strlen (prefix));
438           else
439             {
440               lcp = longest_common_prefix (suffix,
441                                            possible_match + strlen (prefix));
442               g_free (suffix);
443               suffix = lcp;
444               
445               if (*suffix == 0)
446                 break;
447             }
448         }
449     }
450
451   g_free (prefix);
452   
453   return suffix;
454 }
455
456 /**
457  * g_filename_completer_get_completions:
458  * @completer: the filename completer.
459  * @initial_text: text to be completed.
460  * 
461  * Gets an array of completion strings for a given initial text.
462  * 
463  * Returns: array of strings with possible completions for @initial_text.
464  * This array must be freed by g_strfreev() when finished. 
465  **/
466 char **
467 g_filename_completer_get_completions (GFilenameCompleter *completer,
468                                       const char *initial_text)
469 {
470   GList *possible_matches, *l;
471   char *prefix;
472   char *possible_match;
473   GPtrArray *res;
474
475   g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
476   g_return_val_if_fail (initial_text != NULL, NULL);
477
478   possible_matches = init_completion (completer, initial_text, &prefix);
479
480   res = g_ptr_array_new ();
481   for (l = possible_matches; l != NULL; l = l->next)
482     {
483       possible_match = l->data;
484       
485       if (g_str_has_prefix (possible_match, prefix))
486         g_ptr_array_add (res,
487                          g_strconcat (initial_text, possible_match + strlen (prefix), NULL));
488     }
489
490   g_free (prefix);
491   
492   return (char**)g_ptr_array_free (res, FALSE);
493 }
494
495 /**
496  * g_filename_completer_set_dirs_only:
497  * @completer: the filename completer.
498  * @dirs_only: a #gboolean.
499  * 
500  * If @dirs_only is %TRUE, @completer will only 
501  * complete directory names, and not file names.
502  **/
503 void
504 g_filename_completer_set_dirs_only (GFilenameCompleter *completer,
505                                     gboolean dirs_only)
506 {
507   g_return_if_fail (G_IS_FILENAME_COMPLETER (completer));
508
509   completer->dirs_only = dirs_only;
510 }