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