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