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