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