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