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