git-svn-id: http://ltt.polymtl.ca/svn@446 04897980-b3bd-0310-b5e0-8ef037075253
[lttv.git] / ltt / branches / poly / lttv / modules / gui / main / src / gtkdirsel.c
CommitLineData
e076699e 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 */
fc188b78 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 */
89typedef struct _HistoryCallbackArg HistoryCallbackArg;
90
91struct _HistoryCallbackArg
92{
93 gchar *directory;
94 GtkWidget *menu_item;
95};
96
97
98typedef struct _CompletionState CompletionState;
99typedef struct _CompletionDir CompletionDir;
100typedef struct _CompletionDirSent CompletionDirSent;
101typedef struct _CompletionDirEntry CompletionDirEntry;
102typedef struct _CompletionUserDir CompletionUserDir;
103typedef 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 */
122struct _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
132struct _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 */
152struct _CompletionDirEntry
153{
154 gboolean is_dir;
155 gchar *entry_name;
156 gchar *sort_key;
157};
158
159struct _CompletionUserDir
160{
161 gchar *login;
162 gchar *homedir;
163};
164
165struct _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
179struct _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
207enum {
208 PROP_0,
209 PROP_SHOW_FILEOPS,
210 PROP_FILENAME,
211 PROP_SELECT_MULTIPLE
212};
213
214enum {
215 DIR_COLUMN
216};
217
218enum {
219 FILE_COLUMN
220};
221
222/* File completion functions which would be external, were they used
223 * outside of this file.
224 */
225
226static CompletionState* cmpl_init_state (void);
227static void cmpl_free_state (CompletionState *cmpl_state);
228static gint cmpl_state_okay (CompletionState* cmpl_state);
229static const gchar* cmpl_strerror (gint);
230
231static 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 */
238static 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 */
243static gint cmpl_is_a_completion (PossibleCompletion*);
244
245/* True if the completion is a directory
246 */
247static gboolean cmpl_is_directory (PossibleCompletion*);
248
249/* Obtains the next completion, or NULL
250 */
251static 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 */
260static 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 */
265static 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 */
272static 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 */
277static 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 */
282static const gchar* cmpl_completion_fullname (const gchar*, CompletionState* cmpl_state);
283
284
285/* Directory operations. */
286static 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)
290static gboolean check_dir (gchar *dir_name,
291 struct stat *result,
292 gboolean *stat_subdirs);
293#endif
294static CompletionDir* open_dir (gchar* dir_name,
295 CompletionState* cmpl_state);
296#ifdef HAVE_PWD_H
297static CompletionDir* open_user_dir (const gchar* text_to_complete,
298 CompletionState *cmpl_state);
299#endif
300static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir,
301 CompletionState *cmpl_state);
302static CompletionDirSent* open_new_dir (gchar* dir_name,
303 struct stat* sbuf,
304 gboolean stat_subdirs);
305static gint correct_dir_fullname (CompletionDir* cmpl_dir);
306static gint correct_parent (CompletionDir* cmpl_dir,
307 struct stat *sbuf);
308#ifndef G_OS_WIN32
309static gchar* find_parent_dir_fullname (gchar* dirname);
310#endif
311static CompletionDir* attach_dir (CompletionDirSent* sent,
312 gchar* dir_name,
313 CompletionState *cmpl_state);
314static void free_dir_sent (CompletionDirSent* sent);
315static void free_dir (CompletionDir *dir);
316static void prune_memory_usage(CompletionState *cmpl_state);
317
318/* Completion operations */
319#ifdef HAVE_PWD_H
320static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
321 CompletionState *cmpl_state);
322#endif
323static PossibleCompletion* attempt_dir_completion(CompletionState *cmpl_state);
324static CompletionDir* find_completion_dir(gchar* text_to_complete,
325 gchar** remaining_text,
326 CompletionState* cmpl_state);
327static PossibleCompletion* append_completion_text(gchar* text,
328 CompletionState* cmpl_state);
329#ifdef HAVE_PWD_H
330static gint get_pwdb(CompletionState* cmpl_state);
331static gint compare_user_dir(const void* a, const void* b);
332#endif
333static gint first_diff_index(gchar* pat, gchar* text);
334static gint compare_cmpl_dir(const void* a, const void* b);
335static void update_cmpl(PossibleCompletion* poss,
336 CompletionState* cmpl_state);
337
338static void gtk_dir_selection_class_init (GtkDirSelectionClass *klass);
339static void gtk_dir_selection_set_property (GObject *object,
340 guint prop_id,
341 const GValue *value,
342 GParamSpec *pspec);
343static void gtk_dir_selection_get_property (GObject *object,
344 guint prop_id,
345 GValue *value,
346 GParamSpec *pspec);
347static void gtk_dir_selection_init (GtkDirSelection *filesel);
348static void gtk_dir_selection_finalize (GObject *object);
349static void gtk_dir_selection_destroy (GtkObject *object);
350static void gtk_dir_selection_map (GtkWidget *widget);
351static gint gtk_dir_selection_key_press (GtkWidget *widget,
352 GdkEventKey *event,
353 gpointer user_data);
354static 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);
359static void gtk_dir_selection_update_fileops (GtkDirSelection *filesel);
360
361static void gtk_dir_selection_file_activate (GtkTreeView *tree_view,
362 GtkTreePath *path,
363 GtkTreeViewColumn *column,
364 gpointer user_data);
365static void gtk_dir_selection_file_changed (GtkTreeSelection *selection,
366 gpointer user_data);
367static void gtk_dir_selection_dir_activate (GtkTreeView *tree_view,
368 GtkTreePath *path,
369 GtkTreeViewColumn *column,
370 gpointer user_data);
371static void gtk_dir_selection_dir_changed (GtkTreeSelection *selection,
372 gpointer user_data);
373static void gtk_dir_selection_populate (GtkDirSelection *fs,
374 gchar *rel_path,
375 gboolean try_complete,
376 gboolean reset_entry);
377static void gtk_dir_selection_abort (GtkDirSelection *fs);
378
379static void gtk_dir_selection_update_history_menu (GtkDirSelection *fs,
380 gchar *current_dir);
381
382static void gtk_dir_selection_create_dir (GtkWidget *widget, gpointer data);
383static void gtk_dir_selection_delete_file (GtkWidget *widget, gpointer data);
384static void gtk_dir_selection_rename_file (GtkWidget *widget, gpointer data);
385
386static 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
395static GtkWindowClass *parent_class = NULL;
396
397/* Saves errno when something cmpl does fails. */
398static 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 */
414static int
415translate_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
436GType
437gtk_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
464static void
465gtk_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
508static 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
538static 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
571static gboolean
572grab_default (GtkWidget *widget)
573{
574 gtk_widget_grab_default (widget);
575 return FALSE;
576}
577
578static void
579gtk_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
794static gchar *
795uri_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
837static void
838dnd_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
853static void
854filenames_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
928enum
929{
930 TARGET_URILIST,
931 TARGET_UTF8_STRING,
932 TARGET_STRING,
933 TARGET_TEXT,
934 TARGET_COMPOUND_TEXT
935};
936
937
938static void
939filenames_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
985static void
986file_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
1020GtkWidget*
1021gtk_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
1034void
1035gtk_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
1078void
1079gtk_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 **/
1117void
1118gtk_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 **/
1169G_CONST_RETURN gchar*
1170gtk_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
1196void
1197gtk_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
1208static void
1209gtk_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
1260static void
1261gtk_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
1271static void
1272gtk_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
1283static void
1284gtk_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
1310static void
1311gtk_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
1321static gboolean
1322entry_is_empty (GtkEntry *entry)
1323{
1324 const gchar *text = gtk_entry_get_text (entry);
1325
1326 return *text == '\0';
1327}
1328
1329static void
1330gtk_dir_selection_fileop_entry_changed (GtkEntry *entry,
1331 GtkWidget *button)
1332{
1333 gtk_widget_set_sensitive (button, !entry_is_empty (entry));
1334}
1335
1336static void
1337gtk_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
1384static void
1385gtk_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
1463static void
1464gtk_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
1519static void
1520gtk_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
1572static void
1573gtk_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
1647static void
1648gtk_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
1735static gint
1736gtk_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
1758static void
1759gtk_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
1775static gint
1776gtk_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
1805static void
1806gtk_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
1830static void
1831gtk_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
1911static gchar *
1912get_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
1941static void
1942gtk_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
1961static void
1962gtk_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
1981static void
1982win32_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
2011static gchar *
2012escape_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
2027static void
2028gtk_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 g_free (sel_text);
2163 }
2164
2165 if (fs->history_pulldown)
2166 {
2167 gtk_dir_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state));
2168 }
2169
2170 }
2171}
2172
2173static void
2174gtk_dir_selection_abort (GtkDirSelection *fs)
2175{
2176 gchar err_buf[256];
2177
2178 g_snprintf (err_buf, sizeof (err_buf), _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno));
2179
2180 /* BEEP gdk_beep(); */
2181
2182 if (fs->selection_entry)
2183 gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf);
2184}
2185
2186/**
2187 * gtk_dir_selection_set_select_multiple:
2188 * @filesel: a #GtkDirSelection
2189 * @select_multiple: whether or not the user is allowed to select multiple
2190 * files in the file list.
2191 *
2192 * Sets whether the user is allowed to select multiple files in the file list.
2193 * Use gtk_dir_selection_get_selections () to get the list of selected files.
2194 **/
2195void
2196gtk_dir_selection_set_select_multiple (GtkDirSelection *filesel,
2197 gboolean select_multiple)
2198{
2199 GtkTreeSelection *sel;
2200 GtkSelectionMode mode;
2201
2202 g_return_if_fail (GTK_IS_DIR_SELECTION (filesel));
2203
2204 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list));
2205
2206 mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE;
2207
2208 if (mode != gtk_tree_selection_get_mode (sel))
2209 {
2210 gtk_tree_selection_set_mode (sel, mode);
2211
2212 g_object_notify (G_OBJECT (filesel), "select-multiple");
2213 }
2214}
2215
2216/**
2217 * gtk_dir_selection_get_select_multiple:
2218 * @filesel: a #GtkDirSelection
2219 *
2220 * Determines whether or not the user is allowed to select multiple files in
2221 * the file list. See gtk_dir_selection_set_select_multiple().
2222 *
2223 * Return value: %TRUE if the user is allowed to select multiple files in the
2224 * file list
2225 **/
2226gboolean
2227gtk_dir_selection_get_select_multiple (GtkDirSelection *filesel)
2228{
2229 GtkTreeSelection *sel;
2230
2231 g_return_val_if_fail (GTK_IS_DIR_SELECTION (filesel), FALSE);
2232
2233 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list));
2234 return (gtk_tree_selection_get_mode (sel) == GTK_SELECTION_MULTIPLE);
2235}
2236
2237static void
2238multiple_changed_foreach (GtkTreeModel *model,
2239 GtkTreePath *path,
2240 GtkTreeIter *iter,
2241 gpointer data)
2242{
2243 GPtrArray *names = data;
2244 gchar *filename;
2245
2246 gtk_tree_model_get (model, iter, FILE_COLUMN, &filename, -1);
2247
2248 g_ptr_array_add (names, filename);
2249}
2250
2251static void
2252free_selected_names (GPtrArray *names)
2253{
2254 gint i;
2255
2256 for (i = 0; i < names->len; i++)
2257 g_free (g_ptr_array_index (names, i));
2258
2259 g_ptr_array_free (names, TRUE);
2260}
2261
2262static void
2263gtk_dir_selection_file_changed (GtkTreeSelection *selection,
2264 gpointer user_data)
2265{
2266 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
2267 GPtrArray *new_names;
2268 gchar *filename;
2269 const gchar *entry;
2270 gint index = -1;
2271
2272 new_names = g_ptr_array_sized_new (8);
2273
2274 gtk_tree_selection_selected_foreach (selection,
2275 multiple_changed_foreach,
2276 new_names);
2277
2278 /* nothing selected */
2279 if (new_names->len == 0)
2280 {
2281 g_ptr_array_free (new_names, TRUE);
2282
2283 if (fs->selected_names != NULL)
2284 {
2285 free_selected_names (fs->selected_names);
2286 fs->selected_names = NULL;
2287 }
2288
2289 goto maybe_clear_entry;
2290 }
2291
2292 if (new_names->len != 1)
2293 {
2294 GPtrArray *old_names = fs->selected_names;
2295
2296 if (old_names != NULL)
2297 {
2298 /* A common case is selecting a range of files from top to bottom,
2299 * so quickly check for that to avoid looping over the entire list
2300 */
2301 if (compare_filenames (g_ptr_array_index (old_names, old_names->len - 1),
2302 g_ptr_array_index (new_names, new_names->len - 1)) != 0)
2303 index = new_names->len - 1;
2304 else
2305 {
2306 gint i = 0, j = 0, cmp;
2307
2308 /* do a quick diff, stopping at the first file not in the
2309 * old list
2310 */
2311 while (i < old_names->len && j < new_names->len)
2312 {
2313 cmp = compare_filenames (g_ptr_array_index (old_names, i),
2314 g_ptr_array_index (new_names, j));
2315 if (cmp < 0)
2316 {
2317 i++;
2318 }
2319 else if (cmp == 0)
2320 {
2321 i++;
2322 j++;
2323 }
2324 else if (cmp > 0)
2325 {
2326 index = j;
2327 break;
2328 }
2329 }
2330
2331 /* we ran off the end of the old list */
2332 if (index == -1 && i < new_names->len)
2333 index = j;
2334 }
2335 }
2336 else
2337 {
2338 /* A phantom anchor still exists at the point where the last item
2339 * was selected, which is used for subsequent range selections.
2340 * So search up from there.
2341 */
2342 if (fs->last_selected &&
2343 compare_filenames (fs->last_selected,
2344 g_ptr_array_index (new_names, 0)) == 0)
2345 index = new_names->len - 1;
2346 else
2347 index = 0;
2348 }
2349 }
2350 else
2351 index = 0;
2352
2353 if (fs->selected_names != NULL)
2354 free_selected_names (fs->selected_names);
2355
2356 fs->selected_names = new_names;
2357
2358 if (index != -1)
2359 {
2360 if (fs->last_selected != NULL)
2361 g_free (fs->last_selected);
2362
2363 fs->last_selected = g_strdup (g_ptr_array_index (new_names, index));
2364 filename = get_real_filename (fs->last_selected, FALSE);
2365
2366 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename);
2367
2368 if (filename != fs->last_selected)
2369 g_free (filename);
2370
2371 return;
2372 }
2373
2374maybe_clear_entry:
2375
2376 entry = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
2377 if ((entry != NULL) && (fs->last_selected != NULL) &&
2378 (compare_filenames (entry, fs->last_selected) == 0))
2379 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
2380}
2381
2382static void
2383gtk_dir_selection_dir_changed (GtkTreeSelection *selection,
2384 gpointer user_data)
2385{
2386 GtkDirSelection *fs = GTK_DIR_SELECTION (user_data);
2387 GPtrArray *new_names;
2388 gchar *filename;
2389 const gchar *entry;
2390 gint index = -1;
2391
2392 new_names = g_ptr_array_sized_new (8);
2393
2394 gtk_tree_selection_selected_foreach (selection,
2395 multiple_changed_foreach,
2396 new_names);
2397
2398 /* nothing selected */
2399 if (new_names->len == 0)
2400 {
2401 g_ptr_array_free (new_names, TRUE);
2402
2403 if (fs->selected_names != NULL)
2404 {
2405 free_selected_names (fs->selected_names);
2406 fs->selected_names = NULL;
2407 }
2408
2409 goto maybe_clear_entry;
2410 }
2411
2412 if (new_names->len != 1)
2413 {
2414 GPtrArray *old_names = fs->selected_names;
2415
2416 if (old_names != NULL)
2417 {
2418 /* A common case is selecting a range of files from top to bottom,
2419 * so quickly check for that to avoid looping over the entire list
2420 */
2421 if (compare_filenames (g_ptr_array_index (old_names, old_names->len - 1),
2422 g_ptr_array_index (new_names, new_names->len - 1)) != 0)
2423 index = new_names->len - 1;
2424 else
2425 {
2426 gint i = 0, j = 0, cmp;
2427
2428 /* do a quick diff, stopping at the first file not in the
2429 * old list
2430 */
2431 while (i < old_names->len && j < new_names->len)
2432 {
2433 cmp = compare_filenames (g_ptr_array_index (old_names, i),
2434 g_ptr_array_index (new_names, j));
2435 if (cmp < 0)
2436 {
2437 i++;
2438 }
2439 else if (cmp == 0)
2440 {
2441 i++;
2442 j++;
2443 }
2444 else if (cmp > 0)
2445 {
2446 index = j;
2447 break;
2448 }
2449 }
2450
2451 /* we ran off the end of the old list */
2452 if (index == -1 && i < new_names->len)
2453 index = j;
2454 }
2455 }
2456 else
2457 {
2458 /* A phantom anchor still exists at the point where the last item
2459 * was selected, which is used for subsequent range selections.
2460 * So search up from there.
2461 */
2462 if (fs->last_selected &&
2463 compare_filenames (fs->last_selected,
2464 g_ptr_array_index (new_names, 0)) == 0)
2465 index = new_names->len - 1;
2466 else
2467 index = 0;
2468 }
2469 }
2470 else
2471 index = 0;
2472
2473 if (fs->selected_names != NULL)
2474 free_selected_names (fs->selected_names);
2475
2476 fs->selected_names = new_names;
2477
2478 if (index != -1)
2479 {
94dcfb9e 2480 const gchar * err;
2481 gchar str[256];
fc188b78 2482 err = gtk_label_get_text (GTK_LABEL (fs->selection_text));
2483 err += 11; //pass over "Selection: "
2484 sprintf(str,"%s\0",err);
2485
2486
2487 if (fs->last_selected != NULL)
2488 g_free (fs->last_selected);
2489
2490 fs->last_selected = g_strdup (g_ptr_array_index (new_names, index));
2491 filename = get_real_filename (fs->last_selected, FALSE);
2492
2493 strcat(str,"/");
2494 strcat(str,filename);
2495 str[strlen(str)-1] = '\0';
2496
2497 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), str);
2498
2499 if (filename != fs->last_selected)
2500 g_free (filename);
2501
2502 return;
2503 }
2504
2505maybe_clear_entry:
2506
2507 entry = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry));
2508 if ((entry != NULL) && (fs->last_selected != NULL) &&
2509 (compare_filenames (entry, fs->last_selected) == 0))
2510 gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), "");
2511}
2512
2513/**
2514 * gtk_dir_selection_get_selections:
2515 * @filesel: a #GtkDirSelection
2516 *
2517 * Retrieves the list of file selections the user has made in the dialog box.
2518 * This function is intended for use when the user can select multiple files
2519 * in the file list. The first file in the list is equivalent to what
2520 * gtk_dir_selection_get_filename() would return.
2521 *
2522 * The filenames are in the encoding of g_filename_from_utf8(), which may or
2523 * may not be the same as that used by GTK+ (UTF-8). To convert to UTF-8, call
2524 * g_filename_to_utf8() on each string.
2525 *
2526 * Return value: a newly-allocated %NULL-terminated array of strings. Use
2527 * g_strfreev() to free it.
2528 **/
2529gchar **
2530gtk_dir_selection_get_selections (GtkDirSelection *filesel)
2531{
2532 GPtrArray *names;
2533 gchar **selections;
2534 gchar *filename, *dirname;
2535 gchar *current, *buf;
2536 gint i, count;
2537 gboolean unselected_entry;
2538
2539 g_return_val_if_fail (GTK_IS_DIR_SELECTION (filesel), NULL);
2540
2541 filename = g_strdup (gtk_dir_selection_get_filename (filesel));
2542
2543 if (strlen (filename) == 0)
2544 {
2545 g_free (filename);
2546 return NULL;
2547 }
2548
2549 names = filesel->selected_names;
2550
2551 if (names != NULL)
2552 selections = g_new (gchar *, names->len + 2);
2553 else
2554 selections = g_new (gchar *, 2);
2555
2556 count = 0;
2557 unselected_entry = TRUE;
2558
2559 if (names != NULL)
2560 {
2561 dirname = g_path_get_dirname (filename);
2562
2563 for (i = 0; i < names->len; i++)
2564 {
2565 buf = g_filename_from_utf8 (g_ptr_array_index (names, i), -1,
2566 NULL, NULL, NULL);
2567 current = g_build_filename (dirname, buf, NULL);
2568 g_free (buf);
2569
2570 selections[count++] = current;
2571
2572 if (unselected_entry && compare_filenames (current, filename) == 0)
2573 unselected_entry = FALSE;
2574 }
2575
2576 g_free (dirname);
2577 }
2578
2579 if (unselected_entry)
2580 selections[count++] = filename;
2581 else
2582 g_free (filename);
2583
2584 selections[count] = NULL;
2585
2586 return selections;
2587}
2588
2589/**********************************************************************/
2590/* External Interface */
2591/**********************************************************************/
2592
2593/* The four completion state selectors
2594 */
2595static gchar*
2596cmpl_updated_text (CompletionState *cmpl_state)
2597{
2598 return cmpl_state->updated_text;
2599}
2600
2601static gboolean
2602cmpl_updated_dir (CompletionState *cmpl_state)
2603{
2604 return cmpl_state->re_complete;
2605}
2606
2607static gchar*
2608cmpl_reference_position (CompletionState *cmpl_state)
2609{
2610 return cmpl_state->reference_dir->fullname;
2611}
2612
2613static gint
2614cmpl_last_valid_char (CompletionState *cmpl_state)
2615{
2616 return cmpl_state->last_valid_char;
2617}
2618
2619static const gchar*
2620cmpl_completion_fullname (const gchar *text,
2621 CompletionState *cmpl_state)
2622{
2623 static const char nothing[2] = "";
2624
2625 if (!cmpl_state_okay (cmpl_state))
2626 {
2627 return nothing;
2628 }
2629 else if (g_path_is_absolute (text))
2630 {
2631 strcpy (cmpl_state->updated_text, text);
2632 }
2633#ifdef HAVE_PWD_H
2634 else if (text[0] == '~')
2635 {
2636 CompletionDir* dir;
2637 char* slash;
2638
2639 dir = open_user_dir (text, cmpl_state);
2640
2641 if (!dir)
2642 {
2643 /* spencer says just return ~something, so
2644 * for now just do it. */
2645 strcpy (cmpl_state->updated_text, text);
2646 }
2647 else
2648 {
2649
2650 strcpy (cmpl_state->updated_text, dir->fullname);
2651
2652 slash = strchr (text, G_DIR_SEPARATOR);
2653
2654 if (slash)
2655 strcat (cmpl_state->updated_text, slash);
2656 }
2657 }
2658#endif
2659 else
2660 {
2661 strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname);
2662 if (cmpl_state->updated_text[strlen (cmpl_state->updated_text) - 1] != G_DIR_SEPARATOR)
2663 strcat (cmpl_state->updated_text, G_DIR_SEPARATOR_S);
2664 strcat (cmpl_state->updated_text, text);
2665 }
2666
2667 return cmpl_state->updated_text;
2668}
2669
2670/* The three completion selectors
2671 */
2672static gchar*
2673cmpl_this_completion (PossibleCompletion* pc)
2674{
2675 return pc->text;
2676}
2677
2678static gboolean
2679cmpl_is_directory (PossibleCompletion* pc)
2680{
2681 return pc->is_directory;
2682}
2683
2684static gint
2685cmpl_is_a_completion (PossibleCompletion* pc)
2686{
2687 return pc->is_a_completion;
2688}
2689
2690/**********************************************************************/
2691/* Construction, deletion */
2692/**********************************************************************/
2693
2694static CompletionState*
2695cmpl_init_state (void)
2696{
2697 gchar *sys_getcwd_buf;
2698 gchar *utf8_cwd;
2699 CompletionState *new_state;
2700
2701 new_state = g_new (CompletionState, 1);
2702
2703 /* g_get_current_dir() returns a string in the "system" charset */
2704 sys_getcwd_buf = g_get_current_dir ();
2705 utf8_cwd = g_filename_to_utf8 (sys_getcwd_buf, -1, NULL, NULL, NULL);
2706 g_free (sys_getcwd_buf);
2707
2708tryagain:
2709
2710 new_state->reference_dir = NULL;
2711 new_state->completion_dir = NULL;
2712 new_state->active_completion_dir = NULL;
2713 new_state->directory_storage = NULL;
2714 new_state->directory_sent_storage = NULL;
2715 new_state->last_valid_char = 0;
2716 new_state->updated_text = g_new (gchar, MAXPATHLEN);
2717 new_state->updated_text_alloc = MAXPATHLEN;
2718 new_state->the_completion.text = g_new (gchar, MAXPATHLEN);
2719 new_state->the_completion.text_alloc = MAXPATHLEN;
2720 new_state->user_dir_name_buffer = NULL;
2721 new_state->user_directories = NULL;
2722
2723 new_state->reference_dir = open_dir (utf8_cwd, new_state);
2724
2725 if (!new_state->reference_dir)
2726 {
2727 /* Directories changing from underneath us, grumble */
2728 strcpy (utf8_cwd, G_DIR_SEPARATOR_S);
2729 goto tryagain;
2730 }
2731
2732 g_free (utf8_cwd);
2733 return new_state;
2734}
2735
2736static void
2737cmpl_free_dir_list (GList* dp0)
2738{
2739 GList *dp = dp0;
2740
2741 while (dp)
2742 {
2743 free_dir (dp->data);
2744 dp = dp->next;
2745 }
2746
2747 g_list_free (dp0);
2748}
2749
2750static void
2751cmpl_free_dir_sent_list (GList* dp0)
2752{
2753 GList *dp = dp0;
2754
2755 while (dp)
2756 {
2757 free_dir_sent (dp->data);
2758 dp = dp->next;
2759 }
2760
2761 g_list_free (dp0);
2762}
2763
2764static void
2765cmpl_free_state (CompletionState* cmpl_state)
2766{
2767 g_return_if_fail (cmpl_state != NULL);
2768
2769 cmpl_free_dir_list (cmpl_state->directory_storage);
2770 cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage);
2771
2772 if (cmpl_state->user_dir_name_buffer)
2773 g_free (cmpl_state->user_dir_name_buffer);
2774 if (cmpl_state->user_directories)
2775 g_free (cmpl_state->user_directories);
2776 if (cmpl_state->the_completion.text)
2777 g_free (cmpl_state->the_completion.text);
2778 if (cmpl_state->updated_text)
2779 g_free (cmpl_state->updated_text);
2780
2781 g_free (cmpl_state);
2782}
2783
2784static void
2785free_dir (CompletionDir* dir)
2786{
2787 g_free (dir->cmpl_text);
2788 g_free (dir->fullname);
2789 g_free (dir);
2790}
2791
2792static void
2793free_dir_sent (CompletionDirSent* sent)
2794{
2795 gint i;
2796 for (i = 0; i < sent->entry_count; i++)
2797 {
2798 g_free (sent->entries[i].entry_name);
2799 g_free (sent->entries[i].sort_key);
2800 }
2801 g_free (sent->entries);
2802 g_free (sent);
2803}
2804
2805static void
2806prune_memory_usage (CompletionState *cmpl_state)
2807{
2808 GList* cdsl = cmpl_state->directory_sent_storage;
2809 GList* cdl = cmpl_state->directory_storage;
2810 GList* cdl0 = cdl;
2811 gint len = 0;
2812
2813 for (; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1)
2814 cdsl = cdsl->next;
2815
2816 if (cdsl)
2817 {
2818 cmpl_free_dir_sent_list (cdsl->next);
2819 cdsl->next = NULL;
2820 }
2821
2822 cmpl_state->directory_storage = NULL;
2823 while (cdl)
2824 {
2825 if (cdl->data == cmpl_state->reference_dir)
2826 cmpl_state->directory_storage = g_list_prepend (NULL, cdl->data);
2827 else
2828 free_dir (cdl->data);
2829 cdl = cdl->next;
2830 }
2831
2832 g_list_free (cdl0);
2833}
2834
2835/**********************************************************************/
2836/* The main entrances. */
2837/**********************************************************************/
2838
2839static PossibleCompletion*
2840cmpl_completion_matches (gchar *text_to_complete,
2841 gchar **remaining_text,
2842 CompletionState *cmpl_state)
2843{
2844 gchar* first_slash;
2845 PossibleCompletion *poss;
2846
2847 prune_memory_usage (cmpl_state);
2848
2849 g_assert (text_to_complete != NULL);
2850
2851 cmpl_state->user_completion_index = -1;
2852 cmpl_state->last_completion_text = text_to_complete;
2853 cmpl_state->the_completion.text[0] = 0;
2854 cmpl_state->last_valid_char = 0;
2855 cmpl_state->updated_text_len = -1;
2856 cmpl_state->updated_text[0] = 0;
2857 cmpl_state->re_complete = FALSE;
2858
2859#ifdef HAVE_PWD_H
2860 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2861
2862 if (text_to_complete[0] == '~' && !first_slash)
2863 {
2864 /* Text starts with ~ and there is no slash, show all the
2865 * home directory completions.
2866 */
2867 poss = attempt_homedir_completion (text_to_complete, cmpl_state);
2868
2869 update_cmpl (poss, cmpl_state);
2870
2871 return poss;
2872 }
2873#endif
2874 cmpl_state->reference_dir =
2875 open_ref_dir (text_to_complete, remaining_text, cmpl_state);
2876
2877 if (!cmpl_state->reference_dir)
2878 return NULL;
2879
2880 cmpl_state->completion_dir =
2881 find_completion_dir (*remaining_text, remaining_text, cmpl_state);
2882
2883 cmpl_state->last_valid_char = *remaining_text - text_to_complete;
2884
2885 if (!cmpl_state->completion_dir)
2886 return NULL;
2887
2888 cmpl_state->completion_dir->cmpl_index = -1;
2889 cmpl_state->completion_dir->cmpl_parent = NULL;
2890 cmpl_state->completion_dir->cmpl_text = g_strdup (*remaining_text);
2891
2892 cmpl_state->active_completion_dir = cmpl_state->completion_dir;
2893
2894 cmpl_state->reference_dir = cmpl_state->completion_dir;
2895
2896 poss = attempt_dir_completion (cmpl_state);
2897
2898 update_cmpl (poss, cmpl_state);
2899
2900 return poss;
2901}
2902
2903static PossibleCompletion*
2904cmpl_next_completion (CompletionState* cmpl_state)
2905{
2906 PossibleCompletion* poss = NULL;
2907
2908 cmpl_state->the_completion.text[0] = 0;
2909
2910#ifdef HAVE_PWD_H
2911 if (cmpl_state->user_completion_index >= 0)
2912 poss = attempt_homedir_completion (cmpl_state->last_completion_text, cmpl_state);
2913 else
2914 poss = attempt_dir_completion (cmpl_state);
2915#else
2916 poss = attempt_dir_completion (cmpl_state);
2917#endif
2918
2919 update_cmpl (poss, cmpl_state);
2920
2921 return poss;
2922}
2923
2924/**********************************************************************/
2925/* Directory Operations */
2926/**********************************************************************/
2927
2928/* Open the directory where completion will begin from, if possible. */
2929static CompletionDir*
2930open_ref_dir (gchar *text_to_complete,
2931 gchar **remaining_text,
2932 CompletionState *cmpl_state)
2933{
2934 gchar* first_slash;
2935 CompletionDir *new_dir;
2936
2937 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
2938
2939#ifdef G_WITH_CYGWIN
2940 if (text_to_complete[0] == '/' && text_to_complete[1] == '/')
2941 {
2942 char root_dir[5];
2943 g_snprintf (root_dir, sizeof (root_dir), "//%c", text_to_complete[2]);
2944
2945 new_dir = open_dir (root_dir, cmpl_state);
2946
2947 if (new_dir) {
2948 *remaining_text = text_to_complete + 4;
2949 }
2950 }
2951#else
2952 if (FALSE)
2953 ;
2954#endif
2955#ifdef HAVE_PWD_H
2956 else if (text_to_complete[0] == '~')
2957 {
2958 new_dir = open_user_dir (text_to_complete, cmpl_state);
2959
2960 if (new_dir)
2961 {
2962 if (first_slash)
2963 *remaining_text = first_slash + 1;
2964 else
2965 *remaining_text = text_to_complete + strlen (text_to_complete);
2966 }
2967 else
2968 {
2969 return NULL;
2970 }
2971 }
2972#endif
2973 else if (g_path_is_absolute (text_to_complete) || !cmpl_state->reference_dir)
2974 {
2975 gchar *tmp = g_strdup (text_to_complete);
2976 gchar *p;
2977
2978 p = tmp;
2979 while (*p && *p != '*' && *p != '?')
2980 p++;
2981
2982 *p = '\0';
2983 p = strrchr (tmp, G_DIR_SEPARATOR);
2984 if (p)
2985 {
2986 if (p == tmp)
2987 p++;
2988
2989 *p = '\0';
2990
2991 new_dir = open_dir (tmp, cmpl_state);
2992
2993 if (new_dir)
2994 *remaining_text = text_to_complete +
2995 ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp));
2996 }
2997 else
2998 {
2999 /* If no possible candidates, use the cwd */
3000 gchar *sys_curdir = g_get_current_dir ();
3001 gchar *utf8_curdir = g_filename_to_utf8 (sys_curdir, -1, NULL, NULL, NULL);
3002
3003 g_free (sys_curdir);
3004
3005 new_dir = open_dir (utf8_curdir, cmpl_state);
3006
3007 if (new_dir)
3008 *remaining_text = text_to_complete;
3009
3010 g_free (utf8_curdir);
3011 }
3012
3013 g_free (tmp);
3014 }
3015 else
3016 {
3017 *remaining_text = text_to_complete;
3018
3019 new_dir = open_dir (cmpl_state->reference_dir->fullname, cmpl_state);
3020 }
3021
3022 if (new_dir)
3023 {
3024 new_dir->cmpl_index = -1;
3025 new_dir->cmpl_parent = NULL;
3026 }
3027
3028 return new_dir;
3029}
3030
3031#ifdef HAVE_PWD_H
3032
3033/* open a directory by user name */
3034static CompletionDir*
3035open_user_dir (const gchar *text_to_complete,
3036 CompletionState *cmpl_state)
3037{
3038 CompletionDir *result;
3039 gchar *first_slash;
3040 gint cmp_len;
3041
3042 g_assert (text_to_complete && text_to_complete[0] == '~');
3043
3044 first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
3045
3046 if (first_slash)
3047 cmp_len = first_slash - text_to_complete - 1;
3048 else
3049 cmp_len = strlen (text_to_complete + 1);
3050
3051 if (!cmp_len)
3052 {
3053 /* ~/ */
3054 const gchar *homedir = g_get_home_dir ();
3055 gchar *utf8_homedir = g_filename_to_utf8 (homedir, -1, NULL, NULL, NULL);
3056
3057 if (utf8_homedir)
3058 result = open_dir (utf8_homedir, cmpl_state);
3059 else
3060 result = NULL;
3061
3062 g_free (utf8_homedir);
3063 }
3064 else
3065 {
3066 /* ~user/ */
3067 gchar* copy = g_new (char, cmp_len + 1);
3068 gchar *utf8_dir;
3069 struct passwd *pwd;
3070
3071 strncpy (copy, text_to_complete + 1, cmp_len);
3072 copy[cmp_len] = 0;
3073 pwd = getpwnam (copy);
3074 g_free (copy);
3075 if (!pwd)
3076 {
3077 cmpl_errno = errno;
3078 return NULL;
3079 }
3080 utf8_dir = g_filename_to_utf8 (pwd->pw_dir, -1, NULL, NULL, NULL);
3081 result = open_dir (utf8_dir, cmpl_state);
3082 g_free (utf8_dir);
3083 }
3084 return result;
3085}
3086
3087#endif
3088
3089/* open a directory relative the the current relative directory */
3090static CompletionDir*
3091open_relative_dir (gchar *dir_name,
3092 CompletionDir *dir,
3093 CompletionState *cmpl_state)
3094{
3095 CompletionDir *result;
3096 GString *path;
3097
3098 path = g_string_sized_new (dir->fullname_len + strlen (dir_name) + 10);
3099 g_string_assign (path, dir->fullname);
3100
3101 if (dir->fullname_len > 1
3102 && path->str[dir->fullname_len - 1] != G_DIR_SEPARATOR)
3103 g_string_append_c (path, G_DIR_SEPARATOR);
3104 g_string_append (path, dir_name);
3105
3106 result = open_dir (path->str, cmpl_state);
3107
3108 g_string_free (path, TRUE);
3109
3110 return result;
3111}
3112
3113/* after the cache lookup fails, really open a new directory */
3114static CompletionDirSent*
3115open_new_dir (gchar *dir_name,
3116 struct stat *sbuf,
3117 gboolean stat_subdirs)
3118{
3119 CompletionDirSent *sent;
3120 GDir *directory;
3121 const char *dirent;
3122 GError *error = NULL;
3123 gint entry_count = 0;
3124 gint n_entries = 0;
3125 gint i;
3126 struct stat ent_sbuf;
3127 GString *path;
3128 gchar *sys_dir_name;
3129
3130 sent = g_new (CompletionDirSent, 1);
3131 sent->mtime = sbuf->st_mtime;
3132 sent->inode = sbuf->st_ino;
3133 sent->device = sbuf->st_dev;
3134
3135 path = g_string_sized_new (2*MAXPATHLEN + 10);
3136
3137 sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
3138 if (!sys_dir_name)
3139 {
3140 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3141 return NULL;
3142 }
3143
3144 directory = g_dir_open (sys_dir_name, 0, &error);
3145 if (!directory)
3146 {
3147 cmpl_errno = error->code; /* ??? */
3148 g_free (sys_dir_name);
3149 return NULL;
3150 }
3151
3152 while ((dirent = g_dir_read_name (directory)) != NULL)
3153 entry_count++;
3154
3155 entry_count += 2; /* For ".",".." */
3156
3157 sent->entries = g_new (CompletionDirEntry, entry_count);
3158 sent->entry_count = entry_count;
3159
3160 g_dir_rewind (directory);
3161
3162 for (i = 0; i < entry_count; i += 1)
3163 {
3164 GError *error = NULL;
3165
3166 if (i == 0)
3167 dirent = ".";
3168 else if (i == 1)
3169 dirent = "..";
3170 else
3171 {
3172 dirent = g_dir_read_name (directory);
3173 if (!dirent) /* Directory changed */
3174 break;
3175 }
3176
3177 sent->entries[n_entries].entry_name = g_filename_to_utf8 (dirent, -1, NULL, NULL, &error);
3178 if (sent->entries[n_entries].entry_name == NULL
3179 || !g_utf8_validate (sent->entries[n_entries].entry_name, -1, NULL))
3180 {
3181 gchar *escaped_str = g_strescape (dirent, NULL);
3182 g_message (_("The filename \"%s\" couldn't be converted to UTF-8 "
3183 "(try setting the environment variable G_BROKEN_FILENAMES): %s"),
3184 escaped_str,
3185 error->message ? error->message : _("Invalid Utf-8"));
3186 g_free (escaped_str);
3187 g_clear_error (&error);
3188 continue;
3189 }
3190 g_clear_error (&error);
3191
3192 sent->entries[n_entries].sort_key = g_utf8_collate_key (sent->entries[n_entries].entry_name, -1);
3193
3194 g_string_assign (path, sys_dir_name);
3195 if (path->str[path->len-1] != G_DIR_SEPARATOR)
3196 {
3197 g_string_append_c (path, G_DIR_SEPARATOR);
3198 }
3199 g_string_append (path, dirent);
3200
3201 if (stat_subdirs)
3202 {
3203 /* Here we know path->str is a "system charset" string */
3204 if (stat (path->str, &ent_sbuf) >= 0 && S_ISDIR (ent_sbuf.st_mode))
3205 sent->entries[n_entries].is_dir = TRUE;
3206 else
3207 /* stat may fail, and we don't mind, since it could be a
3208 * dangling symlink. */
3209 sent->entries[n_entries].is_dir = FALSE;
3210 }
3211 else
3212 sent->entries[n_entries].is_dir = 1;
3213
3214 n_entries++;
3215 }
3216 sent->entry_count = n_entries;
3217
3218 g_free (sys_dir_name);
3219 g_string_free (path, TRUE);
3220 qsort (sent->entries, sent->entry_count, sizeof (CompletionDirEntry), compare_cmpl_dir);
3221
3222 g_dir_close (directory);
3223
3224 return sent;
3225}
3226
3227#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3228
3229static gboolean
3230check_dir (gchar *dir_name,
3231 struct stat *result,
3232 gboolean *stat_subdirs)
3233{
3234 /* A list of directories that we know only contain other directories.
3235 * Trying to stat every file in these directories would be very
3236 * expensive.
3237 */
3238
3239 static struct {
3240 gchar *name;
3241 gboolean present;
3242 struct stat statbuf;
3243 } no_stat_dirs[] = {
3244 { "/afs", FALSE, { 0 } },
3245 { "/net", FALSE, { 0 } }
3246 };
3247
3248 static const gint n_no_stat_dirs = G_N_ELEMENTS (no_stat_dirs);
3249 static gboolean initialized = FALSE;
3250 gchar *sys_dir_name;
3251 gint i;
3252
3253 if (!initialized)
3254 {
3255 initialized = TRUE;
3256 for (i = 0; i < n_no_stat_dirs; i++)
3257 {
3258 if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0)
3259 no_stat_dirs[i].present = TRUE;
3260 }
3261 }
3262
3263 sys_dir_name = g_filename_from_utf8 (dir_name, -1, NULL, NULL, NULL);
3264 if (!sys_dir_name)
3265 {
3266 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3267 return FALSE;
3268 }
3269
3270 if (stat (sys_dir_name, result) < 0)
3271 {
3272 g_free (sys_dir_name);
3273 cmpl_errno = errno;
3274 return FALSE;
3275 }
3276 g_free (sys_dir_name);
3277
3278 *stat_subdirs = TRUE;
3279 for (i = 0; i < n_no_stat_dirs; i++)
3280 {
3281 if (no_stat_dirs[i].present &&
3282 (no_stat_dirs[i].statbuf.st_dev == result->st_dev) &&
3283 (no_stat_dirs[i].statbuf.st_ino == result->st_ino))
3284 {
3285 *stat_subdirs = FALSE;
3286 break;
3287 }
3288 }
3289
3290 return TRUE;
3291}
3292
3293#endif
3294
3295/* open a directory by absolute pathname */
3296static CompletionDir*
3297open_dir (gchar *dir_name,
3298 CompletionState *cmpl_state)
3299{
3300 struct stat sbuf;
3301 gboolean stat_subdirs;
3302 CompletionDirSent *sent;
3303 GList* cdsl;
3304
3305#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
3306 if (!check_dir (dir_name, &sbuf, &stat_subdirs))
3307 return NULL;
3308
3309 cdsl = cmpl_state->directory_sent_storage;
3310
3311 while (cdsl)
3312 {
3313 sent = cdsl->data;
3314
3315 if (sent->inode == sbuf.st_ino &&
3316 sent->mtime == sbuf.st_mtime &&
3317 sent->device == sbuf.st_dev)
3318 return attach_dir (sent, dir_name, cmpl_state);
3319
3320 cdsl = cdsl->next;
3321 }
3322#else
3323 stat_subdirs = TRUE;
3324#endif
3325
3326 sent = open_new_dir (dir_name, &sbuf, stat_subdirs);
3327
3328 if (sent)
3329 {
3330 cmpl_state->directory_sent_storage =
3331 g_list_prepend (cmpl_state->directory_sent_storage, sent);
3332
3333 return attach_dir (sent, dir_name, cmpl_state);
3334 }
3335
3336 return NULL;
3337}
3338
3339static CompletionDir*
3340attach_dir (CompletionDirSent *sent,
3341 gchar *dir_name,
3342 CompletionState *cmpl_state)
3343{
3344 CompletionDir* new_dir;
3345
3346 new_dir = g_new (CompletionDir, 1);
3347
3348 cmpl_state->directory_storage =
3349 g_list_prepend (cmpl_state->directory_storage, new_dir);
3350
3351 new_dir->sent = sent;
3352 new_dir->fullname = g_strdup (dir_name);
3353 new_dir->fullname_len = strlen (dir_name);
3354 new_dir->cmpl_text = NULL;
3355
3356 return new_dir;
3357}
3358
3359static gint
3360correct_dir_fullname (CompletionDir* cmpl_dir)
3361{
3362 gint length = strlen (cmpl_dir->fullname);
3363 gchar *first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3364 gchar *sys_filename;
3365 struct stat sbuf;
3366
3367 /* Does it end with /. (\.) ? */
3368 if (length >= 2 &&
3369 strcmp (cmpl_dir->fullname + length - 2, G_DIR_SEPARATOR_S ".") == 0)
3370 {
3371 /* Is it just the root directory (on a drive) ? */
3372 if (cmpl_dir->fullname + length - 2 == first_slash)
3373 {
3374 cmpl_dir->fullname[length - 1] = 0;
3375 cmpl_dir->fullname_len = length - 1;
3376 return TRUE;
3377 }
3378 else
3379 {
3380 cmpl_dir->fullname[length - 2] = 0;
3381 }
3382 }
3383
3384 /* Ends with /./ (\.\)? */
3385 else if (length >= 3 &&
3386 strcmp (cmpl_dir->fullname + length - 3,
3387 G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) == 0)
3388 cmpl_dir->fullname[length - 2] = 0;
3389
3390 /* Ends with /.. (\..) ? */
3391 else if (length >= 3 &&
3392 strcmp (cmpl_dir->fullname + length - 3,
3393 G_DIR_SEPARATOR_S "..") == 0)
3394 {
3395 /* Is it just /.. (X:\..)? */
3396 if (cmpl_dir->fullname + length - 3 == first_slash)
3397 {
3398 cmpl_dir->fullname[length - 2] = 0;
3399 cmpl_dir->fullname_len = length - 2;
3400 return TRUE;
3401 }
3402
3403 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3404 if (!sys_filename)
3405 {
3406 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3407 return FALSE;
3408 }
3409
3410 if (stat (sys_filename, &sbuf) < 0)
3411 {
3412 g_free (sys_filename);
3413 cmpl_errno = errno;
3414 return FALSE;
3415 }
3416 g_free (sys_filename);
3417
3418 cmpl_dir->fullname[length - 3] = 0;
3419
3420 if (!correct_parent (cmpl_dir, &sbuf))
3421 return FALSE;
3422 }
3423
3424 /* Ends with /../ (\..\)? */
3425 else if (length >= 4 &&
3426 strcmp (cmpl_dir->fullname + length - 4,
3427 G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) == 0)
3428 {
3429 /* Is it just /../ (X:\..\)? */
3430 if (cmpl_dir->fullname + length - 4 == first_slash)
3431 {
3432 cmpl_dir->fullname[length - 3] = 0;
3433 cmpl_dir->fullname_len = length - 3;
3434 return TRUE;
3435 }
3436
3437 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3438 if (!sys_filename)
3439 {
3440 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3441 return FALSE;
3442 }
3443
3444 if (stat (sys_filename, &sbuf) < 0)
3445 {
3446 g_free (sys_filename);
3447 cmpl_errno = errno;
3448 return FALSE;
3449 }
3450 g_free (sys_filename);
3451
3452 cmpl_dir->fullname[length - 4] = 0;
3453
3454 if (!correct_parent (cmpl_dir, &sbuf))
3455 return FALSE;
3456 }
3457
3458 cmpl_dir->fullname_len = strlen (cmpl_dir->fullname);
3459
3460 return TRUE;
3461}
3462
3463static gint
3464correct_parent (CompletionDir *cmpl_dir,
3465 struct stat *sbuf)
3466{
3467 struct stat parbuf;
3468 gchar *last_slash;
3469 gchar *first_slash;
3470 gchar *new_name;
3471 gchar *sys_filename;
3472 gchar c = 0;
3473
3474 last_slash = strrchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3475 g_assert (last_slash);
3476 first_slash = strchr (cmpl_dir->fullname, G_DIR_SEPARATOR);
3477
3478 /* Clever (?) way to check for top-level directory that works also on
3479 * Win32, where there is a drive letter and colon prefixed...
3480 */
3481 if (last_slash != first_slash)
3482 {
3483 last_slash[0] = 0;
3484 }
3485 else
3486 {
3487 c = last_slash[1];
3488 last_slash[1] = 0;
3489 }
3490
3491 sys_filename = g_filename_from_utf8 (cmpl_dir->fullname, -1, NULL, NULL, NULL);
3492 if (!sys_filename)
3493 {
3494 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3495 if (!c)
3496 last_slash[0] = G_DIR_SEPARATOR;
3497 return FALSE;
3498 }
3499
3500 if (stat (sys_filename, &parbuf) < 0)
3501 {
3502 g_free (sys_filename);
3503 cmpl_errno = errno;
3504 if (!c)
3505 last_slash[0] = G_DIR_SEPARATOR;
3506 return FALSE;
3507 }
3508 g_free (sys_filename);
3509
3510#ifndef G_OS_WIN32 /* No inode numbers on Win32 */
3511 if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev)
3512 /* it wasn't a link */
3513 return TRUE;
3514
3515 if (c)
3516 last_slash[1] = c;
3517 else
3518 last_slash[0] = G_DIR_SEPARATOR;
3519
3520 /* it was a link, have to figure it out the hard way */
3521
3522 new_name = find_parent_dir_fullname (cmpl_dir->fullname);
3523
3524 if (!new_name)
3525 return FALSE;
3526
3527 g_free (cmpl_dir->fullname);
3528
3529 cmpl_dir->fullname = new_name;
3530#endif
3531
3532 return TRUE;
3533}
3534
3535#ifndef G_OS_WIN32
3536
3537static gchar*
3538find_parent_dir_fullname (gchar* dirname)
3539{
3540 gchar *sys_orig_dir;
3541 gchar *result;
3542 gchar *sys_cwd;
3543 gchar *sys_dirname;
3544
3545 sys_orig_dir = g_get_current_dir ();
3546 sys_dirname = g_filename_from_utf8 (dirname, -1, NULL, NULL, NULL);
3547 if (!sys_dirname)
3548 {
3549 g_free (sys_orig_dir);
3550 cmpl_errno = CMPL_ERRNO_DID_NOT_CONVERT;
3551 return NULL;
3552 }
3553
3554 if (chdir (sys_dirname) != 0 || chdir ("..") != 0)
3555 {
3556 cmpl_errno = errno;
3557 chdir (sys_orig_dir);
3558 g_free (sys_dirname);
3559 g_free (sys_orig_dir);
3560 return NULL;
3561 }
3562 g_free (sys_dirname);
3563
3564 sys_cwd = g_get_current_dir ();
3565 result = g_filename_to_utf8 (sys_cwd, -1, NULL, NULL, NULL);
3566 g_free (sys_cwd);
3567
3568 if (chdir (sys_orig_dir) != 0)
3569 {
3570 cmpl_errno = errno;
3571 g_free (sys_orig_dir);
3572 return NULL;
3573 }
3574
3575 g_free (sys_orig_dir);
3576 return result;
3577}
3578
3579#endif
3580
3581/**********************************************************************/
3582/* Completion Operations */
3583/**********************************************************************/
3584
3585#ifdef HAVE_PWD_H
3586
3587static PossibleCompletion*
3588attempt_homedir_completion (gchar *text_to_complete,
3589 CompletionState *cmpl_state)
3590{
3591 gint index, length;
3592
3593 if (!cmpl_state->user_dir_name_buffer &&
3594 !get_pwdb (cmpl_state))
3595 return NULL;
3596 length = strlen (text_to_complete) - 1;
3597
3598 cmpl_state->user_completion_index += 1;
3599
3600 while (cmpl_state->user_completion_index < cmpl_state->user_directories_len)
3601 {
3602 index = first_diff_index (text_to_complete + 1,
3603 cmpl_state->user_directories
3604 [cmpl_state->user_completion_index].login);
3605
3606 switch (index)
3607 {
3608 case PATTERN_MATCH:
3609 break;
3610 default:
3611 if (cmpl_state->last_valid_char < (index + 1))
3612 cmpl_state->last_valid_char = index + 1;
3613 cmpl_state->user_completion_index += 1;
3614 continue;
3615 }
3616
3617 cmpl_state->the_completion.is_a_completion = 1;
3618 cmpl_state->the_completion.is_directory = TRUE;
3619
3620 append_completion_text ("~", cmpl_state);
3621
3622 append_completion_text (cmpl_state->
3623 user_directories[cmpl_state->user_completion_index].login,
3624 cmpl_state);
3625
3626 return append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3627 }
3628
3629 if (text_to_complete[1]
3630 || cmpl_state->user_completion_index > cmpl_state->user_directories_len)
3631 {
3632 cmpl_state->user_completion_index = -1;
3633 return NULL;
3634 }
3635 else
3636 {
3637 cmpl_state->user_completion_index += 1;
3638 cmpl_state->the_completion.is_a_completion = 1;
3639 cmpl_state->the_completion.is_directory = TRUE;
3640
3641 return append_completion_text ("~" G_DIR_SEPARATOR_S, cmpl_state);
3642 }
3643}
3644
3645#endif
3646
3647#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
3648#define FOLD(c) (tolower(c))
3649#else
3650#define FOLD(c) (c)
3651#endif
3652
3653/* returns the index (>= 0) of the first differing character,
3654 * PATTERN_MATCH if the completion matches */
3655static gint
3656first_diff_index (gchar *pat,
3657 gchar *text)
3658{
3659 gint diff = 0;
3660
3661 while (*pat && *text && FOLD (*text) == FOLD (*pat))
3662 {
3663 pat += 1;
3664 text += 1;
3665 diff += 1;
3666 }
3667
3668 if (*pat)
3669 return diff;
3670
3671 return PATTERN_MATCH;
3672}
3673
3674static PossibleCompletion*
3675append_completion_text (gchar *text,
3676 CompletionState *cmpl_state)
3677{
3678 gint len, i = 1;
3679
3680 if (!cmpl_state->the_completion.text)
3681 return NULL;
3682
3683 len = strlen (text) + strlen (cmpl_state->the_completion.text) + 1;
3684
3685 if (cmpl_state->the_completion.text_alloc > len)
3686 {
3687 strcat (cmpl_state->the_completion.text, text);
3688 return &cmpl_state->the_completion;
3689 }
3690
3691 while (i < len)
3692 i <<= 1;
3693
3694 cmpl_state->the_completion.text_alloc = i;
3695
3696 cmpl_state->the_completion.text = (gchar*) g_realloc (cmpl_state->the_completion.text, i);
3697
3698 if (!cmpl_state->the_completion.text)
3699 return NULL;
3700 else
3701 {
3702 strcat (cmpl_state->the_completion.text, text);
3703 return &cmpl_state->the_completion;
3704 }
3705}
3706
3707static CompletionDir*
3708find_completion_dir (gchar *text_to_complete,
3709 gchar **remaining_text,
3710 CompletionState *cmpl_state)
3711{
3712 gchar* first_slash = strchr (text_to_complete, G_DIR_SEPARATOR);
3713 CompletionDir* dir = cmpl_state->reference_dir;
3714 CompletionDir* next;
3715 *remaining_text = text_to_complete;
3716
3717 while (first_slash)
3718 {
3719 gint len = first_slash - *remaining_text;
3720 gint found = 0;
3721 gchar *found_name = NULL; /* Quiet gcc */
3722 gint i;
3723 gchar* pat_buf = g_new (gchar, len + 1);
3724
3725 strncpy (pat_buf, *remaining_text, len);
3726 pat_buf[len] = 0;
3727
3728 for (i = 0; i < dir->sent->entry_count; i += 1)
3729 {
3730 if (dir->sent->entries[i].is_dir &&
3731 _gtk_fnmatch (pat_buf, dir->sent->entries[i].entry_name))
3732 {
3733 if (found)
3734 {
3735 g_free (pat_buf);
3736 return dir;
3737 }
3738 else
3739 {
3740 found = 1;
3741 found_name = dir->sent->entries[i].entry_name;
3742 }
3743 }
3744 }
3745
3746 if (!found)
3747 {
3748 /* Perhaps we are trying to open an automount directory */
3749 found_name = pat_buf;
3750 }
3751
3752 next = open_relative_dir (found_name, dir, cmpl_state);
3753
3754 if (!next)
3755 {
3756 g_free (pat_buf);
3757 return NULL;
3758}
3759
3760 next->cmpl_parent = dir;
3761
3762 dir = next;
3763
3764 if (!correct_dir_fullname (dir))
3765 {
3766 g_free (pat_buf);
3767 return NULL;
3768 }
3769
3770 *remaining_text = first_slash + 1;
3771 first_slash = strchr (*remaining_text, G_DIR_SEPARATOR);
3772
3773 g_free (pat_buf);
3774 }
3775
3776 return dir;
3777}
3778
3779static void
3780update_cmpl (PossibleCompletion *poss,
3781 CompletionState *cmpl_state)
3782{
3783 gint cmpl_len;
3784
3785 if (!poss || !cmpl_is_a_completion (poss))
3786 return;
3787
3788 cmpl_len = strlen (cmpl_this_completion (poss));
3789
3790 if (cmpl_state->updated_text_alloc < cmpl_len + 1)
3791 {
3792 cmpl_state->updated_text =
3793 (gchar*)g_realloc (cmpl_state->updated_text,
3794 cmpl_state->updated_text_alloc);
3795 cmpl_state->updated_text_alloc = 2*cmpl_len;
3796 }
3797
3798 if (cmpl_state->updated_text_len < 0)
3799 {
3800 strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3801 cmpl_state->updated_text_len = cmpl_len;
3802 cmpl_state->re_complete = cmpl_is_directory (poss);
3803 }
3804 else if (cmpl_state->updated_text_len == 0)
3805 {
3806 cmpl_state->re_complete = FALSE;
3807 }
3808 else
3809 {
3810 gint first_diff =
3811 first_diff_index (cmpl_state->updated_text,
3812 cmpl_this_completion (poss));
3813
3814 cmpl_state->re_complete = FALSE;
3815
3816 if (first_diff == PATTERN_MATCH)
3817 return;
3818
3819 if (first_diff > cmpl_state->updated_text_len)
3820 strcpy (cmpl_state->updated_text, cmpl_this_completion (poss));
3821
3822 cmpl_state->updated_text_len = first_diff;
3823 cmpl_state->updated_text[first_diff] = 0;
3824 }
3825}
3826
3827static PossibleCompletion*
3828attempt_dir_completion (CompletionState *cmpl_state)
3829{
3830 gchar *pat_buf, *first_slash;
3831 CompletionDir *dir = cmpl_state->active_completion_dir;
3832
3833 dir->cmpl_index += 1;
3834
3835 if (dir->cmpl_index == dir->sent->entry_count)
3836 {
3837 if (dir->cmpl_parent == NULL)
3838 {
3839 cmpl_state->active_completion_dir = NULL;
3840
3841 return NULL;
3842 }
3843 else
3844 {
3845 cmpl_state->active_completion_dir = dir->cmpl_parent;
3846
3847 return attempt_dir_completion (cmpl_state);
3848 }
3849 }
3850
3851 g_assert (dir->cmpl_text);
3852
3853 first_slash = strchr (dir->cmpl_text, G_DIR_SEPARATOR);
3854
3855 if (first_slash)
3856 {
3857 gint len = first_slash - dir->cmpl_text;
3858
3859 pat_buf = g_new (gchar, len + 1);
3860 strncpy (pat_buf, dir->cmpl_text, len);
3861 pat_buf[len] = 0;
3862 }
3863 else
3864 {
3865 gint len = strlen (dir->cmpl_text);
3866
3867 pat_buf = g_new (gchar, len + 2);
3868 strcpy (pat_buf, dir->cmpl_text);
3869 /* Don't append a * if the user entered one herself.
3870 * This way one can complete *.h and don't get matches
3871 * on any .help files, for instance.
3872 */
3873 if (strchr (pat_buf, '*') == NULL)
3874 strcpy (pat_buf + len, "*");
3875 }
3876
3877 if (first_slash)
3878 {
3879 if (dir->sent->entries[dir->cmpl_index].is_dir)
3880 {
3881 if (_gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name))
3882 {
3883 CompletionDir* new_dir;
3884
3885 new_dir = open_relative_dir (dir->sent->entries[dir->cmpl_index].entry_name,
3886 dir, cmpl_state);
3887
3888 if (!new_dir)
3889 {
3890 g_free (pat_buf);
3891 return NULL;
3892 }
3893
3894 new_dir->cmpl_parent = dir;
3895
3896 new_dir->cmpl_index = -1;
3897 new_dir->cmpl_text = g_strdup (first_slash + 1);
3898
3899 cmpl_state->active_completion_dir = new_dir;
3900
3901 g_free (pat_buf);
3902 return attempt_dir_completion (cmpl_state);
3903 }
3904 else
3905 {
3906 g_free (pat_buf);
3907 return attempt_dir_completion (cmpl_state);
3908 }
3909 }
3910 else
3911 {
3912 g_free (pat_buf);
3913 return attempt_dir_completion (cmpl_state);
3914 }
3915 }
3916 else
3917 {
3918 if (dir->cmpl_parent != NULL)
3919 {
3920 append_completion_text (dir->fullname +
3921 strlen (cmpl_state->completion_dir->fullname) + 1,
3922 cmpl_state);
3923 append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3924 }
3925
3926 append_completion_text (dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state);
3927
3928 cmpl_state->the_completion.is_a_completion =
3929 _gtk_fnmatch (pat_buf, dir->sent->entries[dir->cmpl_index].entry_name);
3930
3931 cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir;
3932 if (dir->sent->entries[dir->cmpl_index].is_dir)
3933 append_completion_text (G_DIR_SEPARATOR_S, cmpl_state);
3934
3935 g_free (pat_buf);
3936 return &cmpl_state->the_completion;
3937 }
3938}
3939
3940#ifdef HAVE_PWD_H
3941
3942static gint
3943get_pwdb (CompletionState* cmpl_state)
3944{
3945 struct passwd *pwd_ptr;
3946 gchar* buf_ptr;
3947 gchar *utf8;
3948 gint len = 0, i, count = 0;
3949
3950 if (cmpl_state->user_dir_name_buffer)
3951 return TRUE;
3952 setpwent ();
3953
3954 while ((pwd_ptr = getpwent ()) != NULL)
3955 {
3956 utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3957 len += strlen (utf8);
3958 g_free (utf8);
3959 utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3960 len += strlen (utf8);
3961 g_free (utf8);
3962 len += 2;
3963 count += 1;
3964 }
3965
3966 setpwent ();
3967
3968 cmpl_state->user_dir_name_buffer = g_new (gchar, len);
3969 cmpl_state->user_directories = g_new (CompletionUserDir, count);
3970 cmpl_state->user_directories_len = count;
3971
3972 buf_ptr = cmpl_state->user_dir_name_buffer;
3973
3974 for (i = 0; i < count; i += 1)
3975 {
3976 pwd_ptr = getpwent ();
3977 if (!pwd_ptr)
3978 {
3979 cmpl_errno = errno;
3980 goto error;
3981 }
3982
3983 utf8 = g_filename_to_utf8 (pwd_ptr->pw_name, -1, NULL, NULL, NULL);
3984 strcpy (buf_ptr, utf8);
3985 g_free (utf8);
3986
3987 cmpl_state->user_directories[i].login = buf_ptr;
3988
3989 buf_ptr += strlen (buf_ptr);
3990 buf_ptr += 1;
3991
3992 utf8 = g_filename_to_utf8 (pwd_ptr->pw_dir, -1, NULL, NULL, NULL);
3993 strcpy (buf_ptr, utf8);
3994 g_free (utf8);
3995
3996 cmpl_state->user_directories[i].homedir = buf_ptr;
3997
3998 buf_ptr += strlen (buf_ptr);
3999 buf_ptr += 1;
4000 }
4001
4002 qsort (cmpl_state->user_directories,
4003 cmpl_state->user_directories_len,
4004 sizeof (CompletionUserDir),
4005 compare_user_dir);
4006
4007 endpwent ();
4008
4009 return TRUE;
4010
4011error:
4012
4013 if (cmpl_state->user_dir_name_buffer)
4014 g_free (cmpl_state->user_dir_name_buffer);
4015 if (cmpl_state->user_directories)
4016 g_free (cmpl_state->user_directories);
4017
4018 cmpl_state->user_dir_name_buffer = NULL;
4019 cmpl_state->user_directories = NULL;
4020
4021 return FALSE;
4022}
4023
4024static gint
4025compare_user_dir (const void *a,
4026 const void *b)
4027{
4028 return strcmp ((((CompletionUserDir*)a))->login,
4029 (((CompletionUserDir*)b))->login);
4030}
4031
4032#endif
4033
4034static gint
4035compare_cmpl_dir (const void *a,
4036 const void *b)
4037{
4038
4039 return strcmp (((CompletionDirEntry*)a)->sort_key,
4040 (((CompletionDirEntry*)b))->sort_key);
4041}
4042
4043static gint
4044cmpl_state_okay (CompletionState* cmpl_state)
4045{
4046 return cmpl_state && cmpl_state->reference_dir;
4047}
4048
4049static const gchar*
4050cmpl_strerror (gint err)
4051{
4052 if (err == CMPL_ERRNO_TOO_LONG)
4053 return _("Name too long");
4054 else if (err == CMPL_ERRNO_DID_NOT_CONVERT)
4055 return _("Couldn't convert filename");
4056 else
4057 return g_strerror (err);
4058}
4059
94dcfb9e 4060const gchar * gtk_dir_selection_get_dir (GtkDirSelection *filesel)
fc188b78 4061{
4062 return gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
4063}
This page took 0.165425 seconds and 4 git commands to generate.