remember old path, modified DirSel so we do not get any .. anymore
[lttv.git] / ltt / branches / poly / lttv / modules / gui / main / src / gtkdirsel.c
1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2003-2004 XangXiu Yang
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18
19 #include "config.h"
20
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #ifdef HAVE_SYS_PARAM_H
25 #include <sys/param.h>
26 #endif
27 #include <stdlib.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <string.h>
32 #include <errno.h>
33 #ifdef HAVE_PWD_H
34 #include <pwd.h>
35 #endif
36
37 #include <glib.h> /* Include early to get G_OS_WIN32 and
38 * G_WITH_CYGWIN */
39
40 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
41 #include <ctype.h>
42 #define STRICT
43 #include <windows.h>
44 #undef STRICT
45 #endif /* G_OS_WIN32 || G_WITH_CYGWIN */
46 #ifdef G_OS_WIN32
47 #include <winsock.h> /* For gethostname */
48 #endif
49
50 #include "gdk/gdkkeysyms.h"
51 #include <gtk/gtk.h>
52 #include <lttv/gtkdirsel.h>
53
54 #define _(A) A
55 #define WANT_HPANED 1
56
57 #ifdef G_OS_WIN32
58 #include <direct.h>
59 #include <io.h>
60 #define mkdir(p,m) _mkdir(p)
61 #ifndef S_ISDIR
62 #define S_ISDIR(mode) ((mode)&_S_IFDIR)
63 #endif
64 #endif /* G_OS_WIN32 */
65
66 #ifdef G_WITH_CYGWIN
67 #include <sys/cygwin.h> /* For cygwin_conv_to_posix_path */
68 #endif
69
70 #define DIR_LIST_WIDTH 180
71 #define DIR_LIST_HEIGHT 180
72 #define FILE_LIST_WIDTH 180
73 #define FILE_LIST_HEIGHT 180
74
75 /* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this
76 * in here, since the rest of the code in the file does require some
77 * fixed maximum.
78 */
79 #ifndef MAXPATHLEN
80 # ifdef PATH_MAX
81 # define MAXPATHLEN PATH_MAX
82 # else
83 # define MAXPATHLEN 2048
84 # endif
85 #endif
86
87 /* I've put this here so it doesn't get confused with the
88 * file completion interface */
89 typedef struct _HistoryCallbackArg HistoryCallbackArg;
90
91 struct _HistoryCallbackArg
92 {
93 gchar *directory;
94 GtkWidget *menu_item;
95 };
96
97
98 typedef struct _CompletionState CompletionState;
99 typedef struct _CompletionDir CompletionDir;
100 typedef struct _CompletionDirSent CompletionDirSent;
101 typedef struct _CompletionDirEntry CompletionDirEntry;
102 typedef struct _CompletionUserDir CompletionUserDir;
103 typedef struct _PossibleCompletion PossibleCompletion;
104
105 /* Non-external file completion decls and structures */
106
107 /* A contant telling PRCS how many directories to cache. Its actually
108 * kept in a list, so the geometry isn't important. */
109 #define CMPL_DIRECTORY_CACHE_SIZE 10
110
111 /* A constant used to determine whether a substring was an exact
112 * match by first_diff_index()
113 */
114 #define PATTERN_MATCH -1
115 #define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
116 #define CMPL_ERRNO_DID_NOT_CONVERT ((1<<16)-2)
117
118 /* This structure contains all the useful information about a directory
119 * for the purposes of filename completion. These structures are cached
120 * in the CompletionState struct. CompletionDir's are reference counted.
121 */
122 struct _CompletionDirSent
123 {
124 ino_t inode;
125 time_t mtime;
126 dev_t device;
127
128 gint entry_count;
129 struct _CompletionDirEntry *entries;
130 };
131
132 struct _CompletionDir
133 {
134 CompletionDirSent *sent;
135
136 gchar *fullname;
137 gint fullname_len;
138
139 struct _CompletionDir *cmpl_parent;
140 gint cmpl_index;
141 gchar *cmpl_text;
142 };
143
144 /* This structure contains pairs of directory entry names with a flag saying
145 * whether or not they are a valid directory. NOTE: This information is used
146 * to provide the caller with information about whether to update its completions
147 * or try to open a file. Since directories are cached by the directory mtime,
148 * a symlink which points to an invalid file (which will not be a directory),
149 * will not be reevaluated if that file is created, unless the containing
150 * directory is touched. I consider this case to be worth ignoring (josh).
151 */
152 struct _CompletionDirEntry
153 {
154 gboolean is_dir;
155 gchar *entry_name;
156 gchar *sort_key;
157 };
158
159 struct _CompletionUserDir
160 {
161 gchar *login;
162 gchar *homedir;
163 };
164
165 struct _PossibleCompletion
166 {
167 /* accessible fields, all are accessed externally by functions
168 * declared above
169 */
170 gchar *text;
171 gint is_a_completion;
172 gboolean is_directory;
173
174 /* Private fields
175 */
176 gint text_alloc;
177 };
178
179 struct _CompletionState
180 {
181 gint last_valid_char;
182 gchar *updated_text;
183 gint updated_text_len;
184 gint updated_text_alloc;
185 gboolean re_complete;
186
187 gchar *user_dir_name_buffer;
188 gint user_directories_len;
189
190 gchar *last_completion_text;
191
192 gint user_completion_index; /* if >= 0, currently completing ~user */
193
194 struct _CompletionDir *completion_dir; /* directory completing from */
195 struct _CompletionDir *active_completion_dir;
196
197 struct _PossibleCompletion the_completion;
198
199 struct _CompletionDir *reference_dir; /* initial directory */
200
201 GList* directory_storage;
202 GList* directory_sent_storage;
203
204 struct _CompletionUserDir *user_directories;
205 };
206
207 enum {
208 PROP_0,
209 PROP_SHOW_FILEOPS,
210 PROP_FILENAME,
211 PROP_SELECT_MULTIPLE
212 };
213
214 enum {
215 DIR_COLUMN
216 };
217
218 enum {
219 FILE_COLUMN
220 };
221
222 /* File completion functions which would be external, were they used
223 * outside of this file.
224 */
225
226 static CompletionState* cmpl_init_state (void);
227 static void cmpl_free_state (CompletionState *cmpl_state);
228 static gint cmpl_state_okay (CompletionState* cmpl_state);
229 static const gchar* cmpl_strerror (gint);
230
231 static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete,
232 gchar **remaining_text,
233 CompletionState *cmpl_state);
234
235 /* Returns a name for consideration, possibly a completion, this name
236 * will be invalid after the next call to cmpl_next_completion.
237 */
238 static char* cmpl_this_completion (PossibleCompletion*);
239
240 /* True if this completion matches the given text. Otherwise, this
241 * output can be used to have a list of non-completions.
242 */
243 static gint cmpl_is_a_completion (PossibleCompletion*);
244
245 /* True if the completion is a directory
246 */
247 static gboolean cmpl_is_directory (PossibleCompletion*);
248
249 /* Obtains the next completion, or NULL
250 */
251 static PossibleCompletion* cmpl_next_completion (CompletionState*);
252
253 /* Updating completions: the return value of cmpl_updated_text() will
254 * be text_to_complete completed as much as possible after the most
255 * recent call to cmpl_completion_matches. For the present
256 * application, this is the suggested replacement for the user's input
257 * string. You must CALL THIS AFTER ALL cmpl_text_completions have
258 * been received.
259 */
260 static gchar* cmpl_updated_text (CompletionState* cmpl_state);
261
262 /* After updating, to see if the completion was a directory, call
263 * this. If it was, you should consider re-calling completion_matches.
264 */
265 static gboolean cmpl_updated_dir (CompletionState* cmpl_state);
266
267 /* Current location: if using file completion, return the current
268 * directory, from which file completion begins. More specifically,
269 * the cwd concatenated with all exact completions up to the last
270 * directory delimiter('/').
271 */
272 static gchar* cmpl_reference_position (CompletionState* cmpl_state);
273
274 /* backing up: if cmpl_completion_matches returns NULL, you may query
275 * the index of the last completable character into cmpl_updated_text.
276 */
277 static gint cmpl_last_valid_char (CompletionState* cmpl_state);
278
279 /* When the user selects a non-directory, call cmpl_completion_fullname
280 * to get the full name of the selected file.
281 */
282 static const gchar* cmpl_completion_fullname (const gchar*, CompletionState* cmpl_state);
283
284
285 /* Directory operations. */
286 static CompletionDir* open_ref_dir (gchar* text_to_complete,
287 gchar** remaining_text,
288 CompletionState* cmpl_state);
289 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
290 static gboolean check_dir (gchar *dir_name,
291 struct stat *result,
292 gboolean *stat_subdirs);
293 #endif
294 static CompletionDir* open_dir (gchar* dir_name,
295 CompletionState* cmpl_state);
296 #ifdef HAVE_PWD_H
297 static CompletionDir* open_user_dir (const gchar* text_to_complete,
298 CompletionState *cmpl_state);
299 #endif
300 static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
301 CompletionState *cmpl_state);
302 static CompletionDirSent* open_new_dir (gchar* dir_name,
303 struct stat* sbuf,
304 gboolean stat_subdirs);
305 static gint correct_dir_fullname (CompletionDir* cmpl_dir);
306 static gint correct_parent (CompletionDir* cmpl_dir,
307 struct stat *sbuf);
308 #ifndef G_OS_WIN32
309 static gchar* find_parent_dir_fullname (gchar* dirname);
310 #endif
311 static CompletionDir* attach_dir (CompletionDirSent* sent,
312 gchar* dir_name,
313 CompletionState *cmpl_state);
314 static void free_dir_sent (CompletionDirSent* sent);
315 static void free_dir (CompletionDir *dir);
316 static void prune_memory_usage(CompletionState *cmpl_state);
317
318 /* Completion operations */
319 #ifdef HAVE_PWD_H
320 static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
321 CompletionState *cmpl_state);
322 #endif
323 static PossibleCompletion* attempt_dir_completion(CompletionState *cmpl_state);
324 static CompletionDir* find_completion_dir(gchar* text_to_complete,
325 gchar** remaining_text,
326 CompletionState* cmpl_state);
327 static PossibleCompletion* append_completion_text(gchar* text,
328 CompletionState* cmpl_state);
329 #ifdef HAVE_PWD_H
330 static gint get_pwdb(CompletionState* cmpl_state);
331 static gint compare_user_dir(const void* a, const void* b);
332 #endif
333 static gint first_diff_index(gchar* pat, gchar* text);
334 static gint compare_cmpl_dir(const void* a, const void* b);
335 static void update_cmpl(PossibleCompletion* poss,
336 CompletionState* cmpl_state);
337
338 static void gtk_dir_selection_class_init (GtkDirSelectionClass *klass);
339 static void gtk_dir_selection_set_property (GObject *object,
340 guint prop_id,
341 const GValue *value,
342 GParamSpec *pspec);
343 static void gtk_dir_selection_get_property (GObject *object,
344 guint prop_id,
345 GValue *value,
346 GParamSpec *pspec);
347 static void gtk_dir_selection_init (GtkDirSelection *filesel);
348 static void gtk_dir_selection_finalize (GObject *object);
349 static void gtk_dir_selection_destroy (GtkObject *object);
350 static void gtk_dir_selection_map (GtkWidget *widget);
351 static gint gtk_dir_selection_key_press (GtkWidget *widget,
352 GdkEventKey *event,
353 gpointer user_data);
354 static gint gtk_dir_selection_insert_text (GtkWidget *widget,
355 const gchar *new_text,
356 gint new_text_length,
357 gint *position,
358 gpointer user_data);
359 static void gtk_dir_selection_update_fileops (GtkDirSelection *filesel);
360
361 static void gtk_dir_selection_file_activate (GtkTreeView *tree_view,
362 GtkTreePath *path,
363 GtkTreeViewColumn *column,
364 gpointer user_data);
365 static void gtk_dir_selection_file_changed (GtkTreeSelection *selection,
366 gpointer user_data);
367 static void gtk_dir_selection_dir_activate (GtkTreeView *tree_view,
368 GtkTreePath *path,
369 GtkTreeViewColumn *column,
370 gpointer user_data);
371 static void gtk_dir_selection_dir_changed (GtkTreeSelection *selection,
372 gpointer user_data);
373 static void gtk_dir_selection_populate (GtkDirSelection *fs,
374 gchar *rel_path,
375 gboolean try_complete,
376 gboolean reset_entry);
377 static void gtk_dir_selection_abort (GtkDirSelection *fs);
378
379 static void gtk_dir_selection_update_history_menu (GtkDirSelection *fs,
380 gchar *current_dir);
381
382 static void gtk_dir_selection_create_dir (GtkWidget *widget, gpointer data);
383 static void gtk_dir_selection_delete_file (GtkWidget *widget, gpointer data);
384 static void gtk_dir_selection_rename_file (GtkWidget *widget, gpointer data);
385
386 static void free_selected_names (GPtrArray *names);
387
388 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
389 #define compare_filenames(a, b) strcmp(a, b)
390 #else
391 #define compare_filenames(a, b) g_ascii_strcasecmp(a, b)
392 #endif
393
394
395 static GtkWindowClass *parent_class = NULL;
396
397 /* Saves errno when something cmpl does fails. */
398 static gint cmpl_errno;
399
400 #ifdef G_WITH_CYGWIN
401 /*
402 * Take the path currently in the file selection
403 * entry field and translate as necessary from
404 * a WIN32 style to CYGWIN32 style path. For
405 * instance translate:
406 * x:\somepath\file.jpg
407 * to:
408 * /cygdrive/x/somepath/file.jpg
409 *
410 * Replace the path in the selection text field.
411 * Return a boolean value concerning whether a
412 * translation had to be made.
413 */
414 static int
415 translate_win32_path (GtkDirSelection *filesel)
416 {
417 int updated = 0;
418 const gchar *path;
419 gchar newPath[MAX_PATH];
420
421 /*
422 * Retrieve the current path
423 */
424 path = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
425
426 cygwin_conv_to_posix_path (path, newPath);
427 updated = (strcmp (path, newPath) != 0);
428
429 if (updated)
430 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), newPath);
431
432 return updated;
433 }
434 #endif
435
436 GType
437 gtk_dir_selection_get_type (void)
438 {
439 static GType file_selection_type = 0;
440
441 if (!file_selection_type)
442 {
443 static const GTypeInfo filesel_info =
444 {
445 sizeof (GtkDirSelectionClass),
446 NULL, /* base_init */
447 NULL, /* base_finalize */
448 (GClassInitFunc) gtk_dir_selection_class_init,
449 NULL, /* class_finalize */
450 NULL, /* class_data */
451 sizeof (GtkDirSelection),
452 0, /* n_preallocs */
453 (GInstanceInitFunc) gtk_dir_selection_init,
454 };
455
456 file_selection_type =
457 g_type_register_static (GTK_TYPE_DIALOG, "GtkDirSelection",
458 &filesel_info, 0);
459 }
460
461 return file_selection_type;
462 }
463
464 static void
465 gtk_dir_selection_class_init (GtkDirSelectionClass *class)
466 {
467 GObjectClass *gobject_class;
468 GtkObjectClass *object_class;
469 GtkWidgetClass *widget_class;
470
471 gobject_class = (GObjectClass*) class;
472 object_class = (GtkObjectClass*) class;
473 widget_class = (GtkWidgetClass*) class;
474
475 parent_class = g_type_class_peek_parent (class);
476
477 gobject_class->finalize = gtk_dir_selection_finalize;
478 gobject_class->set_property = gtk_dir_selection_set_property;
479 gobject_class->get_property = gtk_dir_selection_get_property;
480
481 g_object_class_install_property (gobject_class,
482 PROP_FILENAME,
483 g_param_spec_string ("filename",
484 _("Filename"),
485 _("The currently selected filename"),
486 NULL,
487 G_PARAM_READABLE | G_PARAM_WRITABLE));
488 g_object_class_install_property (gobject_class,
489 PROP_SHOW_FILEOPS,
490 g_param_spec_boolean ("show_fileops",
491 _("Show file operations"),
492 _("Whether buttons for creating/manipulating files should be displayed"),
493 FALSE,
494 G_PARAM_READABLE |
495 G_PARAM_WRITABLE));
496 g_object_class_install_property (gobject_class,
497 PROP_SELECT_MULTIPLE,
498 g_param_spec_boolean ("select_multiple",
499 _("Select multiple"),
500 _("Whether to allow multiple files to be selected"),
501 FALSE,
502 G_PARAM_READABLE |
503 G_PARAM_WRITABLE));
504 object_class->destroy = gtk_dir_selection_destroy;
505 widget_class->map = gtk_dir_selection_map;
506 }
507
508 static void gtk_dir_selection_set_property (GObject *object,
509 guint prop_id,
510 const GValue *value,
511 GParamSpec *pspec)
512 {
513 GtkDirSelection *filesel;
514
515 filesel = GTK_DIR_SELECTION (object);
516
517 switch (prop_id)
518 {
519 case PROP_FILENAME:
520 gtk_dir_selection_set_filename (filesel,
521 g_value_get_string (value));
522 break;
523 case PROP_SHOW_FILEOPS:
524 if (g_value_get_boolean (value))
525 gtk_dir_selection_show_fileop_buttons (filesel);
526 else
527 gtk_dir_selection_hide_fileop_buttons (filesel);
528 break;
529 case PROP_SELECT_MULTIPLE:
530 gtk_dir_selection_set_select_multiple (filesel, g_value_get_boolean (value));
531 break;
532 default:
533 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
534 break;
535 }
536 }
537
538 static void gtk_dir_selection_get_property (GObject *object,
539 guint prop_id,
540 GValue *value,
541 GParamSpec *pspec)
542 {
543 GtkDirSelection *filesel;
544
545 filesel = GTK_DIR_SELECTION (object);
546
547 switch (prop_id)
548 {
549 case PROP_FILENAME:
550 g_value_set_string (value,
551 gtk_dir_selection_get_filename(filesel));
552 break;
553
554 case PROP_SHOW_FILEOPS:
555 /* This is a little bit hacky, but doing otherwise would require
556 * adding a field to the object.
557 */
558 g_value_set_boolean (value, (filesel->fileop_c_dir &&
559 filesel->fileop_del_file &&
560 filesel->fileop_ren_file));
561 break;
562 case PROP_SELECT_MULTIPLE:
563 g_value_set_boolean (value, gtk_dir_selection_get_select_multiple (filesel));
564 break;
565 default:
566 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
567 break;
568 }
569 }
570
571 static gboolean
572 grab_default (GtkWidget *widget)
573 {
574 gtk_widget_grab_default (widget);
575 return FALSE;
576 }
577
578 static void
579 gtk_dir_selection_init (GtkDirSelection *filesel)
580 {
581 GtkWidget *entry_vbox;
582 GtkWidget *label;
583 GtkWidget *list_hbox, *list_container;
584 GtkWidget *confirm_area;
585 GtkWidget *pulldown_hbox;
586 GtkWidget *scrolled_win;
587 GtkWidget *eventbox;
588 GtkWidget *spacer;
589 GtkDialog *dialog;
590
591 GtkListStore *model;
592 GtkTreeViewColumn *column;
593
594 gtk_widget_push_composite_child ();
595
596 dialog = GTK_DIALOG (filesel);
597
598 filesel->cmpl_state = cmpl_init_state ();
599
600 /* The dialog-sized vertical box */
601 filesel->main_vbox = dialog->vbox;
602 gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
603
604 /* The horizontal box containing create, rename etc. buttons */
605 filesel->button_area = gtk_hbutton_box_new ();
606 gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area), GTK_BUTTONBOX_START);
607 gtk_box_set_spacing (GTK_BOX (filesel->button_area), 0);
608 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area,
609 FALSE, FALSE, 0);
610 gtk_widget_show (filesel->button_area);
611
612 gtk_dir_selection_show_fileop_buttons (filesel);
613
614 /* hbox for pulldown menu */
615 pulldown_hbox = gtk_hbox_new (TRUE, 5);
616 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0);
617 gtk_widget_show (pulldown_hbox);
618
619 /* Pulldown menu */
620 filesel->history_pulldown = gtk_option_menu_new ();
621 // gtk_widget_show (filesel->history_pulldown);
622 // gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown,
623 // FALSE, FALSE, 0);
624
625 /* The horizontal box containing the directory and file listboxes */
626
627 spacer = gtk_hbox_new (FALSE, 0);
628 gtk_widget_set_size_request (spacer, -1, 5);
629 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0);
630 gtk_widget_show (spacer);
631
632 list_hbox = gtk_hbox_new (FALSE, 5);
633 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
634 gtk_widget_show (list_hbox);
635 if (WANT_HPANED)
636 list_container = g_object_new (GTK_TYPE_HPANED,
637 "visible", TRUE,
638 "parent", list_hbox,
639 "border_width", 0,
640 NULL);
641 else
642 list_container = list_hbox;
643
644 spacer = gtk_hbox_new (FALSE, 0);
645 gtk_widget_set_size_request (spacer, -1, 5);
646 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0);
647 gtk_widget_show (spacer);
648
649 /* The directories list */
650
651 model = gtk_list_store_new (1, G_TYPE_STRING);
652 filesel->dir_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
653 g_object_unref (model);
654
655 column = gtk_tree_view_column_new_with_attributes (_("Folders"),
656 gtk_cell_renderer_text_new (),
657 "text", DIR_COLUMN,
658 NULL);
659 label = gtk_label_new_with_mnemonic (_("Fol_ders"));
660 gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->dir_list);
661 gtk_widget_show (label);
662 gtk_tree_view_column_set_widget (column, label);
663 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
664 gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->dir_list), column);
665
666 gtk_widget_set_size_request (filesel->dir_list,
667 DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
668 g_signal_connect (filesel->dir_list, "row_activated",
669 G_CALLBACK (gtk_dir_selection_dir_activate), filesel);
670 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->dir_list)), "changed",
671 G_CALLBACK (gtk_dir_selection_dir_changed), filesel);
672
673 /* gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); */
674
675 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
676 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);
677 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list);
678 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
679 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
680 gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0);
681 if (GTK_IS_PANED (list_container))
682 gtk_paned_pack1 (GTK_PANED (list_container), scrolled_win, TRUE, TRUE);
683 else
684 gtk_container_add (GTK_CONTAINER (list_container), scrolled_win);
685 gtk_widget_show (filesel->dir_list);
686 gtk_widget_show (scrolled_win);
687
688 /* The files list */
689 model = gtk_list_store_new (1, G_TYPE_STRING);
690 filesel->file_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
691 g_object_unref (model);
692
693 column = gtk_tree_view_column_new_with_attributes (_("Files"),
694 gtk_cell_renderer_text_new (),
695 "text", FILE_COLUMN,
696 NULL);
697 label = gtk_label_new_with_mnemonic (_("_Files"));
698 gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->file_list);
699 gtk_widget_show (label);
700 gtk_tree_view_column_set_widget (column, label);
701 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
702 gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->file_list), column);
703
704 gtk_widget_set_size_request (filesel->file_list,
705 FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
706 g_signal_connect (filesel->file_list, "row_activated",
707 G_CALLBACK (gtk_dir_selection_file_activate), filesel);
708 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list)), "changed",
709 G_CALLBACK (gtk_dir_selection_file_changed), filesel);
710
711 /* gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); */
712
713 scrolled_win = gtk_scrolled_window_new (NULL, NULL);
714 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);
715 gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list);
716 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
717 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
718 gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0);
719 // gtk_container_add (GTK_CONTAINER (list_container), scrolled_win);
720 // gtk_widget_show (filesel->file_list);
721 // gtk_widget_show (scrolled_win);
722
723 /* action area for packing buttons into. */
724 filesel->action_area = gtk_hbox_new (TRUE, 0);
725 gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area,
726 FALSE, FALSE, 0);
727 gtk_widget_show (filesel->action_area);
728
729 /* The OK/Cancel button area */
730 confirm_area = dialog->action_area;
731
732 /* The Cancel button */
733 filesel->cancel_button = gtk_dialog_add_button (dialog,
734 GTK_STOCK_CANCEL,
735 GTK_RESPONSE_CANCEL);
736 /* The OK button */
737 filesel->ok_button = gtk_dialog_add_button (dialog,
738 GTK_STOCK_OK,
739 GTK_RESPONSE_OK);
740
741 gtk_widget_grab_default (filesel->ok_button);
742
743 /* The selection entry widget */
744 entry_vbox = gtk_vbox_new (FALSE, 2);
745 gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 2);
746 gtk_widget_show (entry_vbox);
747
748 eventbox = gtk_event_box_new ();
749 filesel->selection_text = label = gtk_label_new ("");
750 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
751 gtk_container_add (GTK_CONTAINER (eventbox), label);
752 gtk_box_pack_start (GTK_BOX (entry_vbox), eventbox, FALSE, FALSE, 0);
753 gtk_widget_show (label);
754 gtk_widget_show (eventbox);
755
756 filesel->selection_entry = gtk_entry_new ();
757 g_signal_connect (filesel->selection_entry, "key_press_event",
758 G_CALLBACK (gtk_dir_selection_key_press), filesel);
759 g_signal_connect (filesel->selection_entry, "insert_text",
760 G_CALLBACK (gtk_dir_selection_insert_text), NULL);
761 g_signal_connect_swapped (filesel->selection_entry, "changed",
762 G_CALLBACK (gtk_dir_selection_update_fileops), filesel);
763 g_signal_connect_swapped (filesel->selection_entry, "focus_in_event",
764 G_CALLBACK (grab_default),
765 filesel->ok_button);
766 g_signal_connect_swapped (filesel->selection_entry, "activate",
767 G_CALLBACK (gtk_button_clicked),
768 filesel->ok_button);
769
770 gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
771 gtk_widget_show (filesel->selection_entry);
772
773 gtk_label_set_mnemonic_widget (GTK_LABEL (filesel->selection_text),
774 filesel->selection_entry);
775
776 if (!cmpl_state_okay (filesel->cmpl_state))
777 {
778 gchar err_buf[256];
779
780 g_snprintf (err_buf, sizeof (err_buf), _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno));
781
782 gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
783 }
784 else
785 {
786 gtk_dir_selection_populate (filesel, "", FALSE, TRUE);
787 }
788
789 gtk_widget_grab_focus (filesel->selection_entry);
790
791 gtk_widget_pop_composite_child ();
792 }
793
794 static gchar *
795 uri_list_extract_first_uri (const gchar* uri_list)
796 {
797 const gchar *p, *q;
798
799 g_return_val_if_fail (uri_list != NULL, NULL);
800
801 p = uri_list;
802 /* We don't actually try to validate the URI according to RFC
803 * 2396, or even check for allowed characters - we just ignore
804 * comments and trim whitespace off the ends. We also
805 * allow LF delimination as well as the specified CRLF.
806 *
807 * We do allow comments like specified in RFC 2483.
808 */
809 while (p)
810 {
811 if (*p != '#')
812 {
813 while (g_ascii_isspace(*p))
814 p++;
815
816 q = p;
817 while (*q && (*q != '\n') && (*q != '\r'))
818 q++;
819
820 if (q > p)
821 {
822 q--;
823 while (q > p && g_ascii_isspace (*q))
824 q--;
825
826 if (q > p)
827 return g_strndup (p, q - p + 1);
828 }
829 }
830 p = strchr (p, '\n');
831 if (p)
832 p++;
833 }
834 return NULL;
835 }
836
837 static void
838 dnd_really_drop (GtkWidget *dialog, gint response_id, GtkDirSelection *fs)
839 {
840 gchar *filename;
841
842 if (response_id == GTK_RESPONSE_YES)
843 {
844 filename = g_object_get_data (G_OBJECT (dialog), "gtk-fs-dnd-filename");
845
846 gtk_dir_selection_set_filename (fs, filename);
847 }
848
849 gtk_widget_destroy (dialog);
850 }
851
852
853 static void
854 filenames_dropped (GtkWidget *widget,
855 GdkDragContext *context,
856 gint x,
857 gint y,
858 GtkSelectionData *selection_data,
859 guint info,
860 guint time)
861 {
862 char *uri = NULL;
863 char *filename = NULL;
864 char *hostname;
865 char this_hostname[257];
866 int res;
867 GError *error = NULL;
868
869 if (!selection_data->data)
870 return;
871
872 uri = uri_list_extract_first_uri ((char *)selection_data->data);
873
874 if (!uri)
875 return;
876
877 filename = g_filename_from_uri (uri, &hostname, &error);
878 g_free (uri);
879
880 if (!filename)
881 {
882 g_warning ("Error getting dropped filename: %s\n",
883 error->message);
884 g_error_free (error);
885 return;
886 }
887
888 res = gethostname (this_hostname, 256);
889 this_hostname[256] = 0;
890
891 if ((hostname == NULL) ||
892 (res == 0 && strcmp (hostname, this_hostname) == 0) ||
893 (strcmp (hostname, "localhost") == 0))
894 gtk_dir_selection_set_filename (GTK_DIR_SELECTION (widget),
895 filename);
896 else
897 {
898 GtkWidget *dialog;
899 gchar *filename_utf8;
900
901 /* Conversion back to UTF-8 should always succeed for the result
902 * of g_filename_from_uri()
903 */
904 filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
905 g_assert (filename_utf8);
906
907 dialog = gtk_message_dialog_new (GTK_WINDOW (widget),
908 GTK_DIALOG_DESTROY_WITH_PARENT,
909 GTK_MESSAGE_QUESTION,
910 GTK_BUTTONS_YES_NO,
911 _("The file \"%s\" resides on another machine (called %s) and may not be available to this program.\n"
912 "Are you sure that you want to select it?"), filename_utf8, hostname);
913 g_free (filename_utf8);
914
915 g_object_set_data_full (G_OBJECT (dialog), "gtk-fs-dnd-filename", g_strdup (filename), g_free);
916
917 g_signal_connect_data (dialog, "response",
918 (GCallback) dnd_really_drop,
919 widget, NULL, 0);
920
921 gtk_widget_show (dialog);
922 }
923
924 g_free (hostname);
925 g_free (filename);
926 }
927
928 enum
929 {
930 TARGET_URILIST,
931 TARGET_UTF8_STRING,
932 TARGET_STRING,
933 TARGET_TEXT,
934 TARGET_COMPOUND_TEXT
935 };
936
937
938 static void
939 filenames_drag_get (GtkWidget *widget,
940 GdkDragContext *context,
941 GtkSelectionData *selection_data,
942 guint info,
943 guint time,
944 GtkDirSelection *filesel)
945 {
946 const gchar *file;
947 gchar *uri_list;
948 char hostname[256];
949 int res;
950 GError *error;
951
952 file = gtk_dir_selection_get_filename (filesel);
953
954 if (file)
955 {
956 if (info == TARGET_URILIST)
957 {
958 res = gethostname (hostname, 256);
959
960 error = NULL;
961 uri_list = g_filename_to_uri (file, (!res)?hostname:NULL, &error);
962 if (!uri_list)
963 {
964 g_warning ("Error getting filename: %s\n",
965 error->message);
966 g_error_free (error);
967 return;
968 }
969
970 gtk_selection_data_set (selection_data,
971 selection_data->target, 8,
972 (void *)uri_list, strlen((char *)uri_list));
973 g_free (uri_list);
974 }
975 else
976 {
977 gchar *filename_utf8 = g_filename_to_utf8 (file, -1, NULL, NULL, NULL);
978 g_assert (filename_utf8);
979 gtk_selection_data_set_text (selection_data, filename_utf8, -1);
980 g_free (filename_utf8);
981 }
982 }
983 }
984
985 static void
986 file_selection_setup_dnd (GtkDirSelection *filesel)
987 {
988 GtkWidget *eventbox;
989 static const GtkTargetEntry drop_types[] = {
990 { "text/uri-list", 0, TARGET_URILIST}
991 };
992 static gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]);
993 static const GtkTargetEntry drag_types[] = {
994 { "text/uri-list", 0, TARGET_URILIST},
995 { "UTF8_STRING", 0, TARGET_UTF8_STRING },
996 { "STRING", 0, 0 },
997 { "TEXT", 0, 0 },
998 { "COMPOUND_TEXT", 0, 0 }
999 };
1000 static gint n_drag_types = sizeof(drag_types)/sizeof(drag_types[0]);
1001
1002 gtk_drag_dest_set (GTK_WIDGET (filesel),
1003 GTK_DEST_DEFAULT_ALL,
1004 drop_types, n_drop_types,
1005 GDK_ACTION_COPY);
1006
1007 g_signal_connect (filesel, "drag_data_received",
1008 G_CALLBACK (filenames_dropped), NULL);
1009
1010 eventbox = gtk_widget_get_parent (filesel->selection_text);
1011 gtk_drag_source_set (eventbox,
1012 GDK_BUTTON1_MASK,
1013 drag_types, n_drag_types,
1014 GDK_ACTION_COPY);
1015
1016 g_signal_connect (eventbox, "drag_data_get",
1017 G_CALLBACK (filenames_drag_get), filesel);
1018 }
1019
1020 GtkWidget*
1021 gtk_dir_selection_new (const gchar *title)
1022 {
1023 GtkDirSelection *filesel;
1024
1025 filesel = g_object_new (GTK_TYPE_DIR_SELECTION, NULL);
1026 gtk_window_set_title (GTK_WINDOW (filesel), title);
1027 gtk_dialog_set_has_separator (GTK_DIALOG (filesel), FALSE);
1028
1029 file_selection_setup_dnd (filesel);
1030
1031 return GTK_WIDGET (filesel);
1032 }
1033
1034 void
1035 gtk_dir_selection_show_fileop_buttons (GtkDirSelection *filesel)
1036 {
1037 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
1038
1039 /* delete, create directory, and rename */
1040 if (!filesel->fileop_c_dir)
1041 {
1042 filesel->fileop_c_dir = gtk_button_new_with_mnemonic (_("_New Folder"));
1043 g_signal_connect (filesel->fileop_c_dir, "clicked",
1044 G_CALLBACK (gtk_dir_selection_create_dir),
1045 filesel);
1046 // gtk_box_pack_start (GTK_BOX (filesel->button_area),
1047 // filesel->fileop_c_dir, TRUE, TRUE, 0);
1048 // gtk_widget_show (filesel->fileop_c_dir);
1049 }
1050
1051 if (!filesel->fileop_del_file)
1052 {
1053 filesel->fileop_del_file = gtk_button_new_with_mnemonic (_("De_lete File"));
1054 g_signal_connect (filesel->fileop_del_file, "clicked",
1055 G_CALLBACK (gtk_dir_selection_delete_file),
1056 filesel);
1057 // gtk_box_pack_start (GTK_BOX (filesel->button_area),
1058 // filesel->fileop_del_file, TRUE, TRUE, 0);
1059 // gtk_widget_show (filesel->fileop_del_file);
1060 }
1061
1062 if (!filesel->fileop_ren_file)
1063 {
1064 filesel->fileop_ren_file = gtk_button_new_with_mnemonic (_("_Rename File"));
1065 g_signal_connect (filesel->fileop_ren_file, "clicked",
1066 G_CALLBACK (gtk_dir_selection_rename_file),
1067 filesel);
1068 // gtk_box_pack_start (GTK_BOX (filesel->button_area),
1069 // filesel->fileop_ren_file, TRUE, TRUE, 0);
1070 // gtk_widget_show (filesel->fileop_ren_file);
1071 }
1072
1073 gtk_dir_selection_update_fileops (filesel);
1074
1075 g_object_notify (G_OBJECT (filesel), "show_fileops");
1076 }
1077
1078 void
1079 gtk_dir_selection_hide_fileop_buttons (GtkDirSelection *filesel)
1080 {
1081 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
1082
1083 if (filesel->fileop_ren_file)
1084 {
1085 gtk_widget_destroy (filesel->fileop_ren_file);
1086 filesel->fileop_ren_file = NULL;
1087 }
1088
1089 if (filesel->fileop_del_file)
1090 {
1091 gtk_widget_destroy (filesel->fileop_del_file);
1092 filesel->fileop_del_file = NULL;
1093 }
1094
1095 if (filesel->fileop_c_dir)
1096 {
1097 gtk_widget_destroy (filesel->fileop_c_dir);
1098 filesel->fileop_c_dir = NULL;
1099 }
1100 g_object_notify (G_OBJECT (filesel), "show_fileops");
1101 }
1102
1103
1104
1105 /**
1106 * gtk_dir_selection_set_filename:
1107 * @filesel: a #GtkDirSelection.
1108 * @filename: a string to set as the default file name.
1109 *
1110 * Sets a default path for the file requestor. If @filename includes a
1111 * directory path, then the requestor will open with that path as its
1112 * current working directory.
1113 *
1114 * The encoding of @filename is the on-disk encoding, which
1115 * may not be UTF-8. See g_filename_from_utf8().
1116 **/
1117 void
1118 gtk_dir_selection_set_filename (GtkDirSelection *filesel,
1119 const gchar *filename)
1120 {
1121 gchar *buf;
1122 const char *name, *last_slash;
1123 char *filename_utf8;
1124
1125 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
1126 g_return_if_fail (filename != NULL);
1127
1128 filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1129 g_return_if_fail (filename_utf8 != NULL);
1130
1131 last_slash = strrchr (filename_utf8, G_DIR_SEPARATOR);
1132
1133 if (!last_slash)
1134 {
1135 buf = g_strdup ("");
1136 name = filename_utf8;
1137 }
1138 else
1139 {
1140 buf = g_strdup (filename_utf8);
1141 buf[last_slash - filename_utf8 + 1] = 0;
1142 name = last_slash + 1;
1143 }
1144
1145 gtk_dir_selection_populate (filesel, buf, FALSE, TRUE);
1146
1147 if (filesel->selection_entry)
1148 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
1149 g_free (buf);
1150 g_object_notify (G_OBJECT (filesel), "filename");
1151
1152 g_free (filename_utf8);
1153 }
1154
1155 /**
1156 * gtk_dir_selection_get_filename:
1157 * @filesel: a #GtkDirSelection
1158 *
1159 * This function returns the selected filename in the on-disk encoding
1160 * (see g_filename_from_utf8()), which may or may not be the same as that
1161 * used by GTK+ (UTF-8). To convert to UTF-8, call g_filename_to_utf8().
1162 * The returned string points to a statically allocated buffer and
1163 * should be copied if you plan to keep it around.
1164 *
1165 * If no file is selected then the selected directory path is returned.
1166 *
1167 * Return value: currently-selected filename in the on-disk encoding.
1168 **/
1169 G_CONST_RETURN gchar*
1170 gtk_dir_selection_get_filename (GtkDirSelection *filesel)
1171 {
1172 static const gchar nothing[2] = "";
1173 static gchar something[MAXPATHLEN*2];
1174 char *sys_filename;
1175 const char *text;
1176
1177 g_return_val_if_fail (GTK_IS_DIR_SELECTION (filesel), nothing);
1178
1179 #ifdef G_WITH_CYGWIN
1180 translate_win32_path (filesel);
1181 #endif
1182 text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
1183 if (text)
1184 {
1185 sys_filename = g_filename_from_utf8 (cmpl_completion_fullname (text, filesel->cmpl_state), -1, NULL, NULL, NULL);
1186 if (!sys_filename)
1187 return nothing;
1188 strncpy (something, sys_filename, sizeof (something));
1189 g_free (sys_filename);
1190 return something;
1191 }
1192
1193 return nothing;
1194 }
1195
1196 void
1197 gtk_dir_selection_complete (GtkDirSelection *filesel,
1198 const gchar *pattern)
1199 {
1200 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
1201 g_return_if_fail (pattern != NULL);
1202
1203 if (filesel->selection_entry)
1204 gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
1205 gtk_dir_selection_populate (filesel, (gchar*) pattern, TRUE, TRUE);
1206 }
1207
1208 static void
1209 gtk_dir_selection_destroy (GtkObject *object)
1210 {
1211 GtkDirSelection *filesel;
1212 GList *list;
1213 HistoryCallbackArg *callback_arg;
1214
1215 g_return_if_fail (GTK_IS_DIR_SELECTION (object));
1216
1217 filesel = GTK_DIR_SELECTION (object);
1218
1219 if (filesel->fileop_dialog)
1220 {
1221 gtk_widget_destroy (filesel->fileop_dialog);
1222 filesel->fileop_dialog = NULL;
1223 }
1224
1225 if (filesel->history_list)
1226 {
1227 list = filesel->history_list;
1228 while (list)
1229 {
1230 callback_arg = list->data;
1231 g_free (callback_arg->directory);
1232 g_free (callback_arg);
1233 list = list->next;
1234 }
1235 g_list_free (filesel->history_list);
1236 filesel->history_list = NULL;
1237 }
1238
1239 if (filesel->cmpl_state)
1240 {
1241 cmpl_free_state (filesel->cmpl_state);
1242 filesel->cmpl_state = NULL;
1243 }
1244
1245 if (filesel->selected_names)
1246 {
1247 free_selected_names (filesel->selected_names);
1248 filesel->selected_names = NULL;
1249 }
1250
1251 if (filesel->last_selected)
1252 {
1253 g_free (filesel->last_selected);
1254 filesel->last_selected = NULL;
1255 }
1256
1257 GTK_OBJECT_CLASS (parent_class)->destroy (object);
1258 }
1259
1260 static void
1261 gtk_dir_selection_map (GtkWidget *widget)
1262 {
1263 GtkDirSelection *filesel = GTK_DIR_SELECTION (widget);
1264
1265 /* Refresh the contents */
1266 gtk_dir_selection_populate (filesel, "", FALSE, FALSE);
1267
1268 GTK_WIDGET_CLASS (parent_class)->map (widget);
1269 }
1270
1271 static void
1272 gtk_dir_selection_finalize (GObject *object)
1273 {
1274 GtkDirSelection *filesel = GTK_DIR_SELECTION (object);
1275
1276 g_free (filesel->fileop_file);
1277
1278 G_OBJECT_CLASS (parent_class)->finalize (object);
1279 }
1280
1281 /* Begin file operations callbacks */
1282
1283 static void
1284 gtk_dir_selection_fileop_error (GtkDirSelection *fs,
1285 gchar *error_message)
1286 {
1287 GtkWidget *dialog;
1288
1289 g_return_if_fail (error_message != NULL);
1290
1291 /* main dialog */
1292 dialog = gtk_message_dialog_new (GTK_WINDOW (fs),
1293 GTK_DIALOG_DESTROY_WITH_PARENT,
1294 GTK_MESSAGE_ERROR,
1295 GTK_BUTTONS_CLOSE,
1296 "%s", error_message);
1297
1298 /* yes, we free it */
1299 g_free (error_message);
1300
1301 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1302
1303 g_signal_connect_swapped (dialog, "response",
1304 G_CALLBACK (gtk_widget_destroy),
1305 dialog);
1306
1307 gtk_widget_show (dialog);
1308 }
1309
1310 static void
1311 gtk_dir_selection_fileop_destroy (GtkWidget *widget,
1312 gpointer data)
1313 {
1314 GtkDirSelection *fs = data;
1315
1316 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1317
1318 fs->fileop_dialog = NULL;
1319 }
1320
1321 static gboolean
1322 entry_is_empty (GtkEntry *entry)
1323 {
1324 const gchar *text = gtk_entry_get_text (entry);
1325
1326 return *text == '\0';
1327 }
1328
1329 static void
1330 gtk_dir_selection_fileop_entry_changed (GtkEntry *entry,
1331 GtkWidget *button)
1332 {
1333 gtk_widget_set_sensitive (button, !entry_is_empty (entry));
1334 }
1335
1336 static void
1337 gtk_dir_selection_create_dir_confirmed (GtkWidget *widget,
1338 gpointer data)
1339 {
1340 GtkDirSelection *fs = data;
1341 const gchar *dirname;
1342 gchar *path;
1343 gchar *full_path;
1344 gchar *sys_full_path;
1345 gchar *buf;
1346 GError *error = NULL;
1347 CompletionState *cmpl_state;
1348
1349 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1350
1351 dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1352 cmpl_state = (CompletionState*) fs->cmpl_state;
1353 path = cmpl_reference_position (cmpl_state);
1354
1355 full_path = g_strconcat (path, G_DIR_SEPARATOR_S, dirname, NULL);
1356 sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
1357 if (error)
1358 {
1359 if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1360 buf = g_strdup_printf (_("The folder name \"%s\" contains symbols that are not allowed in filenames"), dirname);
1361 else
1362 buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n%s"), dirname, error->message,
1363 _("You probably used symbols not allowed in filenames."));
1364 gtk_dir_selection_fileop_error (fs, buf);
1365 g_error_free (error);
1366 goto out;
1367 }
1368
1369 if (mkdir (sys_full_path, 0755) < 0)
1370 {
1371 buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n"), dirname,
1372 g_strerror (errno));
1373 gtk_dir_selection_fileop_error (fs, buf);
1374 }
1375
1376 out:
1377 g_free (full_path);
1378 g_free (sys_full_path);
1379
1380 gtk_widget_destroy (fs->fileop_dialog);
1381 gtk_dir_selection_populate (fs, "", FALSE, FALSE);
1382 }
1383
1384 static void
1385 gtk_dir_selection_create_dir (GtkWidget *widget,
1386 gpointer data)
1387 {
1388 GtkDirSelection *fs = data;
1389 GtkWidget *label;
1390 GtkWidget *dialog;
1391 GtkWidget *vbox;
1392 GtkWidget *button;
1393
1394 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1395
1396 if (fs->fileop_dialog)
1397 return;
1398
1399 /* main dialog */
1400 dialog = gtk_dialog_new ();
1401 fs->fileop_dialog = dialog;
1402 g_signal_connect (dialog, "destroy",
1403 G_CALLBACK (gtk_dir_selection_fileop_destroy),
1404 fs);
1405 gtk_window_set_title (GTK_WINDOW (dialog), _("New Folder"));
1406 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1407 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fs));
1408
1409 /* If file dialog is grabbed, grab option dialog */
1410 /* When option dialog is closed, file dialog will be grabbed again */
1411 if (GTK_WINDOW (fs)->modal)
1412 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1413
1414 vbox = gtk_vbox_new (FALSE, 0);
1415 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1416 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1417 FALSE, FALSE, 0);
1418 gtk_widget_show( vbox);
1419
1420 label = gtk_label_new_with_mnemonic (_("_Folder name:"));
1421 gtk_misc_set_alignment(GTK_MISC (label), 0.0, 0.0);
1422 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1423 gtk_widget_show (label);
1424
1425 /* The directory entry widget */
1426 fs->fileop_entry = gtk_entry_new ();
1427 gtk_label_set_mnemonic_widget (GTK_LABEL (label), fs->fileop_entry);
1428 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1429 TRUE, TRUE, 5);
1430 GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1431 gtk_widget_show (fs->fileop_entry);
1432
1433 /* buttons */
1434 button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
1435 g_signal_connect_swapped (button, "clicked",
1436 G_CALLBACK (gtk_widget_destroy),
1437 dialog);
1438 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1439 button, TRUE, TRUE, 0);
1440 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1441 gtk_widget_grab_default (button);
1442 gtk_widget_show (button);
1443
1444 gtk_widget_grab_focus (fs->fileop_entry);
1445
1446 button = gtk_button_new_with_mnemonic (_("C_reate"));
1447 gtk_widget_set_sensitive (button, FALSE);
1448 g_signal_connect (button, "clicked",
1449 G_CALLBACK (gtk_dir_selection_create_dir_confirmed),
1450 fs);
1451 g_signal_connect (fs->fileop_entry, "changed",
1452 G_CALLBACK (gtk_dir_selection_fileop_entry_changed),
1453 button);
1454
1455 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1456 button, TRUE, TRUE, 0);
1457 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1458 gtk_widget_show (button);
1459
1460 gtk_widget_show (dialog);
1461 }
1462
1463 static void
1464 gtk_dir_selection_delete_dir_response (GtkDialog *dialog,
1465 gint response_id,
1466 gpointer data)
1467 {
1468 GtkDirSelection *fs = data;
1469 CompletionState *cmpl_state;
1470 gchar *path;
1471 gchar *full_path;
1472 gchar *sys_full_path;
1473 GError *error = NULL;
1474 gchar *buf;
1475
1476 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1477
1478 if (response_id != GTK_RESPONSE_OK)
1479 {
1480 gtk_widget_destroy (GTK_WIDGET (dialog));
1481 return;
1482 }
1483
1484 cmpl_state = (CompletionState*) fs->cmpl_state;
1485 path = cmpl_reference_position (cmpl_state);
1486
1487 full_path = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1488 sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
1489 if (error)
1490 {
1491 if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1492 buf = g_strdup_printf (_("The filename \"%s\" contains symbols that are not allowed in filenames"),
1493 fs->fileop_file);
1494 else
1495 buf = g_strdup_printf (_("Error deleting file \"%s\": %s\n%s"),
1496 fs->fileop_file, error->message,
1497 _("It probably contains symbols not allowed in filenames."));
1498
1499 gtk_dir_selection_fileop_error (fs, buf);
1500 g_error_free (error);
1501 goto out;
1502 }
1503
1504 if (unlink (sys_full_path) < 0)
1505 {
1506 buf = g_strdup_printf (_("Error deleting file \"%s\": %s"),
1507 fs->fileop_file, g_strerror (errno));
1508 gtk_dir_selection_fileop_error (fs, buf);
1509 }
1510
1511 out:
1512 g_free (full_path);
1513 g_free (sys_full_path);
1514
1515 gtk_widget_destroy (fs->fileop_dialog);
1516 gtk_dir_selection_populate (fs, "", FALSE, TRUE);
1517 }
1518
1519 static void
1520 gtk_dir_selection_delete_file (GtkWidget *widget,
1521 gpointer data)
1522 {
1523 GtkDirSelection *fs = data;
1524 GtkWidget *dialog;
1525 const gchar *filename;
1526
1527 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1528
1529 if (fs->fileop_dialog)
1530 return;
1531
1532 #ifdef G_WITH_CYGWIN
1533 translate_win32_path (fs);
1534 #endif
1535
1536 filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
1537 if (strlen (filename) < 1)
1538 return;
1539
1540 g_free (fs->fileop_file);
1541 fs->fileop_file = g_strdup (filename);
1542
1543 /* main dialog */
1544 fs->fileop_dialog = dialog =
1545 gtk_message_dialog_new (GTK_WINDOW (fs),
1546 GTK_WINDOW (fs)->modal ? GTK_DIALOG_MODAL : 0,
1547 GTK_MESSAGE_QUESTION,
1548 GTK_BUTTONS_NONE,
1549 _("Really delete file \"%s\" ?"), filename);
1550
1551 g_signal_connect (dialog, "destroy",
1552 G_CALLBACK (gtk_dir_selection_fileop_destroy),
1553 fs);
1554 gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File"));
1555 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1556
1557 /* buttons */
1558 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1559 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1560 GTK_STOCK_DELETE, GTK_RESPONSE_OK,
1561 NULL);
1562
1563 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
1564
1565 g_signal_connect (dialog, "response",
1566 G_CALLBACK (gtk_dir_selection_delete_dir_response),
1567 fs);
1568
1569 gtk_widget_show (dialog);
1570 }
1571
1572 static void
1573 gtk_dir_selection_rename_dir_confirmed (GtkWidget *widget,
1574 gpointer data)
1575 {
1576 GtkDirSelection *fs = data;
1577 gchar *buf;
1578 const gchar *file;
1579 gchar *path;
1580 gchar *new_filename;
1581 gchar *old_filename;
1582 gchar *sys_new_filename;
1583 gchar *sys_old_filename;
1584 CompletionState *cmpl_state;
1585 GError *error = NULL;
1586
1587 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1588
1589 file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
1590 cmpl_state = (CompletionState*) fs->cmpl_state;
1591 path = cmpl_reference_position (cmpl_state);
1592
1593 new_filename = g_strconcat (path, G_DIR_SEPARATOR_S, file, NULL);
1594 old_filename = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1595
1596 sys_new_filename = g_filename_from_utf8 (new_filename, -1, NULL, NULL, &error);
1597 if (error)
1598 {
1599 if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1600 buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), new_filename);
1601 else
1602 buf = g_strdup_printf (_("Error renaming file to \"%s\": %s\n%s"),
1603 new_filename, error->message,
1604 _("You probably used symbols not allowed in filenames."));
1605 gtk_dir_selection_fileop_error (fs, buf);
1606 g_error_free (error);
1607 goto out1;
1608 }
1609
1610 sys_old_filename = g_filename_from_utf8 (old_filename, -1, NULL, NULL, &error);
1611 if (error)
1612 {
1613 if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1614 buf = g_strdup_printf (_("The file name \"%s\" contains symbols that are not allowed in filenames"), old_filename);
1615 else
1616 buf = g_strdup_printf (_("Error renaming file \"%s\": %s\n%s"),
1617 old_filename, error->message,
1618 _("It probably contains symbols not allowed in filenames."));
1619 gtk_dir_selection_fileop_error (fs, buf);
1620 g_error_free (error);
1621 goto out2;
1622 }
1623
1624 if (rename (sys_old_filename, sys_new_filename) < 0)
1625 {
1626 buf = g_strdup_printf (_("Error renaming file \"%s\" to \"%s\": %s"),
1627 sys_old_filename, sys_new_filename,
1628 g_strerror (errno));
1629 gtk_dir_selection_fileop_error (fs, buf);
1630 goto out2;
1631 }
1632
1633 gtk_dir_selection_populate (fs, "", FALSE, FALSE);
1634 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), file);
1635
1636 out2:
1637 g_free (sys_old_filename);
1638
1639 out1:
1640 g_free (new_filename);
1641 g_free (old_filename);
1642 g_free (sys_new_filename);
1643
1644 gtk_widget_destroy (fs->fileop_dialog);
1645 }
1646
1647 static void
1648 gtk_dir_selection_rename_file (GtkWidget *widget,
1649 gpointer data)
1650 {
1651 GtkDirSelection *fs = data;
1652 GtkWidget *label;
1653 GtkWidget *dialog;
1654 GtkWidget *vbox;
1655 GtkWidget *button;
1656 gchar *buf;
1657
1658 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1659
1660 if (fs->fileop_dialog)
1661 return;
1662
1663 g_free (fs->fileop_file);
1664 fs->fileop_file = g_strdup (gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)));
1665 if (strlen (fs->fileop_file) < 1)
1666 return;
1667
1668 /* main dialog */
1669 fs->fileop_dialog = dialog = gtk_dialog_new ();
1670 g_signal_connect (dialog, "destroy",
1671 G_CALLBACK (gtk_dir_selection_fileop_destroy),
1672 fs);
1673 gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File"));
1674 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1675 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fs));
1676
1677 /* If file dialog is grabbed, grab option dialog */
1678 /* When option dialog closed, file dialog will be grabbed again */
1679 if (GTK_WINDOW (fs)->modal)
1680 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1681
1682 vbox = gtk_vbox_new (FALSE, 0);
1683 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
1684 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1685 FALSE, FALSE, 0);
1686 gtk_widget_show(vbox);
1687
1688 buf = g_strdup_printf (_("Rename file \"%s\" to:"), fs->fileop_file);
1689 label = gtk_label_new (buf);
1690 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
1691 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
1692 gtk_widget_show (label);
1693 g_free (buf);
1694
1695 /* New filename entry */
1696 fs->fileop_entry = gtk_entry_new ();
1697 gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry,
1698 TRUE, TRUE, 5);
1699 GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1700 gtk_widget_show (fs->fileop_entry);
1701
1702 gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file);
1703 gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry),
1704 0, strlen (fs->fileop_file));
1705
1706 /* buttons */
1707 button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
1708 g_signal_connect_swapped (button, "clicked",
1709 G_CALLBACK (gtk_widget_destroy),
1710 dialog);
1711 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1712 button, TRUE, TRUE, 0);
1713 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1714 gtk_widget_grab_default (button);
1715 gtk_widget_show (button);
1716
1717 gtk_widget_grab_focus (fs->fileop_entry);
1718
1719 button = gtk_button_new_with_mnemonic (_("_Rename"));
1720 g_signal_connect (button, "clicked",
1721 G_CALLBACK (gtk_dir_selection_rename_dir_confirmed),
1722 fs);
1723 g_signal_connect (fs->fileop_entry, "changed",
1724 G_CALLBACK (gtk_dir_selection_fileop_entry_changed),
1725 button);
1726
1727 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1728 button, TRUE, TRUE, 0);
1729 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
1730 gtk_widget_show (button);
1731
1732 gtk_widget_show (dialog);
1733 }
1734
1735 static gint
1736 gtk_dir_selection_insert_text (GtkWidget *widget,
1737 const gchar *new_text,
1738 gint new_text_length,
1739 gint *position,
1740 gpointer user_data)
1741 {
1742 gchar *filename;
1743
1744 filename = g_filename_from_utf8 (new_text, new_text_length, NULL, NULL, NULL);
1745
1746 if (!filename)
1747 {
1748 gdk_display_beep (gtk_widget_get_display (widget));
1749 g_signal_stop_emission_by_name (widget, "insert_text");
1750 return FALSE;
1751 }
1752
1753 g_free (filename);
1754
1755 return TRUE;
1756 }
1757
1758 static void
1759 gtk_dir_selection_update_fileops (GtkDirSelection *fs)
1760 {
1761 gboolean sensitive;
1762
1763 if (!fs->selection_entry)
1764 return;
1765
1766 sensitive = !entry_is_empty (GTK_ENTRY (fs->selection_entry));
1767
1768 if (fs->fileop_del_file)
1769 gtk_widget_set_sensitive (fs->fileop_del_file, sensitive);
1770
1771 if (fs->fileop_ren_file)
1772 gtk_widget_set_sensitive (fs->fileop_ren_file, sensitive);
1773 }
1774
1775 static gint
1776 gtk_dir_selection_key_press (GtkWidget *widget,
1777 GdkEventKey *event,
1778 gpointer user_data)
1779 {
1780 GtkDirSelection *fs;
1781 char *text;
1782
1783 g_return_val_if_fail (widget != NULL, FALSE);
1784 g_return_val_if_fail (event != NULL, FALSE);
1785
1786 if ((event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab) &&
1787 (event->state & gtk_accelerator_get_default_mod_mask ()) == 0)
1788 {
1789 fs = GTK_DIR_SELECTION (user_data);
1790 #ifdef G_WITH_CYGWIN
1791 translate_win32_path (fs);
1792 #endif
1793 text = g_strdup (gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)));
1794
1795 gtk_dir_selection_populate (fs, text, TRUE, TRUE);
1796
1797 g_free (text);
1798
1799 return TRUE;
1800 }
1801
1802 return FALSE;
1803 }
1804
1805 static void
1806 gtk_dir_selection_history_callback (GtkWidget *widget,
1807 gpointer data)
1808 {
1809 GtkDirSelection *fs = data;
1810 HistoryCallbackArg *callback_arg;
1811 GList *list;
1812
1813 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1814
1815 list = fs->history_list;
1816
1817 while (list) {
1818 callback_arg = list->data;
1819
1820 if (callback_arg->menu_item == widget)
1821 {
1822 gtk_dir_selection_populate (fs, callback_arg->directory, FALSE, FALSE);
1823 break;
1824 }
1825
1826 list = list->next;
1827 }
1828 }
1829
1830 static void
1831 gtk_dir_selection_update_history_menu (GtkDirSelection *fs,
1832 gchar *current_directory)
1833 {
1834 HistoryCallbackArg *callback_arg;
1835 GtkWidget *menu_item;
1836 GList *list;
1837 gchar *current_dir;
1838 gint dir_len;
1839 gint i;
1840
1841 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
1842 g_return_if_fail (current_directory != NULL);
1843
1844 list = fs->history_list;
1845
1846 if (fs->history_menu)
1847 {
1848 while (list) {
1849 callback_arg = list->data;
1850 g_free (callback_arg->directory);
1851 g_free (callback_arg);
1852 list = list->next;
1853 }
1854 g_list_free (fs->history_list);
1855 fs->history_list = NULL;
1856
1857 gtk_widget_destroy (fs->history_menu);
1858 }
1859
1860 fs->history_menu = gtk_menu_new ();
1861
1862 current_dir = g_strdup (current_directory);
1863
1864 dir_len = strlen (current_dir);
1865
1866 for (i = dir_len; i >= 0; i--)
1867 {
1868 /* the i == dir_len is to catch the full path for the first
1869 * entry. */
1870 if ( (current_dir[i] == G_DIR_SEPARATOR) || (i == dir_len))
1871 {
1872 /* another small hack to catch the full path */
1873 if (i != dir_len)
1874 current_dir[i + 1] = '\0';
1875 #ifdef G_WITH_CYGWIN
1876 if (!strcmp (current_dir, "//"))
1877 continue;
1878 #endif
1879 menu_item = gtk_menu_item_new_with_label (current_dir);
1880
1881 callback_arg = g_new (HistoryCallbackArg, 1);
1882 callback_arg->menu_item = menu_item;
1883
1884 /* since the autocompletion gets confused if you don't
1885 * supply a trailing '/' on a dir entry, set the full
1886 * (current) path to "" which just refreshes the filesel */
1887 if (dir_len == i)
1888 {
1889 callback_arg->directory = g_strdup ("");
1890 }
1891 else
1892 {
1893 callback_arg->directory = g_strdup (current_dir);
1894 }
1895
1896 fs->history_list = g_list_append (fs->history_list, callback_arg);
1897
1898 g_signal_connect (menu_item, "activate",
1899 G_CALLBACK (gtk_dir_selection_history_callback),
1900 fs);
1901 gtk_menu_shell_append (GTK_MENU_SHELL (fs->history_menu), menu_item);
1902 gtk_widget_show (menu_item);
1903 }
1904 }
1905
1906 gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown),
1907 fs->history_menu);
1908 g_free (current_dir);
1909 }
1910
1911 static gchar *
1912 get_real_filename (gchar *filename,
1913 gboolean free_old)
1914 {
1915 #ifdef G_WITH_CYGWIN
1916 /* Check to see if the selection was a drive selector */
1917 if (isalpha (filename[0]) && (filename[1] == ':'))
1918 {
1919 gchar temp_filename[MAX_PATH];
1920 int len;
1921
1922 cygwin_conv_to_posix_path (filename, temp_filename);
1923
1924 /* we need trailing '/'. */
1925 len = strlen (temp_filename);
1926 if (len > 0 && temp_filename[len-1] != '/')
1927 {
1928 temp_filename[len] = '/';
1929 temp_filename[len+1] = '\0';
1930 }
1931
1932 if (free_old)
1933 g_free (filename);
1934
1935 return g_strdup (temp_filename);
1936 }
1937 #endif /* G_WITH_CYGWIN */
1938 return filename;
1939 }
1940
1941 static void
1942 gtk_dir_selection_file_activate (GtkTreeView *tree_view,
1943 GtkTreePath *path,
1944 GtkTreeViewColumn *column,
1945 gpointer user_data)
1946 {
1947 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
1948 GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
1949 GtkTreeIter iter;
1950 gchar *filename;
1951
1952 gtk_tree_model_get_iter (model, &iter, path);
1953 gtk_tree_model_get (model, &iter, FILE_COLUMN, &filename, -1);
1954 filename = get_real_filename (filename, TRUE);
1955 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
1956 gtk_button_clicked (GTK_BUTTON (fs->ok_button));
1957
1958 g_free (filename);
1959 }
1960
1961 static void
1962 gtk_dir_selection_dir_activate (GtkTreeView *tree_view,
1963 GtkTreePath *path,
1964 GtkTreeViewColumn *column,
1965 gpointer user_data)
1966 {
1967 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
1968 GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
1969 GtkTreeIter iter;
1970 gchar *filename;
1971
1972 gtk_tree_model_get_iter (model, &iter, path);
1973 gtk_tree_model_get (model, &iter, DIR_COLUMN, &filename, -1);
1974 filename = get_real_filename (filename, TRUE);
1975 gtk_dir_selection_populate (fs, filename, FALSE, FALSE);
1976 g_free (filename);
1977 }
1978
1979 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
1980
1981 static void
1982 win32_gtk_add_drives_to_dir_list (GtkListStore *model)
1983 {
1984 gchar *textPtr;
1985 gchar buffer[128];
1986 char formatBuffer[128];
1987 GtkTreeIter iter;
1988
1989 /* Get the drives string */
1990 GetLogicalDriveStrings (sizeof (buffer), buffer);
1991
1992 /* Add the drives as necessary */
1993 textPtr = buffer;
1994 while (*textPtr != '\0')
1995 {
1996 /* Ignore floppies (?) */
1997 if ((tolower (textPtr[0]) != 'a') && (tolower (textPtr[0]) != 'b'))
1998 {
1999 /* Build the actual displayable string */
2000 g_snprintf (formatBuffer, sizeof (formatBuffer), "%c:\\", toupper (textPtr[0]));
2001
2002 /* Add to the list */
2003 gtk_list_store_append (model, &iter);
2004 gtk_list_store_set (model, &iter, DIR_COLUMN, formatBuffer, -1);
2005 }
2006 textPtr += (strlen (textPtr) + 1);
2007 }
2008 }
2009 #endif
2010
2011 static gchar *
2012 escape_underscores (const gchar *str)
2013 {
2014 GString *result = g_string_new (NULL);
2015 while (*str)
2016 {
2017 if (*str == '_')
2018 g_string_append_c (result, '_');
2019
2020 g_string_append_c (result, *str);
2021 str++;
2022 }
2023
2024 return g_string_free (result, FALSE);
2025 }
2026
2027 static void
2028 gtk_dir_selection_populate (GtkDirSelection *fs,
2029 gchar *rel_path,
2030 gboolean try_complete,
2031 gboolean reset_entry)
2032 {
2033 CompletionState *cmpl_state;
2034 PossibleCompletion* poss;
2035 GtkTreeIter iter;
2036 GtkListStore *dir_model;
2037 GtkListStore *file_model;
2038 gchar* filename;
2039 gchar* rem_path = rel_path;
2040 gchar* sel_text;
2041 gint did_recurse = FALSE;
2042 gint possible_count = 0;
2043 gint selection_index = -1;
2044
2045 g_return_if_fail (GTK_IS_DIR_SELECTION (fs));
2046
2047 cmpl_state = (CompletionState*) fs->cmpl_state;
2048 poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state);
2049
2050 if (!cmpl_state_okay (cmpl_state))
2051 {
2052 /* Something went wrong. */
2053 gtk_dir_selection_abort (fs);
2054 return;
2055 }
2056
2057 g_assert (cmpl_state->reference_dir);
2058
2059 dir_model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fs->dir_list)));
2060 file_model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (fs->file_list)));
2061
2062 gtk_list_store_clear (dir_model);
2063 gtk_list_store_clear (file_model);
2064
2065 /* Set the dir list to include ./ and ../ */
2066 gtk_list_store_append (dir_model, &iter);
2067 gtk_list_store_set (dir_model, &iter, DIR_COLUMN, "." G_DIR_SEPARATOR_S, -1);
2068 gtk_list_store_append (dir_model, &iter);
2069 gtk_list_store_set (dir_model, &iter, DIR_COLUMN, ".." G_DIR_SEPARATOR_S, -1);
2070
2071 while (poss)
2072 {
2073 if (cmpl_is_a_completion (poss))
2074 {
2075 possible_count += 1;
2076
2077 filename = cmpl_this_completion (poss);
2078
2079 if (cmpl_is_directory (poss))
2080 {
2081 if (strcmp (filename, "." G_DIR_SEPARATOR_S) != 0 &&
2082 strcmp (filename, ".." G_DIR_SEPARATOR_S) != 0)
2083 {
2084 gtk_list_store_append (dir_model, &iter);
2085 gtk_list_store_set (dir_model, &iter, DIR_COLUMN, filename, -1);
2086 }
2087 }
2088 else
2089 {
2090 gtk_list_store_append (file_model, &iter);
2091 gtk_list_store_set (file_model, &iter, DIR_COLUMN, filename, -1);
2092 }
2093 }
2094
2095 poss = cmpl_next_completion (cmpl_state);
2096 }
2097
2098 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
2099 /* For Windows, add drives as potential selections */
2100 win32_gtk_add_drives_to_dir_list (dir_model);
2101 #endif
2102
2103 /* File lists are set. */
2104
2105 g_assert (cmpl_state->reference_dir);
2106
2107 if (try_complete)
2108 {
2109
2110 /* User is trying to complete filenames, so advance the user's input
2111 * string to the updated_text, which is the common leading substring
2112 * of all possible completions, and if its a directory attempt
2113 * attempt completions in it. */
2114
2115 if (cmpl_updated_text (cmpl_state)[0])
2116 {
2117
2118 if (cmpl_updated_dir (cmpl_state))
2119 {
2120 gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state));
2121
2122 did_recurse = TRUE;
2123
2124 gtk_dir_selection_populate (fs, dir_name, TRUE, TRUE);
2125
2126 g_free (dir_name);
2127 }
2128 else
2129 {
2130 if (fs->selection_entry)
2131 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry),
2132 cmpl_updated_text (cmpl_state));
2133 }
2134 }
2135 else
2136 {
2137 selection_index = cmpl_last_valid_char (cmpl_state) -
2138 (strlen (rel_path) - strlen (rem_path));
2139 if (fs->selection_entry)
2140 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path);
2141 }
2142 }
2143 else if (reset_entry)
2144 {
2145 if (fs->selection_entry)
2146 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
2147 }
2148
2149 if (!did_recurse)
2150 {
2151 if (fs->selection_entry)
2152 gtk_editable_set_position (GTK_EDITABLE (fs->selection_entry),
2153 selection_index);
2154
2155 if (fs->selection_entry)
2156 {
2157 char *escaped = escape_underscores (cmpl_reference_position (cmpl_state));
2158 sel_text = g_strconcat (_("_Selection: "), escaped, NULL);
2159 g_free (escaped);
2160
2161 gtk_label_set_text_with_mnemonic (GTK_LABEL (fs->selection_text), sel_text);
2162 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), cmpl_reference_position (cmpl_state));
2163 g_free (sel_text);
2164 }
2165
2166 if (fs->history_pulldown)
2167 {
2168 gtk_dir_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
2169 }
2170
2171 }
2172 }
2173
2174 static void
2175 gtk_dir_selection_abort (GtkDirSelection *fs)
2176 {
2177 gchar err_buf[256];
2178
2179 g_snprintf (err_buf, sizeof (err_buf), _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno));
2180
2181 /* BEEP gdk_beep(); */
2182
2183 if (fs->selection_entry)
2184 gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
2185 }
2186
2187 /**
2188 * gtk_dir_selection_set_select_multiple:
2189 * @filesel: a #GtkDirSelection
2190 * @select_multiple: whether or not the user is allowed to select multiple
2191 * files in the file list.
2192 *
2193 * Sets whether the user is allowed to select multiple files in the file list.
2194 * Use gtk_dir_selection_get_selections () to get the list of selected files.
2195 **/
2196 void
2197 gtk_dir_selection_set_select_multiple (GtkDirSelection *filesel,
2198 gboolean select_multiple)
2199 {
2200 GtkTreeSelection *sel;
2201 GtkSelectionMode mode;
2202
2203 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
2204
2205 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list));
2206
2207 mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE;
2208
2209 if (mode != gtk_tree_selection_get_mode (sel))
2210 {
2211 gtk_tree_selection_set_mode (sel, mode);
2212
2213 g_object_notify (G_OBJECT (filesel), "select-multiple");
2214 }
2215 }
2216
2217 /**
2218 * gtk_dir_selection_get_select_multiple:
2219 * @filesel: a #GtkDirSelection
2220 *
2221 * Determines whether or not the user is allowed to select multiple files in
2222 * the file list. See gtk_dir_selection_set_select_multiple().
2223 *
2224 * Return value: %TRUE if the user is allowed to select multiple files in the
2225 * file list
2226 **/
2227 gboolean
2228 gtk_dir_selection_get_select_multiple (GtkDirSelection *filesel)
2229 {
2230 GtkTreeSelection *sel;
2231
2232 g_return_val_if_fail (GTK_IS_DIR_SELECTION (filesel), FALSE);
2233
2234 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list));
2235 return (gtk_tree_selection_get_mode (sel) == GTK_SELECTION_MULTIPLE);
2236 }
2237
2238 static void
2239 multiple_changed_foreach (GtkTreeModel *model,
2240 GtkTreePath *path,
2241 GtkTreeIter *iter,
2242 gpointer data)
2243 {
2244 GPtrArray *names = data;
2245 gchar *filename;
2246
2247 gtk_tree_model_get (model, iter, FILE_COLUMN, &filename, -1);
2248
2249 g_ptr_array_add (names, filename);
2250 }
2251
2252 static void
2253 free_selected_names (GPtrArray *names)
2254 {
2255 gint i;
2256
2257 for (i = 0; i < names->len; i++)
2258 g_free (g_ptr_array_index (names, i));
2259
2260 g_ptr_array_free (names, TRUE);
2261 }
2262
2263 static void
2264 gtk_dir_selection_file_changed (GtkTreeSelection *selection,
2265 gpointer user_data)
2266 {
2267 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
2268 GPtrArray *new_names;
2269 gchar *filename;
2270 const gchar *entry;
2271 gint index = -1;
2272
2273 new_names = g_ptr_array_sized_new (8);
2274
2275 gtk_tree_selection_selected_foreach (selection,
2276 multiple_changed_foreach,
2277 new_names);
2278
2279 /* nothing selected */
2280 if (new_names->len == 0)
2281 {
2282 g_ptr_array_free (new_names, TRUE);
2283
2284 if (fs->selected_names != NULL)
2285 {
2286 free_selected_names (fs->selected_names);
2287 fs->selected_names = NULL;
2288 }
2289
2290 goto maybe_clear_entry;
2291 }
2292
2293 if (new_names->len != 1)
2294 {
2295 GPtrArray *old_names = fs->selected_names;
2296
2297 if (old_names != NULL)
2298 {
2299 /* A common case is selecting a range of files from top to bottom,
2300 * so quickly check for that to avoid looping over the entire list
2301 */
2302 if (compare_filenames (g_ptr_array_index (old_names, old_names->len - 1),
2303 g_ptr_array_index (new_names, new_names->len - 1)) != 0)
2304 index = new_names->len - 1;
2305 else
2306 {
2307 gint i = 0, j = 0, cmp;
2308
2309 /* do a quick diff, stopping at the first file not in the
2310 * old list
2311 */
2312 while (i < old_names->len && j < new_names->len)
2313 {
2314 cmp = compare_filenames (g_ptr_array_index (old_names, i),
2315 g_ptr_array_index (new_names, j));
2316 if (cmp < 0)
2317 {
2318 i++;
2319 }
2320 else if (cmp == 0)
2321 {
2322 i++;
2323 j++;
2324 }
2325 else if (cmp > 0)
2326 {
2327 index = j;
2328 break;
2329 }
2330 }
2331
2332 /* we ran off the end of the old list */
2333 if (index == -1 && i < new_names->len)
2334 index = j;
2335 }
2336 }
2337 else
2338 {
2339 /* A phantom anchor still exists at the point where the last item
2340 * was selected, which is used for subsequent range selections.
2341 * So search up from there.
2342 */
2343 if (fs->last_selected &&
2344 compare_filenames (fs->last_selected,
2345 g_ptr_array_index (new_names, 0)) == 0)
2346 index = new_names->len - 1;
2347 else
2348 index = 0;
2349 }
2350 }
2351 else
2352 index = 0;
2353
2354 if (fs->selected_names != NULL)
2355 free_selected_names (fs->selected_names);
2356
2357 fs->selected_names = new_names;
2358
2359 if (index != -1)
2360 {
2361 if (fs->last_selected != NULL)
2362 g_free (fs->last_selected);
2363
2364 fs->last_selected = g_strdup (g_ptr_array_index (new_names, index));
2365 filename = get_real_filename (fs->last_selected, FALSE);
2366
2367 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
2368
2369 if (filename != fs->last_selected)
2370 g_free (filename);
2371
2372 return;
2373 }
2374
2375 maybe_clear_entry:
2376
2377 entry = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
2378 if ((entry != NULL) && (fs->last_selected != NULL) &&
2379 (compare_filenames (entry, fs->last_selected) == 0))
2380 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
2381 }
2382
2383 static void
2384 gtk_dir_selection_dir_changed (GtkTreeSelection *selection,
2385 gpointer user_data)
2386 {
2387 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
2388 GPtrArray *new_names;
2389 gchar *filename;
2390 const gchar *entry;
2391 gint index = -1;
2392
2393 new_names = g_ptr_array_sized_new (8);
2394
2395 gtk_tree_selection_selected_foreach (selection,
2396 multiple_changed_foreach,
2397 new_names);
2398
2399 /* nothing selected */
2400 if (new_names->len == 0)
2401 {
2402 g_ptr_array_free (new_names, TRUE);
2403
2404 if (fs->selected_names != NULL)
2405 {
2406 free_selected_names (fs->selected_names);
2407 fs->selected_names = NULL;
2408 }
2409
2410 goto maybe_clear_entry;
2411 }
2412
2413 if (new_names->len != 1)
2414 {
2415 GPtrArray *old_names = fs->selected_names;
2416
2417 if (old_names != NULL)
2418 {
2419 /* A common case is selecting a range of files from top to bottom,
2420 * so quickly check for that to avoid looping over the entire list
2421 */
2422 if (compare_filenames (g_ptr_array_index (old_names, old_names->len - 1),
2423 g_ptr_array_index (new_names, new_names->len - 1)) != 0)
2424 index = new_names->len - 1;
2425 else
2426 {
2427 gint i = 0, j = 0, cmp;
2428
2429 /* do a quick diff, stopping at the first file not in the
2430 * old list
2431 */
2432 while (i < old_names->len && j < new_names->len)
2433 {
2434 cmp = compare_filenames (g_ptr_array_index (old_names, i),
2435 g_ptr_array_index (new_names, j));
2436 if (cmp < 0)
2437 {
2438 i++;
2439 }
2440 else if (cmp == 0)
2441 {
2442 i++;
2443 j++;
2444 }
2445 else if (cmp > 0)
2446 {
2447 index = j;
2448 break;
2449 }
2450 }
2451
2452 /* we ran off the end of the old list */
2453 if (index == -1 && i < new_names->len)
2454 index = j;
2455 }
2456 }
2457 else
2458 {
2459 /* A phantom anchor still exists at the point where the last item
2460 * was selected, which is used for subsequent range selections.
2461 * So search up from there.
2462 */
2463 if (fs->last_selected &&
2464 compare_filenames (fs->last_selected,
2465 g_ptr_array_index (new_names, 0)) == 0)
2466 index = new_names->len - 1;
2467 else
2468 index = 0;
2469 }
2470 }
2471 else
2472 index = 0;
2473
2474 if (fs->selected_names != NULL)
2475 free_selected_names (fs->selected_names);
2476
2477 fs->selected_names = new_names;
2478
2479 if (index != -1)
2480 {
2481 const gchar * err;
2482 gchar str[256];
2483 err = gtk_label_get_text (GTK_LABEL (fs->selection_text));
2484 err += 11; //pass over "Selection: "
2485 sprintf(str,"%s\0",err);
2486
2487
2488 if (fs->last_selected != NULL)
2489 g_free (fs->last_selected);
2490
2491 fs->last_selected = g_strdup (g_ptr_array_index (new_names, index));
2492 filename = get_real_filename (fs->last_selected, FALSE);
2493
2494 strcat(str,"/");
2495 strcat(str,filename);
2496 str[strlen(str)-1] = '\0';
2497
2498 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), str);
2499
2500 if (filename != fs->last_selected)
2501 g_free (filename);
2502
2503 return;
2504 }
2505
2506 maybe_clear_entry:
2507
2508 entry = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
2509 if ((entry != NULL) && (fs->last_selected != NULL) &&
2510 (compare_filenames (entry, fs->last_selected) == 0))
2511 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
2512 }
2513
2514 /**
2515 * gtk_dir_selection_get_selections:
2516 * @filesel: a #GtkDirSelection
2517 *
2518 * Retrieves the list of file selections the user has made in the dialog box.
2519 * This function is intended for use when the user can select multiple files
2520 * in the file list. The first file in the list is equivalent to what
2521 * gtk_dir_selection_get_filename() would return.
2522 *
2523 * The filenames are in the encoding of g_filename_from_utf8(), which may or
2524 * may not be the same as that used by GTK+ (UTF-8). To convert to UTF-8, call
2525 * g_filename_to_utf8() on each string.
2526 *
2527 * Return value: a newly-allocated %NULL-terminated array of strings. Use
2528 * g_strfreev() to free it.
2529 **/
2530 gchar **
2531 gtk_dir_selection_get_selections (GtkDirSelection *filesel)
2532 {
2533 GPtrArray *names;
2534 gchar **selections;
2535 gchar *filename, *dirname;
2536 gchar *current, *buf;
2537 gint i, count;
2538 gboolean unselected_entry;
2539
2540 g_return_val_if_fail (GTK_IS_DIR_SELECTION (filesel), NULL);
2541
2542 filename = g_strdup (gtk_dir_selection_get_filename (filesel));
2543
2544 if (strlen (filename) == 0)
2545 {
2546 g_free (filename);
2547 return NULL;
2548 }
2549
2550 names = filesel->selected_names;
2551
2552 if (names != NULL)
2553 selections = g_new (gchar *, names->len + 2);
2554 else
2555 selections = g_new (gchar *, 2);
2556
2557 count = 0;
2558 unselected_entry = TRUE;
2559
2560 if (names != NULL)
2561 {
2562 dirname = g_path_get_dirname (filename);
2563
2564 for (i = 0; i < names->len; i++)
2565 {
2566 buf = g_filename_from_utf8 (g_ptr_array_index (names, i), -1,
2567 NULL, NULL, NULL);
2568 current = g_build_filename (dirname, buf, NULL);
2569 g_free (buf);
2570
2571 selections[count++] = current;
2572
2573 if (unselected_entry && compare_filenames (current, filename) == 0)
2574 unselected_entry = FALSE;
2575 }
2576
2577 g_free (dirname);
2578 }
2579
2580 if (unselected_entry)
2581 selections[count++] = filename;
2582 else
2583 g_free (filename);
2584
2585 selections[count] = NULL;
2586
2587 return selections;
2588 }
2589
2590 /**********************************************************************/
2591 /* External Interface */
2592 /**********************************************************************/
2593
2594 /* The four completion state selectors
2595 */
2596 static gchar*
2597 cmpl_updated_text (CompletionState *cmpl_state)
2598 {
2599 return cmpl_state->updated_text;
2600 }
2601
2602 static gboolean
2603 cmpl_updated_dir (CompletionState *cmpl_state)
2604 {
2605 return cmpl_state->re_complete;
2606 }
2607
2608 static gchar*
2609 cmpl_reference_position (CompletionState *cmpl_state)
2610 {
2611 return cmpl_state->reference_dir->fullname;
2612 }
2613
2614 static gint
2615 cmpl_last_valid_char (CompletionState *cmpl_state)
2616 {
2617 return cmpl_state->last_valid_char;
2618 }
2619
2620 static const gchar*
2621 cmpl_completion_fullname (const gchar *text,
2622 CompletionState *cmpl_state)
2623 {
2624 static const char nothing[2] = "";
2625
2626 if (!cmpl_state_okay (cmpl_state))
2627 {
2628 return nothing;
2629 }
2630 else if (g_path_is_absolute (text))
2631 {
2632 strcpy (cmpl_state->updated_text, text);
2633 }
2634 #ifdef HAVE_PWD_H
2635 else if (text[0] == '~')
2636 {
2637 CompletionDir* dir;
2638 char* slash;
2639
2640 dir = open_user_dir (text, cmpl_state);
2641
2642 if (!dir)
2643 {
2644 /* spencer says just return ~something, so
2645 * for now just do it. */
2646 strcpy (cmpl_state->updated_text, text);
2647 }
2648 else
2649 {
2650
2651 strcpy (cmpl_state->updated_text, dir->fullname);
2652
2653 slash = strchr (text, G_DIR_SEPARATOR);
2654
2655 if (slash)
2656 strcat (cmpl_state->updated_text, slash);
2657 }
2658 }
2659 #endif
2660 else
2661 {
2662 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
2663 if (cmpl_state->updated_text[strlen (cmpl_state->updated_text) - 1] != G_DIR_SEPARATOR)
2664 strcat (cmpl_state->updated_text, G_DIR_SEPARATOR_S);
2665 strcat (cmpl_state->updated_text, text);
2666 }
2667
2668 return cmpl_state->updated_text;
2669 }
2670
2671 /* The three completion selectors
2672 */
2673 static gchar*
2674 cmpl_this_completion (PossibleCompletion* pc)
2675 {
2676 return pc->text;
2677 }
2678
2679 static gboolean
2680 cmpl_is_directory (PossibleCompletion* pc)
2681 {
2682 return pc->is_directory;
2683 }
2684
2685 static gint
2686 cmpl_is_a_completion (PossibleCompletion* pc)
2687 {
2688 return pc->is_a_completion;
2689 }
2690
2691 /**********************************************************************/
2692 /* Construction, deletion */
2693 /**********************************************************************/
2694
2695 static CompletionState*
2696 cmpl_init_state (void)
2697 {
2698 gchar *sys_getcwd_buf;
2699 gchar *utf8_cwd;
2700 CompletionState *new_state;
2701
2702 new_state = g_new (CompletionState, 1);
2703
2704 /* g_get_current_dir() returns a string in the "system" charset */
2705 sys_getcwd_buf = g_get_current_dir ();
2706 utf8_cwd = g_filename_to_utf8 (sys_getcwd_buf, -1, NULL, NULL, NULL);
2707 g_free (sys_getcwd_buf);
2708
2709 tryagain:
2710
2711 new_state->reference_dir = NULL;
2712 new_state->completion_dir = NULL;
2713 new_state->active_completion_dir = NULL;
2714 new_state->directory_storage = NULL;
2715 new_state->directory_sent_storage = NULL;
2716 new_state->last_valid_char = 0;
2717 new_state->updated_text = g_new (gchar, MAXPATHLEN);
2718 new_state->updated_text_alloc = MAXPATHLEN;
2719 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
2720 new_state->the_completion.text_alloc = MAXPATHLEN;
2721 new_state->user_dir_name_buffer = NULL;
2722 new_state->user_directories = NULL;
2723
2724 new_state->reference_dir = open_dir (utf8_cwd, new_state);
2725
2726 if (!new_state->reference_dir)
2727 {
2728 /* Directories changing from underneath us, grumble */
2729 strcpy (utf8_cwd, G_DIR_SEPARATOR_S);
2730 goto tryagain;
2731 }
2732
2733 g_free (utf8_cwd);
2734 return new_state;
2735 }
2736
2737 static void
2738 cmpl_free_dir_list (GList* dp0)
2739 {
2740 GList *dp = dp0;
2741
2742 while (dp)
2743 {
2744 free_dir (dp->data);
2745 dp = dp->next;
2746 }
2747
2748 g_list_free (dp0);
2749 }
2750
2751 static void
2752 cmpl_free_dir_sent_list (GList* dp0)
2753 {
2754 GList *dp = dp0;
2755
2756 while (dp)
2757 {
2758 free_dir_sent (dp->data);
2759 dp = dp->next;
2760 }
2761
2762 g_list_free (dp0);
2763 }
2764
2765 static void
2766 cmpl_free_state (CompletionState* cmpl_state)
2767 {
2768 g_return_if_fail (cmpl_state != NULL);
2769
2770 cmpl_free_dir_list (cmpl_state->directory_storage);
2771 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2772
2773 if (cmpl_state->user_dir_name_buffer)
2774 g_free (cmpl_state->user_dir_name_buffer);
2775 if (cmpl_state->user_directories)
2776 g_free (cmpl_state->user_directories);
2777 if (cmpl_state->the_completion.text)
2778 g_free (cmpl_state->the_completion.text);
2779 if (cmpl_state->updated_text)
2780 g_free (cmpl_state->updated_text);
2781
2782 g_free (cmpl_state);
2783 }
2784
2785 static void
2786 free_dir (CompletionDir* dir)
2787 {
2788 g_free (dir->cmpl_text);
2789 g_free (dir->fullname);
2790 g_free (dir);
2791 }
2792
2793 static void
2794 free_dir_sent (CompletionDirSent* sent)
2795 {
2796 gint i;
2797 for (i = 0; i < sent->entry_count; i++)
2798 {
2799 g_free (sent->entries[i].entry_name);
2800 g_free (sent->entries[i].sort_key);
2801 }
2802 g_free (sent->entries);
2803 g_free (sent);
2804 }
2805
2806 static void
2807 prune_memory_usage (CompletionState *cmpl_state)
2808 {
2809 GList* cdsl = cmpl_state->directory_sent_storage;
2810 GList* cdl = cmpl_state->directory_storage;
2811 GList* cdl0 = cdl;
2812 gint len = 0;
2813
2814 for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2815 cdsl = cdsl->next;
2816
2817 if (cdsl)
2818 {
2819 cmpl_free_dir_sent_list (cdsl->next);
2820 cdsl->next = NULL;
2821 }
2822
2823 cmpl_state->directory_storage = NULL;
2824 while (cdl)
2825 {
2826 if (cdl->data == cmpl_state->reference_dir)
2827 cmpl_state->directory_storage = g_list_prepend (NULL, cdl->data);
2828 else
2829 free_dir (cdl->data);
2830 cdl = cdl->next;
2831 }
2832
2833 g_list_free (cdl0);
2834 }
2835
2836 /**********************************************************************/
2837 /* The main entrances. */
2838 /**********************************************************************/
2839
2840 static PossibleCompletion*
2841 cmpl_completion_matches (gchar *text_to_complete,
2842 gchar **remaining_text,
2843 CompletionState *cmpl_state)
2844 {
2845 gchar* first_slash;
2846 PossibleCompletion *poss;
2847
2848 prune_memory_usage (cmpl_state);
2849
2850 g_assert (text_to_complete != NULL);
2851
2852 cmpl_state->user_completion_index = -1;
2853 cmpl_state->last_completion_text = text_to_complete;
2854 cmpl_state->the_completion.text[0] = 0;
2855 cmpl_state->last_valid_char = 0;
2856 cmpl_state->updated_text_len = -1;
2857 cmpl_state->updated_text[0] = 0;
2858 cmpl_state->re_complete = FALSE;
2859
2860 #ifdef HAVE_PWD_H
2861 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2862
2863 if (text_to_complete[0] == '~' && !first_slash)
2864 {
2865 /* Text starts with ~ and there is no slash, show all the
2866 * home directory completions.
2867 */
2868 poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2869
2870 update_cmpl (poss, cmpl_state);
2871
2872 return poss;
2873 }
2874 #endif
2875 cmpl_state->reference_dir =
2876 open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2877
2878 if (!cmpl_state->reference_dir)
2879 return NULL;
2880
2881 cmpl_state->completion_dir =
2882 find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2883
2884 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2885
2886 if (!cmpl_state->completion_dir)
2887 return NULL;
2888
2889 cmpl_state->completion_dir->cmpl_index = -1;
2890 cmpl_state->completion_dir->cmpl_parent = NULL;
2891 cmpl_state->completion_dir->cmpl_text = g_strdup (*remaining_text);
2892
2893 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2894
2895 cmpl_state->reference_dir = cmpl_state->completion_dir;
2896
2897 poss = attempt_dir_completion (cmpl_state);
2898
2899 update_cmpl (poss, cmpl_state);
2900
2901 return poss;
2902 }
2903
2904 static PossibleCompletion*
2905 cmpl_next_completion (CompletionState* cmpl_state)
2906 {
2907 PossibleCompletion* poss = NULL;
2908
2909 cmpl_state->the_completion.text[0] = 0;
2910
2911 #ifdef HAVE_PWD_H
2912 if (cmpl_state->user_completion_index >= 0)
2913 poss = attempt_homedir_completion (cmpl_state->last_completion_text, cmpl_state);
2914 else
2915 poss = attempt_dir_completion (cmpl_state);
2916 #else
2917 poss = attempt_dir_completion (cmpl_state);
2918 #endif
2919
2920 update_cmpl (poss, cmpl_state);
2921
2922 return poss;
2923 }
2924
2925 /**********************************************************************/
2926 /* Directory Operations */
2927 /**********************************************************************/
2928
2929 /* Open the directory where completion will begin from, if possible. */
2930 static CompletionDir*
2931 open_ref_dir (gchar *text_to_complete,
2932 gchar **remaining_text,
2933 CompletionState *cmpl_state)
2934 {
2935 gchar* first_slash;
2936 CompletionDir *new_dir;
2937
2938 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2939
2940 #ifdef G_WITH_CYGWIN
2941 if (text_to_complete[0] == '/' && text_to_complete[1] == '/')
2942 {
2943 char root_dir[5];
2944 g_snprintf (root_dir, sizeof (root_dir), "//%c", text_to_complete[2]);
2945
2946 new_dir = open_dir (root_dir, cmpl_state);
2947
2948 if (new_dir) {
2949 *remaining_text = text_to_complete + 4;
2950 }
2951 }
2952 #else
2953 if (FALSE)
2954 ;
2955 #endif
2956 #ifdef HAVE_PWD_H
2957 else if (text_to_complete[0] == '~')
2958 {
2959 new_dir = open_user_dir (text_to_complete, cmpl_state);
2960
2961 if (new_dir)
2962 {
2963 if (first_slash)
2964 *remaining_text = first_slash + 1;
2965 else
2966 *remaining_text = text_to_complete + strlen (text_to_complete);
2967 }
2968 else
2969 {
2970 return NULL;
2971 }
2972 }
2973 #endif
2974 else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir)
2975 {
2976 gchar *tmp = g_strdup (text_to_complete);
2977 gchar *p;
2978
2979 p = tmp;
2980 while (*p && *p != '*' && *p != '?')
2981 p++;
2982
2983 *p = '\0';
2984 p = strrchr (tmp, G_DIR_SEPARATOR);
2985 if (p)
2986 {
2987 if (p == tmp)
2988 p++;
2989
2990 *p = '\0';
2991
2992 new_dir = open_dir (tmp, cmpl_state);
2993
2994 if (new_dir)
2995 *remaining_text = text_to_complete +
2996 ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2997 }
2998 else
2999 {
3000 /* If no possible candidates, use the cwd */
3001 gchar *sys_curdir = g_get_current_dir ();
3002 gchar *utf8_curdir = g_filename_to_utf8 (sys_curdir, -1, NULL, NULL, NULL);
3003
3004 g_free (sys_curdir);
3005
3006 new_dir = open_dir (utf8_curdir, cmpl_state);
3007
3008 if (new_dir)
3009 *remaining_text = text_to_complete;
3010
3011 g_free (utf8_curdir);
3012 }
3013
3014 g_free (tmp);
3015 }
3016 else
3017 {
3018 *remaining_text = text_to_complete;
3019
3020 new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state);
3021 }
3022
3023 if (new_dir)
3024 {
3025 new_dir->cmpl_index = -1;
3026 new_dir->cmpl_parent = NULL;
3027 }
3028
3029 return new_dir;
3030 }
3031
3032 #ifdef HAVE_PWD_H
3033
3034 /* open a directory by user name */
3035 static CompletionDir*
3036 open_user_dir (const gchar *text_to_complete,
3037 CompletionState *cmpl_state)
3038 {
3039 CompletionDir *result;
3040 gchar *first_slash;
3041 gint cmp_len;
3042
3043 g_assert (text_to_complete && text_to_complete[0] == '~');
3044
3045 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
3046
3047 if (first_slash)
3048 cmp_len = first_slash - text_to_complete - 1;
3049 else
3050 cmp_len = strlen (text_to_complete + 1);
3051
3052 if (!cmp_len)
3053 {
3054 /* ~/ */
3055 const gchar *homedir = g_get_home_dir ();
3056 gchar *utf8_homedir = g_filename_to_utf8 (homedir, -1, NULL, NULL, NULL);
3057
3058 if (utf8_homedir)
3059 result = open_dir (utf8_homedir, cmpl_state);
3060 else
3061 result = NULL;
3062
3063 g_free (utf8_homedir);
3064 }
3065 else
3066 {
3067 /* ~user/ */
3068 gchar* copy = g_new (char, cmp_len + 1);
3069 gchar *utf8_dir;
3070 struct passwd *pwd;
3071
3072 strncpy (copy, text_to_complete + 1, cmp_len);
3073 copy[cmp_len] = 0;
3074 pwd = getpwnam (copy);
3075 g_free (copy);
3076 if (!pwd)
3077 {
3078 cmpl_errno = errno;
3079 return NULL;
3080 }
3081 utf8_dir = g_filename_to_utf8 (pwd->pw_dir, -1, NULL, NULL, NULL);
3082 result = open_dir (utf8_dir, cmpl_state);
3083 g_free (utf8_dir);
3084 }
3085 return result;
3086 }
3087
3088 #endif
3089
3090 /* open a directory relative the the current relative directory */
3091 static CompletionDir*
3092 open_relative_dir (gchar *dir_name,
3093 CompletionDir *dir,
3094 CompletionState *cmpl_state)
3095 {
3096 CompletionDir *result;
3097 GString *path;
3098
3099 path = g_string_sized_new (dir->fullname_len + strlen (dir_name) + 10);
3100 g_string_assign (path, dir->fullname);
3101
3102 if (dir->fullname_len > 1
3103 && path->str[dir->fullname_len - 1] != G_DIR_SEPARATOR)
3104 g_string_append_c (path, G_DIR_SEPARATOR);
3105 g_string_append (path, dir_name);
3106
3107 result = open_dir (path->str, cmpl_state);
3108
3109 g_string_free (path, TRUE);
3110
3111 return result;
3112 }
3113
3114 /* after the cache lookup fails, really open a new directory */
3115 static CompletionDirSent*
3116 open_new_dir (gchar *dir_name,
3117 struct stat *sbuf,
3118 gboolean stat_subdirs)
3119 {
3120 CompletionDirSent *sent;
3121 GDir *directory;
3122 const char *dirent;
3123 GError *error = NULL;
3124 gint entry_count = 0;
3125 gint n_entries = 0;
3126 gint i;
3127 struct stat ent_sbuf;
3128 GString *path;
3129 gchar *sys_dir_name;
3130
3131 sent = g_new (CompletionDirSent, 1);
3132 sent->mtime = sbuf->st_mtime;
3133 sent->inode = sbuf->st_ino;
3134 sent->device = sbuf->st_dev;
3135
3136 path = g_string_sized_new (2*MAXPATHLEN + 10);
3137
3138 sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
3139 if (!sys_dir_name)
3140 {
3141 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3142 return NULL;
3143 }
3144
3145 directory = g_dir_open (sys_dir_name, 0, &error);
3146 if (!directory)
3147 {
3148 cmpl_errno = error->code; /* ??? */
3149 g_free (sys_dir_name);
3150 return NULL;
3151 }
3152
3153 while ((dirent = g_dir_read_name (directory)) != NULL)
3154 entry_count++;
3155
3156 entry_count += 2; /* For ".",".." */
3157
3158 sent->entries = g_new (CompletionDirEntry, entry_count);
3159 sent->entry_count = entry_count;
3160
3161 g_dir_rewind (directory);
3162
3163 for (i = 0; i < entry_count; i += 1)
3164 {
3165 GError *error = NULL;
3166
3167 if (i == 0)
3168 dirent = ".";
3169 else if (i == 1)
3170 dirent = "..";
3171 else
3172 {
3173 dirent = g_dir_read_name (directory);
3174 if (!dirent) /* Directory changed */
3175 break;
3176 }
3177
3178 sent->entries[n_entries].entry_name = g_filename_to_utf8 (dirent, -1, NULL, NULL, &error);
3179 if (sent->entries[n_entries].entry_name == NULL
3180 || !g_utf8_validate (sent->entries[n_entries].entry_name, -1, NULL))
3181 {
3182 gchar *escaped_str = g_strescape (dirent, NULL);
3183 g_message (_("The filename \"%s\" couldn't be converted to UTF-8 "
3184 "(try setting the environment variable G_BROKEN_FILENAMES): %s"),
3185 escaped_str,
3186 error->message ? error->message : _("Invalid Utf-8"));
3187 g_free (escaped_str);
3188 g_clear_error (&error);
3189 continue;
3190 }
3191 g_clear_error (&error);
3192
3193 sent->entries[n_entries].sort_key = g_utf8_collate_key (sent->entries[n_entries].entry_name, -1);
3194
3195 g_string_assign (path, sys_dir_name);
3196 if (path->str[path->len-1] != G_DIR_SEPARATOR)
3197 {
3198 g_string_append_c (path, G_DIR_SEPARATOR);
3199 }
3200 g_string_append (path, dirent);
3201
3202 if (stat_subdirs)
3203 {
3204 /* Here we know path->str is a "system charset" string */
3205 if (stat (path->str, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode))
3206 sent->entries[n_entries].is_dir = TRUE;
3207 else
3208 /* stat may fail, and we don't mind, since it could be a
3209 * dangling symlink. */
3210 sent->entries[n_entries].is_dir = FALSE;
3211 }
3212 else
3213 sent->entries[n_entries].is_dir = 1;
3214
3215 n_entries++;
3216 }
3217 sent->entry_count = n_entries;
3218
3219 g_free (sys_dir_name);
3220 g_string_free (path, TRUE);
3221 qsort (sent->entries, sent->entry_count, sizeof (CompletionDirEntry), compare_cmpl_dir);
3222
3223 g_dir_close (directory);
3224
3225 return sent;
3226 }
3227
3228 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3229
3230 static gboolean
3231 check_dir (gchar *dir_name,
3232 struct stat *result,
3233 gboolean *stat_subdirs)
3234 {
3235 /* A list of directories that we know only contain other directories.
3236 * Trying to stat every file in these directories would be very
3237 * expensive.
3238 */
3239
3240 static struct {
3241 gchar *name;
3242 gboolean present;
3243 struct stat statbuf;
3244 } no_stat_dirs[] = {
3245 { "/afs", FALSE, { 0 } },
3246 { "/net", FALSE, { 0 } }
3247 };
3248
3249 static const gint n_no_stat_dirs = G_N_ELEMENTS (no_stat_dirs);
3250 static gboolean initialized = FALSE;
3251 gchar *sys_dir_name;
3252 gint i;
3253
3254 if (!initialized)
3255 {
3256 initialized = TRUE;
3257 for (i = 0; i < n_no_stat_dirs; i++)
3258 {
3259 if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
3260 no_stat_dirs[i].present = TRUE;
3261 }
3262 }
3263
3264 sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
3265 if (!sys_dir_name)
3266 {
3267 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3268 return FALSE;
3269 }
3270
3271 if (stat (sys_dir_name, result) < 0)
3272 {
3273 g_free (sys_dir_name);
3274 cmpl_errno = errno;
3275 return FALSE;
3276 }
3277 g_free (sys_dir_name);
3278
3279 *stat_subdirs = TRUE;
3280 for (i = 0; i < n_no_stat_dirs; i++)
3281 {
3282 if (no_stat_dirs[i].present &&
3283 (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
3284 (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
3285 {
3286 *stat_subdirs = FALSE;
3287 break;
3288 }
3289 }
3290
3291 return TRUE;
3292 }
3293
3294 #endif
3295
3296 /* open a directory by absolute pathname */
3297 static CompletionDir*
3298 open_dir (gchar *dir_name,
3299 CompletionState *cmpl_state)
3300 {
3301 struct stat sbuf;
3302 gboolean stat_subdirs;
3303 CompletionDirSent *sent;
3304 GList* cdsl;
3305
3306 #if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3307 if (!check_dir (dir_name, &sbuf, &stat_subdirs))
3308 return NULL;
3309
3310 cdsl = cmpl_state->directory_sent_storage;
3311
3312 while (cdsl)
3313 {
3314 sent = cdsl->data;
3315
3316 if (sent->inode == sbuf.st_ino &&
3317 sent->mtime == sbuf.st_mtime &&
3318 sent->device == sbuf.st_dev)
3319 return attach_dir (sent, dir_name, cmpl_state);
3320
3321 cdsl = cdsl->next;
3322 }
3323 #else
3324 stat_subdirs = TRUE;
3325 #endif
3326
3327 sent = open_new_dir (dir_name, &sbuf, stat_subdirs);
3328
3329 if (sent)
3330 {
3331 cmpl_state->directory_sent_storage =
3332 g_list_prepend (cmpl_state->directory_sent_storage, sent);
3333
3334 return attach_dir (sent, dir_name, cmpl_state);
3335 }
3336
3337 return NULL;
3338 }
3339
3340 static CompletionDir*
3341 attach_dir (CompletionDirSent *sent,
3342 gchar *dir_name,
3343 CompletionState *cmpl_state)
3344 {
3345 CompletionDir* new_dir;
3346
3347 new_dir = g_new (CompletionDir, 1);
3348
3349 cmpl_state->directory_storage =
3350 g_list_prepend (cmpl_state->directory_storage, new_dir);
3351
3352 new_dir->sent = sent;
3353 new_dir->fullname = g_strdup (dir_name);
3354 new_dir->fullname_len = strlen (dir_name);
3355 new_dir->cmpl_text = NULL;
3356
3357 return new_dir;
3358 }
3359
3360 static gint
3361 correct_dir_fullname (CompletionDir* cmpl_dir)
3362 {
3363 gint length = strlen (cmpl_dir->fullname);
3364 gchar *first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3365 gchar *sys_filename;
3366 struct stat sbuf;
3367
3368 /* Does it end with /. (\.) ? */
3369 if (length >= 2 &&
3370 strcmp (cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0)
3371 {
3372 /* Is it just the root directory (on a drive) ? */
3373 if (cmpl_dir->fullname + length - 2 == first_slash)
3374 {
3375 cmpl_dir->fullname[length - 1] = 0;
3376 cmpl_dir->fullname_len = length - 1;
3377 return TRUE;
3378 }
3379 else
3380 {
3381 cmpl_dir->fullname[length - 2] = 0;
3382 }
3383 }
3384
3385 /* Ends with /./ (\.\)? */
3386 else if (length >= 3 &&
3387 strcmp (cmpl_dir->fullname + length - 3,
3388 G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0)
3389 cmpl_dir->fullname[length - 2] = 0;
3390
3391 /* Ends with /.. (\..) ? */
3392 else if (length >= 3 &&
3393 strcmp (cmpl_dir->fullname + length - 3,
3394 G_DIR_SEPARATOR_S "..") == 0)
3395 {
3396 /* Is it just /.. (X:\..)? */
3397 if (cmpl_dir->fullname + length - 3 == first_slash)
3398 {
3399 cmpl_dir->fullname[length - 2] = 0;
3400 cmpl_dir->fullname_len = length - 2;
3401 return TRUE;
3402 }
3403
3404 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3405 if (!sys_filename)
3406 {
3407 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3408 return FALSE;
3409 }
3410
3411 if (stat (sys_filename, &sbuf) < 0)
3412 {
3413 g_free (sys_filename);
3414 cmpl_errno = errno;
3415 return FALSE;
3416 }
3417 g_free (sys_filename);
3418
3419 cmpl_dir->fullname[length - 3] = 0;
3420
3421 if (!correct_parent (cmpl_dir, &sbuf))
3422 return FALSE;
3423 }
3424
3425 /* Ends with /../ (\..\)? */
3426 else if (length >= 4 &&
3427 strcmp (cmpl_dir->fullname + length - 4,
3428 G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0)
3429 {
3430 /* Is it just /../ (X:\..\)? */
3431 if (cmpl_dir->fullname + length - 4 == first_slash)
3432 {
3433 cmpl_dir->fullname[length - 3] = 0;
3434 cmpl_dir->fullname_len = length - 3;
3435 return TRUE;
3436 }
3437
3438 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3439 if (!sys_filename)
3440 {
3441 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3442 return FALSE;
3443 }
3444
3445 if (stat (sys_filename, &sbuf) < 0)
3446 {
3447 g_free (sys_filename);
3448 cmpl_errno = errno;
3449 return FALSE;
3450 }
3451 g_free (sys_filename);
3452
3453 cmpl_dir->fullname[length - 4] = 0;
3454
3455 if (!correct_parent (cmpl_dir, &sbuf))
3456 return FALSE;
3457 }
3458
3459 cmpl_dir->fullname_len = strlen (cmpl_dir->fullname);
3460
3461 return TRUE;
3462 }
3463
3464 static gint
3465 correct_parent (CompletionDir *cmpl_dir,
3466 struct stat *sbuf)
3467 {
3468 struct stat parbuf;
3469 gchar *last_slash;
3470 gchar *first_slash;
3471 gchar *new_name;
3472 gchar *sys_filename;
3473 gchar c = 0;
3474
3475 last_slash = strrchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3476 g_assert (last_slash);
3477 first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3478
3479 /* Clever (?) way to check for top-level directory that works also on
3480 * Win32, where there is a drive letter and colon prefixed...
3481 */
3482 if (last_slash != first_slash)
3483 {
3484 last_slash[0] = 0;
3485 }
3486 else
3487 {
3488 c = last_slash[1];
3489 last_slash[1] = 0;
3490 }
3491
3492 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3493 if (!sys_filename)
3494 {
3495 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3496 if (!c)
3497 last_slash[0] = G_DIR_SEPARATOR;
3498 return FALSE;
3499 }
3500
3501 if (stat (sys_filename, &parbuf) < 0)
3502 {
3503 g_free (sys_filename);
3504 cmpl_errno = errno;
3505 if (!c)
3506 last_slash[0] = G_DIR_SEPARATOR;
3507 return FALSE;
3508 }
3509 g_free (sys_filename);
3510
3511 #ifndef G_OS_WIN32 /* No inode numbers on Win32 */
3512 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
3513 /* it wasn't a link */
3514 return TRUE;
3515
3516 if (c)
3517 last_slash[1] = c;
3518 else
3519 last_slash[0] = G_DIR_SEPARATOR;
3520
3521 /* it was a link, have to figure it out the hard way */
3522
3523 new_name = find_parent_dir_fullname (cmpl_dir->fullname);
3524
3525 if (!new_name)
3526 return FALSE;
3527
3528 g_free (cmpl_dir->fullname);
3529
3530 cmpl_dir->fullname = new_name;
3531 #endif
3532
3533 return TRUE;
3534 }
3535
3536 #ifndef G_OS_WIN32
3537
3538 static gchar*
3539 find_parent_dir_fullname (gchar* dirname)
3540 {
3541 gchar *sys_orig_dir;
3542 gchar *result;
3543 gchar *sys_cwd;
3544 gchar *sys_dirname;
3545
3546 sys_orig_dir = g_get_current_dir ();
3547 sys_dirname = g_filename_from_utf8 (dirname, -1, NULL, NULL, NULL);
3548 if (!sys_dirname)
3549 {
3550 g_free (sys_orig_dir);
3551 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3552 return NULL;
3553 }
3554
3555 if (chdir (sys_dirname) != 0 || chdir ("..") != 0)
3556 {
3557 cmpl_errno = errno;
3558 chdir (sys_orig_dir);
3559 g_free (sys_dirname);
3560 g_free (sys_orig_dir);
3561 return NULL;
3562 }
3563 g_free (sys_dirname);
3564
3565 sys_cwd = g_get_current_dir ();
3566 result = g_filename_to_utf8 (sys_cwd, -1, NULL, NULL, NULL);
3567 g_free (sys_cwd);
3568
3569 if (chdir (sys_orig_dir) != 0)
3570 {
3571 cmpl_errno = errno;
3572 g_free (sys_orig_dir);
3573 return NULL;
3574 }
3575
3576 g_free (sys_orig_dir);
3577 return result;
3578 }
3579
3580 #endif
3581
3582 /**********************************************************************/
3583 /* Completion Operations */
3584 /**********************************************************************/
3585
3586 #ifdef HAVE_PWD_H
3587
3588 static PossibleCompletion*
3589 attempt_homedir_completion (gchar *text_to_complete,
3590 CompletionState *cmpl_state)
3591 {
3592 gint index, length;
3593
3594 if (!cmpl_state->user_dir_name_buffer &&
3595 !get_pwdb (cmpl_state))
3596 return NULL;
3597 length = strlen (text_to_complete) - 1;
3598
3599 cmpl_state->user_completion_index += 1;
3600
3601 while (cmpl_state->user_completion_index < cmpl_state->user_directories_len)
3602 {
3603 index = first_diff_index (text_to_complete + 1,
3604 cmpl_state->user_directories
3605 [cmpl_state->user_completion_index].login);
3606
3607 switch (index)
3608 {
3609 case PATTERN_MATCH:
3610 break;
3611 default:
3612 if (cmpl_state->last_valid_char < (index + 1))
3613 cmpl_state->last_valid_char = index + 1;
3614 cmpl_state->user_completion_index += 1;
3615 continue;
3616 }
3617
3618 cmpl_state->the_completion.is_a_completion = 1;
3619 cmpl_state->the_completion.is_directory = TRUE;
3620
3621 append_completion_text ("~", cmpl_state);
3622
3623 append_completion_text (cmpl_state->
3624 user_directories[cmpl_state->user_completion_index].login,
3625 cmpl_state);
3626
3627 return append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3628 }
3629
3630 if (text_to_complete[1]
3631 || cmpl_state->user_completion_index > cmpl_state->user_directories_len)
3632 {
3633 cmpl_state->user_completion_index = -1;
3634 return NULL;
3635 }
3636 else
3637 {
3638 cmpl_state->user_completion_index += 1;
3639 cmpl_state->the_completion.is_a_completion = 1;
3640 cmpl_state->the_completion.is_directory = TRUE;
3641
3642 return append_completion_text ("~" G_DIR_SEPARATOR_S, cmpl_state);
3643 }
3644 }
3645
3646 #endif
3647
3648 #if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
3649 #define FOLD(c) (tolower(c))
3650 #else
3651 #define FOLD(c) (c)
3652 #endif
3653
3654 /* returns the index (>= 0) of the first differing character,
3655 * PATTERN_MATCH if the completion matches */
3656 static gint
3657 first_diff_index (gchar *pat,
3658 gchar *text)
3659 {
3660 gint diff = 0;
3661
3662 while (*pat && *text && FOLD (*text) == FOLD (*pat))
3663 {
3664 pat += 1;
3665 text += 1;
3666 diff += 1;
3667 }
3668
3669 if (*pat)
3670 return diff;
3671
3672 return PATTERN_MATCH;
3673 }
3674
3675 static PossibleCompletion*
3676 append_completion_text (gchar *text,
3677 CompletionState *cmpl_state)
3678 {
3679 gint len, i = 1;
3680
3681 if (!cmpl_state->the_completion.text)
3682 return NULL;
3683
3684 len = strlen (text) + strlen (cmpl_state->the_completion.text) + 1;
3685
3686 if (cmpl_state->the_completion.text_alloc > len)
3687 {
3688 strcat (cmpl_state->the_completion.text, text);
3689 return &cmpl_state->the_completion;
3690 }
3691
3692 while (i < len)
3693 i <<= 1;
3694
3695 cmpl_state->the_completion.text_alloc = i;
3696
3697 cmpl_state->the_completion.text = (gchar*) g_realloc (cmpl_state->the_completion.text, i);
3698
3699 if (!cmpl_state->the_completion.text)
3700 return NULL;
3701 else
3702 {
3703 strcat (cmpl_state->the_completion.text, text);
3704 return &cmpl_state->the_completion;
3705 }
3706 }
3707
3708 static CompletionDir*
3709 find_completion_dir (gchar *text_to_complete,
3710 gchar **remaining_text,
3711 CompletionState *cmpl_state)
3712 {
3713 gchar* first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
3714 CompletionDir* dir = cmpl_state->reference_dir;
3715 CompletionDir* next;
3716 *remaining_text = text_to_complete;
3717
3718 while (first_slash)
3719 {
3720 gint len = first_slash - *remaining_text;
3721 gint found = 0;
3722 gchar *found_name = NULL; /* Quiet gcc */
3723 gint i;
3724 gchar* pat_buf = g_new (gchar, len + 1);
3725
3726 strncpy (pat_buf, *remaining_text, len);
3727 pat_buf[len] = 0;
3728
3729 for (i = 0; i < dir->sent->entry_count; i += 1)
3730 {
3731 if (dir->sent->entries[i].is_dir &&
3732 _gtk_fnmatch (pat_buf, dir->sent->entries[i].entry_name))
3733 {
3734 if (found)
3735 {
3736 g_free (pat_buf);
3737 return dir;
3738 }
3739 else
3740 {
3741 found = 1;
3742 found_name = dir->sent->entries[i].entry_name;
3743 }
3744 }
3745 }
3746
3747 if (!found)
3748 {
3749 /* Perhaps we are trying to open an automount directory */
3750 found_name = pat_buf;
3751 }
3752
3753 next = open_relative_dir (found_name, dir, cmpl_state);
3754
3755 if (!next)
3756 {
3757 g_free (pat_buf);
3758 return NULL;
3759 }
3760
3761 next->cmpl_parent = dir;
3762
3763 dir = next;
3764
3765 if (!correct_dir_fullname (dir))
3766 {
3767 g_free (pat_buf);
3768 return NULL;
3769 }
3770
3771 *remaining_text = first_slash + 1;
3772 first_slash = strchr (*remaining_text, G_DIR_SEPARATOR);
3773
3774 g_free (pat_buf);
3775 }
3776
3777 return dir;
3778 }
3779
3780 static void
3781 update_cmpl (PossibleCompletion *poss,
3782 CompletionState *cmpl_state)
3783 {
3784 gint cmpl_len;
3785
3786 if (!poss || !cmpl_is_a_completion (poss))
3787 return;
3788
3789 cmpl_len = strlen (cmpl_this_completion (poss));
3790
3791 if (cmpl_state->updated_text_alloc < cmpl_len + 1)
3792 {
3793 cmpl_state->updated_text =
3794 (gchar*)g_realloc (cmpl_state->updated_text,
3795 cmpl_state->updated_text_alloc);
3796 cmpl_state->updated_text_alloc = 2*cmpl_len;
3797 }
3798
3799 if (cmpl_state->updated_text_len < 0)
3800 {
3801 strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3802 cmpl_state->updated_text_len = cmpl_len;
3803 cmpl_state->re_complete = cmpl_is_directory (poss);
3804 }
3805 else if (cmpl_state->updated_text_len == 0)
3806 {
3807 cmpl_state->re_complete = FALSE;
3808 }
3809 else
3810 {
3811 gint first_diff =
3812 first_diff_index (cmpl_state->updated_text,
3813 cmpl_this_completion (poss));
3814
3815 cmpl_state->re_complete = FALSE;
3816
3817 if (first_diff == PATTERN_MATCH)
3818 return;
3819
3820 if (first_diff > cmpl_state->updated_text_len)
3821 strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3822
3823 cmpl_state->updated_text_len = first_diff;
3824 cmpl_state->updated_text[first_diff] = 0;
3825 }
3826 }
3827
3828 static PossibleCompletion*
3829 attempt_dir_completion (CompletionState *cmpl_state)
3830 {
3831 gchar *pat_buf, *first_slash;
3832 CompletionDir *dir = cmpl_state->active_completion_dir;
3833
3834 dir->cmpl_index += 1;
3835
3836 if (dir->cmpl_index == dir->sent->entry_count)
3837 {
3838 if (dir->cmpl_parent == NULL)
3839 {
3840 cmpl_state->active_completion_dir = NULL;
3841
3842 return NULL;
3843 }
3844 else
3845 {
3846 cmpl_state->active_completion_dir = dir->cmpl_parent;
3847
3848 return attempt_dir_completion (cmpl_state);
3849 }
3850 }
3851
3852 g_assert (dir->cmpl_text);
3853
3854 first_slash = strchr (dir->cmpl_text, G_DIR_SEPARATOR);
3855
3856 if (first_slash)
3857 {
3858 gint len = first_slash - dir->cmpl_text;
3859
3860 pat_buf = g_new (gchar, len + 1);
3861 strncpy (pat_buf, dir->cmpl_text, len);
3862 pat_buf[len] = 0;
3863 }
3864 else
3865 {
3866 gint len = strlen (dir->cmpl_text);
3867
3868 pat_buf = g_new (gchar, len + 2);
3869 strcpy (pat_buf, dir->cmpl_text);
3870 /* Don't append a * if the user entered one herself.
3871 * This way one can complete *.h and don't get matches
3872 * on any .help files, for instance.
3873 */
3874 if (strchr (pat_buf, '*') == NULL)
3875 strcpy (pat_buf + len, "*");
3876 }
3877
3878 if (first_slash)
3879 {
3880 if (dir->sent->entries[dir->cmpl_index].is_dir)
3881 {
3882 if (_gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name))
3883 {
3884 CompletionDir* new_dir;
3885
3886 new_dir = open_relative_dir (dir->sent->entries[dir->cmpl_index].entry_name,
3887 dir, cmpl_state);
3888
3889 if (!new_dir)
3890 {
3891 g_free (pat_buf);
3892 return NULL;
3893 }
3894
3895 new_dir->cmpl_parent = dir;
3896
3897 new_dir->cmpl_index = -1;
3898 new_dir->cmpl_text = g_strdup (first_slash + 1);
3899
3900 cmpl_state->active_completion_dir = new_dir;
3901
3902 g_free (pat_buf);
3903 return attempt_dir_completion (cmpl_state);
3904 }
3905 else
3906 {
3907 g_free (pat_buf);
3908 return attempt_dir_completion (cmpl_state);
3909 }
3910 }
3911 else
3912 {
3913 g_free (pat_buf);
3914 return attempt_dir_completion (cmpl_state);
3915 }
3916 }
3917 else
3918 {
3919 if (dir->cmpl_parent != NULL)
3920 {
3921 append_completion_text (dir->fullname +
3922 strlen (cmpl_state->completion_dir->fullname) + 1,
3923 cmpl_state);
3924 append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3925 }
3926
3927 append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3928
3929 cmpl_state->the_completion.is_a_completion =
3930 _gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name);
3931
3932 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3933 if (dir->sent->entries[dir->cmpl_index].is_dir)
3934 append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3935
3936 g_free (pat_buf);
3937 return &cmpl_state->the_completion;
3938 }
3939 }
3940
3941 #ifdef HAVE_PWD_H
3942
3943 static gint
3944 get_pwdb (CompletionState* cmpl_state)
3945 {
3946 struct passwd *pwd_ptr;
3947 gchar* buf_ptr;
3948 gchar *utf8;
3949 gint len = 0, i, count = 0;
3950
3951 if (cmpl_state->user_dir_name_buffer)
3952 return TRUE;
3953 setpwent ();
3954
3955 while ((pwd_ptr = getpwent ()) != NULL)
3956 {
3957 utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3958 len += strlen (utf8);
3959 g_free (utf8);
3960 utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3961 len += strlen (utf8);
3962 g_free (utf8);
3963 len += 2;
3964 count += 1;
3965 }
3966
3967 setpwent ();
3968
3969 cmpl_state->user_dir_name_buffer = g_new (gchar, len);
3970 cmpl_state->user_directories = g_new (CompletionUserDir, count);
3971 cmpl_state->user_directories_len = count;
3972
3973 buf_ptr = cmpl_state->user_dir_name_buffer;
3974
3975 for (i = 0; i < count; i += 1)
3976 {
3977 pwd_ptr = getpwent ();
3978 if (!pwd_ptr)
3979 {
3980 cmpl_errno = errno;
3981 goto error;
3982 }
3983
3984 utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3985 strcpy (buf_ptr, utf8);
3986 g_free (utf8);
3987
3988 cmpl_state->user_directories[i].login = buf_ptr;
3989
3990 buf_ptr += strlen (buf_ptr);
3991 buf_ptr += 1;
3992
3993 utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3994 strcpy (buf_ptr, utf8);
3995 g_free (utf8);
3996
3997 cmpl_state->user_directories[i].homedir = buf_ptr;
3998
3999 buf_ptr += strlen (buf_ptr);
4000 buf_ptr += 1;
4001 }
4002
4003 qsort (cmpl_state->user_directories,
4004 cmpl_state->user_directories_len,
4005 sizeof (CompletionUserDir),
4006 compare_user_dir);
4007
4008 endpwent ();
4009
4010 return TRUE;
4011
4012 error:
4013
4014 if (cmpl_state->user_dir_name_buffer)
4015 g_free (cmpl_state->user_dir_name_buffer);
4016 if (cmpl_state->user_directories)
4017 g_free (cmpl_state->user_directories);
4018
4019 cmpl_state->user_dir_name_buffer = NULL;
4020 cmpl_state->user_directories = NULL;
4021
4022 return FALSE;
4023 }
4024
4025 static gint
4026 compare_user_dir (const void *a,
4027 const void *b)
4028 {
4029 return strcmp ((((CompletionUserDir*)a))->login,
4030 (((CompletionUserDir*)b))->login);
4031 }
4032
4033 #endif
4034
4035 static gint
4036 compare_cmpl_dir (const void *a,
4037 const void *b)
4038 {
4039
4040 return strcmp (((CompletionDirEntry*)a)->sort_key,
4041 (((CompletionDirEntry*)b))->sort_key);
4042 }
4043
4044 static gint
4045 cmpl_state_okay (CompletionState* cmpl_state)
4046 {
4047 return cmpl_state && cmpl_state->reference_dir;
4048 }
4049
4050 static const gchar*
4051 cmpl_strerror (gint err)
4052 {
4053 if (err == CMPL_ERRNO_TOO_LONG)
4054 return _("Name too long");
4055 else if (err == CMPL_ERRNO_DID_NOT_CONVERT)
4056 return _("Couldn't convert filename");
4057 else
4058 return g_strerror (err);
4059 }
4060
4061 const gchar * gtk_dir_selection_get_dir (GtkDirSelection *filesel)
4062 {
4063 return gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
4064 }
This page took 0.112637 seconds and 4 git commands to generate.