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 Library 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 | * Library General Public License for more details. |
---|
13 | * |
---|
14 | * You should have received a copy of the GNU Library 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-1999. 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 <stdio.h> |
---|
28 | #include <sys/types.h> |
---|
29 | #include <sys/stat.h> |
---|
30 | #include <sys/param.h> |
---|
31 | #include <dirent.h> |
---|
32 | #include <stdlib.h> |
---|
33 | #include <unistd.h> |
---|
34 | #include <string.h> |
---|
35 | #include <errno.h> |
---|
36 | #include <pwd.h> |
---|
37 | #include "fnmatch.h" |
---|
38 | |
---|
39 | #include "gdk/gdkkeysyms.h" |
---|
40 | #include "gtkbutton.h" |
---|
41 | #include "gtkentry.h" |
---|
42 | #include "gtkfilesel.h" |
---|
43 | #include "gtkhbox.h" |
---|
44 | #include "gtkhbbox.h" |
---|
45 | #include "gtklabel.h" |
---|
46 | #include "gtklist.h" |
---|
47 | #include "gtklistitem.h" |
---|
48 | #include "gtkmain.h" |
---|
49 | #include "gtkscrolledwindow.h" |
---|
50 | #include "gtksignal.h" |
---|
51 | #include "gtkvbox.h" |
---|
52 | #include "gtkmenu.h" |
---|
53 | #include "gtkmenuitem.h" |
---|
54 | #include "gtkoptionmenu.h" |
---|
55 | #include "gtkclist.h" |
---|
56 | #include "gtkdialog.h" |
---|
57 | #include "gtkintl.h" |
---|
58 | |
---|
59 | #define DIR_LIST_WIDTH 180 |
---|
60 | #define DIR_LIST_HEIGHT 180 |
---|
61 | #define FILE_LIST_WIDTH 180 |
---|
62 | #define FILE_LIST_HEIGHT 180 |
---|
63 | |
---|
64 | /* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this |
---|
65 | * in here, since the rest of the code in the file does require some |
---|
66 | * fixed maximum. |
---|
67 | */ |
---|
68 | #ifndef MAXPATHLEN |
---|
69 | # ifdef PATH_MAX |
---|
70 | # define MAXPATHLEN PATH_MAX |
---|
71 | # else |
---|
72 | # define MAXPATHLEN 2048 |
---|
73 | # endif |
---|
74 | #endif |
---|
75 | |
---|
76 | /* I've put this here so it doesn't get confused with the |
---|
77 | * file completion interface */ |
---|
78 | typedef struct _HistoryCallbackArg HistoryCallbackArg; |
---|
79 | |
---|
80 | struct _HistoryCallbackArg |
---|
81 | { |
---|
82 | gchar *directory; |
---|
83 | GtkWidget *menu_item; |
---|
84 | }; |
---|
85 | |
---|
86 | |
---|
87 | typedef struct _CompletionState CompletionState; |
---|
88 | typedef struct _CompletionDir CompletionDir; |
---|
89 | typedef struct _CompletionDirSent CompletionDirSent; |
---|
90 | typedef struct _CompletionDirEntry CompletionDirEntry; |
---|
91 | typedef struct _CompletionUserDir CompletionUserDir; |
---|
92 | typedef struct _PossibleCompletion PossibleCompletion; |
---|
93 | |
---|
94 | /* Non-external file completion decls and structures */ |
---|
95 | |
---|
96 | /* A contant telling PRCS how many directories to cache. Its actually |
---|
97 | * kept in a list, so the geometry isn't important. */ |
---|
98 | #define CMPL_DIRECTORY_CACHE_SIZE 10 |
---|
99 | |
---|
100 | /* A constant used to determine whether a substring was an exact |
---|
101 | * match by first_diff_index() |
---|
102 | */ |
---|
103 | #define PATTERN_MATCH -1 |
---|
104 | /* The arguments used by all fnmatch() calls below |
---|
105 | */ |
---|
106 | #define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD) |
---|
107 | |
---|
108 | #define CMPL_ERRNO_TOO_LONG ((1<<16)-1) |
---|
109 | |
---|
110 | /* This structure contains all the useful information about a directory |
---|
111 | * for the purposes of filename completion. These structures are cached |
---|
112 | * in the CompletionState struct. CompletionDir's are reference counted. |
---|
113 | */ |
---|
114 | struct _CompletionDirSent |
---|
115 | { |
---|
116 | ino_t inode; |
---|
117 | time_t mtime; |
---|
118 | dev_t device; |
---|
119 | |
---|
120 | gint entry_count; |
---|
121 | gchar *name_buffer; /* memory segment containing names of all entries */ |
---|
122 | |
---|
123 | struct _CompletionDirEntry *entries; |
---|
124 | }; |
---|
125 | |
---|
126 | struct _CompletionDir |
---|
127 | { |
---|
128 | CompletionDirSent *sent; |
---|
129 | |
---|
130 | gchar *fullname; |
---|
131 | gint fullname_len; |
---|
132 | |
---|
133 | struct _CompletionDir *cmpl_parent; |
---|
134 | gint cmpl_index; |
---|
135 | gchar *cmpl_text; |
---|
136 | }; |
---|
137 | |
---|
138 | /* This structure contains pairs of directory entry names with a flag saying |
---|
139 | * whether or not they are a valid directory. NOTE: This information is used |
---|
140 | * to provide the caller with information about whether to update its completions |
---|
141 | * or try to open a file. Since directories are cached by the directory mtime, |
---|
142 | * a symlink which points to an invalid file (which will not be a directory), |
---|
143 | * will not be reevaluated if that file is created, unless the containing |
---|
144 | * directory is touched. I consider this case to be worth ignoring (josh). |
---|
145 | */ |
---|
146 | struct _CompletionDirEntry |
---|
147 | { |
---|
148 | gint is_dir; |
---|
149 | gchar *entry_name; |
---|
150 | }; |
---|
151 | |
---|
152 | struct _CompletionUserDir |
---|
153 | { |
---|
154 | gchar *login; |
---|
155 | gchar *homedir; |
---|
156 | }; |
---|
157 | |
---|
158 | struct _PossibleCompletion |
---|
159 | { |
---|
160 | /* accessible fields, all are accessed externally by functions |
---|
161 | * declared above |
---|
162 | */ |
---|
163 | gchar *text; |
---|
164 | gint is_a_completion; |
---|
165 | gint is_directory; |
---|
166 | |
---|
167 | /* Private fields |
---|
168 | */ |
---|
169 | gint text_alloc; |
---|
170 | }; |
---|
171 | |
---|
172 | struct _CompletionState |
---|
173 | { |
---|
174 | gint last_valid_char; |
---|
175 | gchar *updated_text; |
---|
176 | gint updated_text_len; |
---|
177 | gint updated_text_alloc; |
---|
178 | gint re_complete; |
---|
179 | |
---|
180 | gchar *user_dir_name_buffer; |
---|
181 | gint user_directories_len; |
---|
182 | |
---|
183 | gchar *last_completion_text; |
---|
184 | |
---|
185 | gint user_completion_index; /* if >= 0, currently completing ~user */ |
---|
186 | |
---|
187 | struct _CompletionDir *completion_dir; /* directory completing from */ |
---|
188 | struct _CompletionDir *active_completion_dir; |
---|
189 | |
---|
190 | struct _PossibleCompletion the_completion; |
---|
191 | |
---|
192 | struct _CompletionDir *reference_dir; /* initial directory */ |
---|
193 | |
---|
194 | GList* directory_storage; |
---|
195 | GList* directory_sent_storage; |
---|
196 | |
---|
197 | struct _CompletionUserDir *user_directories; |
---|
198 | }; |
---|
199 | |
---|
200 | |
---|
201 | /* File completion functions which would be external, were they used |
---|
202 | * outside of this file. |
---|
203 | */ |
---|
204 | |
---|
205 | static CompletionState* cmpl_init_state (void); |
---|
206 | static void cmpl_free_state (CompletionState *cmpl_state); |
---|
207 | static gint cmpl_state_okay (CompletionState* cmpl_state); |
---|
208 | static gchar* cmpl_strerror (gint); |
---|
209 | |
---|
210 | static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete, |
---|
211 | gchar **remaining_text, |
---|
212 | CompletionState *cmpl_state); |
---|
213 | |
---|
214 | /* Returns a name for consideration, possibly a completion, this name |
---|
215 | * will be invalid after the next call to cmpl_next_completion. |
---|
216 | */ |
---|
217 | static char* cmpl_this_completion (PossibleCompletion*); |
---|
218 | |
---|
219 | /* True if this completion matches the given text. Otherwise, this |
---|
220 | * output can be used to have a list of non-completions. |
---|
221 | */ |
---|
222 | static gint cmpl_is_a_completion (PossibleCompletion*); |
---|
223 | |
---|
224 | /* True if the completion is a directory |
---|
225 | */ |
---|
226 | static gint cmpl_is_directory (PossibleCompletion*); |
---|
227 | |
---|
228 | /* Obtains the next completion, or NULL |
---|
229 | */ |
---|
230 | static PossibleCompletion* cmpl_next_completion (CompletionState*); |
---|
231 | |
---|
232 | /* Updating completions: the return value of cmpl_updated_text() will |
---|
233 | * be text_to_complete completed as much as possible after the most |
---|
234 | * recent call to cmpl_completion_matches. For the present |
---|
235 | * application, this is the suggested replacement for the user's input |
---|
236 | * string. You must CALL THIS AFTER ALL cmpl_text_completions have |
---|
237 | * been received. |
---|
238 | */ |
---|
239 | static gchar* cmpl_updated_text (CompletionState* cmpl_state); |
---|
240 | |
---|
241 | /* After updating, to see if the completion was a directory, call |
---|
242 | * this. If it was, you should consider re-calling completion_matches. |
---|
243 | */ |
---|
244 | static gint cmpl_updated_dir (CompletionState* cmpl_state); |
---|
245 | |
---|
246 | /* Current location: if using file completion, return the current |
---|
247 | * directory, from which file completion begins. More specifically, |
---|
248 | * the cwd concatenated with all exact completions up to the last |
---|
249 | * directory delimiter('/'). |
---|
250 | */ |
---|
251 | static gchar* cmpl_reference_position (CompletionState* cmpl_state); |
---|
252 | |
---|
253 | /* backing up: if cmpl_completion_matches returns NULL, you may query |
---|
254 | * the index of the last completable character into cmpl_updated_text. |
---|
255 | */ |
---|
256 | static gint cmpl_last_valid_char (CompletionState* cmpl_state); |
---|
257 | |
---|
258 | /* When the user selects a non-directory, call cmpl_completion_fullname |
---|
259 | * to get the full name of the selected file. |
---|
260 | */ |
---|
261 | static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state); |
---|
262 | |
---|
263 | |
---|
264 | /* Directory operations. */ |
---|
265 | static CompletionDir* open_ref_dir (gchar* text_to_complete, |
---|
266 | gchar** remaining_text, |
---|
267 | CompletionState* cmpl_state); |
---|
268 | static gboolean check_dir (gchar *dir_name, |
---|
269 | struct stat *result, |
---|
270 | gboolean *stat_subdirs); |
---|
271 | static CompletionDir* open_dir (gchar* dir_name, |
---|
272 | CompletionState* cmpl_state); |
---|
273 | static CompletionDir* open_user_dir (gchar* text_to_complete, |
---|
274 | CompletionState *cmpl_state); |
---|
275 | static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir, |
---|
276 | CompletionState *cmpl_state); |
---|
277 | static CompletionDirSent* open_new_dir (gchar* dir_name, |
---|
278 | struct stat* sbuf, |
---|
279 | gboolean stat_subdirs); |
---|
280 | static gint correct_dir_fullname (CompletionDir* cmpl_dir); |
---|
281 | static gint correct_parent (CompletionDir* cmpl_dir, |
---|
282 | struct stat *sbuf); |
---|
283 | static gchar* find_parent_dir_fullname (gchar* dirname); |
---|
284 | static CompletionDir* attach_dir (CompletionDirSent* sent, |
---|
285 | gchar* dir_name, |
---|
286 | CompletionState *cmpl_state); |
---|
287 | static void free_dir_sent (CompletionDirSent* sent); |
---|
288 | static void free_dir (CompletionDir *dir); |
---|
289 | static void prune_memory_usage(CompletionState *cmpl_state); |
---|
290 | |
---|
291 | /* Completion operations */ |
---|
292 | static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete, |
---|
293 | CompletionState *cmpl_state); |
---|
294 | static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state); |
---|
295 | static CompletionDir* find_completion_dir(gchar* text_to_complete, |
---|
296 | gchar** remaining_text, |
---|
297 | CompletionState* cmpl_state); |
---|
298 | static PossibleCompletion* append_completion_text(gchar* text, |
---|
299 | CompletionState* cmpl_state); |
---|
300 | static gint get_pwdb(CompletionState* cmpl_state); |
---|
301 | static gint first_diff_index(gchar* pat, gchar* text); |
---|
302 | static gint compare_user_dir(const void* a, const void* b); |
---|
303 | static gint compare_cmpl_dir(const void* a, const void* b); |
---|
304 | static void update_cmpl(PossibleCompletion* poss, |
---|
305 | CompletionState* cmpl_state); |
---|
306 | |
---|
307 | static void gtk_file_selection_class_init (GtkFileSelectionClass *klass); |
---|
308 | static void gtk_file_selection_init (GtkFileSelection *filesel); |
---|
309 | static void gtk_file_selection_destroy (GtkObject *object); |
---|
310 | static gint gtk_file_selection_key_press (GtkWidget *widget, |
---|
311 | GdkEventKey *event, |
---|
312 | gpointer user_data); |
---|
313 | |
---|
314 | static void gtk_file_selection_file_button (GtkWidget *widget, |
---|
315 | gint row, |
---|
316 | gint column, |
---|
317 | GdkEventButton *bevent, |
---|
318 | gpointer user_data); |
---|
319 | |
---|
320 | static void gtk_file_selection_dir_button (GtkWidget *widget, |
---|
321 | gint row, |
---|
322 | gint column, |
---|
323 | GdkEventButton *bevent, |
---|
324 | gpointer data); |
---|
325 | |
---|
326 | static void gtk_file_selection_populate (GtkFileSelection *fs, |
---|
327 | gchar *rel_path, |
---|
328 | gint try_complete); |
---|
329 | static void gtk_file_selection_abort (GtkFileSelection *fs); |
---|
330 | |
---|
331 | static void gtk_file_selection_update_history_menu (GtkFileSelection *fs, |
---|
332 | gchar *current_dir); |
---|
333 | |
---|
334 | static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data); |
---|
335 | static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data); |
---|
336 | static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data); |
---|
337 | |
---|
338 | |
---|
339 | |
---|
340 | static GtkWindowClass *parent_class = NULL; |
---|
341 | |
---|
342 | /* Saves errno when something cmpl does fails. */ |
---|
343 | static gint cmpl_errno; |
---|
344 | |
---|
345 | GtkType |
---|
346 | gtk_file_selection_get_type (void) |
---|
347 | { |
---|
348 | static GtkType file_selection_type = 0; |
---|
349 | |
---|
350 | if (!file_selection_type) |
---|
351 | { |
---|
352 | static const GtkTypeInfo filesel_info = |
---|
353 | { |
---|
354 | "GtkFileSelection", |
---|
355 | sizeof (GtkFileSelection), |
---|
356 | sizeof (GtkFileSelectionClass), |
---|
357 | (GtkClassInitFunc) gtk_file_selection_class_init, |
---|
358 | (GtkObjectInitFunc) gtk_file_selection_init, |
---|
359 | /* reserved_1 */ NULL, |
---|
360 | /* reserved_2 */ NULL, |
---|
361 | (GtkClassInitFunc) NULL, |
---|
362 | }; |
---|
363 | |
---|
364 | file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info); |
---|
365 | } |
---|
366 | |
---|
367 | return file_selection_type; |
---|
368 | } |
---|
369 | |
---|
370 | static void |
---|
371 | gtk_file_selection_class_init (GtkFileSelectionClass *class) |
---|
372 | { |
---|
373 | GtkObjectClass *object_class; |
---|
374 | |
---|
375 | object_class = (GtkObjectClass*) class; |
---|
376 | |
---|
377 | parent_class = gtk_type_class (GTK_TYPE_WINDOW); |
---|
378 | |
---|
379 | object_class->destroy = gtk_file_selection_destroy; |
---|
380 | } |
---|
381 | |
---|
382 | static void |
---|
383 | gtk_file_selection_init (GtkFileSelection *filesel) |
---|
384 | { |
---|
385 | GtkWidget *entry_vbox; |
---|
386 | GtkWidget *label; |
---|
387 | GtkWidget *list_hbox; |
---|
388 | GtkWidget *confirm_area; |
---|
389 | GtkWidget *pulldown_hbox; |
---|
390 | GtkWidget *scrolled_win; |
---|
391 | |
---|
392 | char *dir_title [2]; |
---|
393 | char *file_title [2]; |
---|
394 | |
---|
395 | filesel->cmpl_state = cmpl_init_state (); |
---|
396 | |
---|
397 | /* The dialog-sized vertical box */ |
---|
398 | filesel->main_vbox = gtk_vbox_new (FALSE, 10); |
---|
399 | gtk_container_set_border_width (GTK_CONTAINER (filesel), 10); |
---|
400 | gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox); |
---|
401 | gtk_widget_show (filesel->main_vbox); |
---|
402 | |
---|
403 | /* The horizontal box containing create, rename etc. buttons */ |
---|
404 | filesel->button_area = gtk_hbutton_box_new (); |
---|
405 | gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START); |
---|
406 | gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0); |
---|
407 | gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, |
---|
408 | FALSE, FALSE, 0); |
---|
409 | gtk_widget_show (filesel->button_area); |
---|
410 | |
---|
411 | gtk_file_selection_show_fileop_buttons(filesel); |
---|
412 | |
---|
413 | /* hbox for pulldown menu */ |
---|
414 | pulldown_hbox = gtk_hbox_new (TRUE, 5); |
---|
415 | gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0); |
---|
416 | gtk_widget_show (pulldown_hbox); |
---|
417 | |
---|
418 | /* Pulldown menu */ |
---|
419 | filesel->history_pulldown = gtk_option_menu_new (); |
---|
420 | gtk_widget_show (filesel->history_pulldown); |
---|
421 | gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, |
---|
422 | FALSE, FALSE, 0); |
---|
423 | |
---|
424 | /* The horizontal box containing the directory and file listboxes */ |
---|
425 | list_hbox = gtk_hbox_new (FALSE, 5); |
---|
426 | gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0); |
---|
427 | gtk_widget_show (list_hbox); |
---|
428 | |
---|
429 | /* The directories clist */ |
---|
430 | dir_title[0] = _("Directories"); |
---|
431 | dir_title[1] = NULL; |
---|
432 | filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title); |
---|
433 | gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT); |
---|
434 | gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row", |
---|
435 | (GtkSignalFunc) gtk_file_selection_dir_button, |
---|
436 | (gpointer) filesel); |
---|
437 | gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); |
---|
438 | |
---|
439 | scrolled_win = gtk_scrolled_window_new (NULL, NULL); |
---|
440 | gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list); |
---|
441 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), |
---|
442 | GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); |
---|
443 | gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); |
---|
444 | gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); |
---|
445 | gtk_widget_show (filesel->dir_list); |
---|
446 | gtk_widget_show (scrolled_win); |
---|
447 | |
---|
448 | /* The files clist */ |
---|
449 | file_title[0] = _("Files"); |
---|
450 | file_title[1] = NULL; |
---|
451 | filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title); |
---|
452 | gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT); |
---|
453 | gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row", |
---|
454 | (GtkSignalFunc) gtk_file_selection_file_button, |
---|
455 | (gpointer) filesel); |
---|
456 | gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); |
---|
457 | |
---|
458 | scrolled_win = gtk_scrolled_window_new (NULL, NULL); |
---|
459 | gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list); |
---|
460 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), |
---|
461 | GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); |
---|
462 | gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); |
---|
463 | gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); |
---|
464 | gtk_widget_show (filesel->file_list); |
---|
465 | gtk_widget_show (scrolled_win); |
---|
466 | |
---|
467 | /* action area for packing buttons into. */ |
---|
468 | filesel->action_area = gtk_hbox_new (TRUE, 0); |
---|
469 | gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area, |
---|
470 | FALSE, FALSE, 0); |
---|
471 | gtk_widget_show (filesel->action_area); |
---|
472 | |
---|
473 | /* The OK/Cancel button area */ |
---|
474 | confirm_area = gtk_hbutton_box_new (); |
---|
475 | gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END); |
---|
476 | gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5); |
---|
477 | gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0); |
---|
478 | gtk_widget_show (confirm_area); |
---|
479 | |
---|
480 | /* The OK button */ |
---|
481 | filesel->ok_button = gtk_button_new_with_label (_("OK")); |
---|
482 | GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT); |
---|
483 | gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0); |
---|
484 | gtk_widget_grab_default (filesel->ok_button); |
---|
485 | gtk_widget_show (filesel->ok_button); |
---|
486 | |
---|
487 | /* The Cancel button */ |
---|
488 | filesel->cancel_button = gtk_button_new_with_label (_("Cancel")); |
---|
489 | GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT); |
---|
490 | gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0); |
---|
491 | gtk_widget_show (filesel->cancel_button); |
---|
492 | |
---|
493 | /* The selection entry widget */ |
---|
494 | entry_vbox = gtk_vbox_new (FALSE, 2); |
---|
495 | gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0); |
---|
496 | gtk_widget_show (entry_vbox); |
---|
497 | |
---|
498 | filesel->selection_text = label = gtk_label_new (""); |
---|
499 | gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); |
---|
500 | gtk_box_pack_start (GTK_BOX (entry_vbox), label, FALSE, FALSE, 0); |
---|
501 | gtk_widget_show (label); |
---|
502 | |
---|
503 | filesel->selection_entry = gtk_entry_new (); |
---|
504 | gtk_signal_connect (GTK_OBJECT (filesel->selection_entry), "key_press_event", |
---|
505 | (GtkSignalFunc) gtk_file_selection_key_press, filesel); |
---|
506 | gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "focus_in_event", |
---|
507 | (GtkSignalFunc) gtk_widget_grab_default, |
---|
508 | GTK_OBJECT (filesel->ok_button)); |
---|
509 | gtk_signal_connect_object (GTK_OBJECT (filesel->selection_entry), "activate", |
---|
510 | (GtkSignalFunc) gtk_button_clicked, |
---|
511 | GTK_OBJECT (filesel->ok_button)); |
---|
512 | gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0); |
---|
513 | gtk_widget_show (filesel->selection_entry); |
---|
514 | |
---|
515 | if (!cmpl_state_okay (filesel->cmpl_state)) |
---|
516 | { |
---|
517 | gchar err_buf[256]; |
---|
518 | |
---|
519 | sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno)); |
---|
520 | |
---|
521 | gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf); |
---|
522 | } |
---|
523 | else |
---|
524 | { |
---|
525 | gtk_file_selection_populate (filesel, "", FALSE); |
---|
526 | } |
---|
527 | |
---|
528 | gtk_widget_grab_focus (filesel->selection_entry); |
---|
529 | } |
---|
530 | |
---|
531 | GtkWidget* |
---|
532 | gtk_file_selection_new (const gchar *title) |
---|
533 | { |
---|
534 | GtkFileSelection *filesel; |
---|
535 | |
---|
536 | filesel = gtk_type_new (GTK_TYPE_FILE_SELECTION); |
---|
537 | gtk_window_set_title (GTK_WINDOW (filesel), title); |
---|
538 | |
---|
539 | return GTK_WIDGET (filesel); |
---|
540 | } |
---|
541 | |
---|
542 | void |
---|
543 | gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel) |
---|
544 | { |
---|
545 | g_return_if_fail (filesel != NULL); |
---|
546 | g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); |
---|
547 | |
---|
548 | /* delete, create directory, and rename */ |
---|
549 | if (!filesel->fileop_c_dir) |
---|
550 | { |
---|
551 | filesel->fileop_c_dir = gtk_button_new_with_label (_("Create Dir")); |
---|
552 | gtk_signal_connect (GTK_OBJECT (filesel->fileop_c_dir), "clicked", |
---|
553 | (GtkSignalFunc) gtk_file_selection_create_dir, |
---|
554 | (gpointer) filesel); |
---|
555 | gtk_box_pack_start (GTK_BOX (filesel->button_area), |
---|
556 | filesel->fileop_c_dir, TRUE, TRUE, 0); |
---|
557 | gtk_widget_show (filesel->fileop_c_dir); |
---|
558 | } |
---|
559 | |
---|
560 | if (!filesel->fileop_del_file) |
---|
561 | { |
---|
562 | filesel->fileop_del_file = gtk_button_new_with_label (_("Delete File")); |
---|
563 | gtk_signal_connect (GTK_OBJECT (filesel->fileop_del_file), "clicked", |
---|
564 | (GtkSignalFunc) gtk_file_selection_delete_file, |
---|
565 | (gpointer) filesel); |
---|
566 | gtk_box_pack_start (GTK_BOX (filesel->button_area), |
---|
567 | filesel->fileop_del_file, TRUE, TRUE, 0); |
---|
568 | gtk_widget_show (filesel->fileop_del_file); |
---|
569 | } |
---|
570 | |
---|
571 | if (!filesel->fileop_ren_file) |
---|
572 | { |
---|
573 | filesel->fileop_ren_file = gtk_button_new_with_label (_("Rename File")); |
---|
574 | gtk_signal_connect (GTK_OBJECT (filesel->fileop_ren_file), "clicked", |
---|
575 | (GtkSignalFunc) gtk_file_selection_rename_file, |
---|
576 | (gpointer) filesel); |
---|
577 | gtk_box_pack_start (GTK_BOX (filesel->button_area), |
---|
578 | filesel->fileop_ren_file, TRUE, TRUE, 0); |
---|
579 | gtk_widget_show (filesel->fileop_ren_file); |
---|
580 | } |
---|
581 | |
---|
582 | gtk_widget_queue_resize(GTK_WIDGET(filesel)); |
---|
583 | } |
---|
584 | |
---|
585 | void |
---|
586 | gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel) |
---|
587 | { |
---|
588 | g_return_if_fail (filesel != NULL); |
---|
589 | g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); |
---|
590 | |
---|
591 | if (filesel->fileop_ren_file) |
---|
592 | { |
---|
593 | gtk_widget_destroy (filesel->fileop_ren_file); |
---|
594 | filesel->fileop_ren_file = NULL; |
---|
595 | } |
---|
596 | |
---|
597 | if (filesel->fileop_del_file) |
---|
598 | { |
---|
599 | gtk_widget_destroy (filesel->fileop_del_file); |
---|
600 | filesel->fileop_del_file = NULL; |
---|
601 | } |
---|
602 | |
---|
603 | if (filesel->fileop_c_dir) |
---|
604 | { |
---|
605 | gtk_widget_destroy (filesel->fileop_c_dir); |
---|
606 | filesel->fileop_c_dir = NULL; |
---|
607 | } |
---|
608 | } |
---|
609 | |
---|
610 | |
---|
611 | |
---|
612 | void |
---|
613 | gtk_file_selection_set_filename (GtkFileSelection *filesel, |
---|
614 | const gchar *filename) |
---|
615 | { |
---|
616 | char buf[MAXPATHLEN]; |
---|
617 | const char *name, *last_slash; |
---|
618 | |
---|
619 | g_return_if_fail (filesel != NULL); |
---|
620 | g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); |
---|
621 | g_return_if_fail (filename != NULL); |
---|
622 | |
---|
623 | last_slash = strrchr (filename, '/'); |
---|
624 | |
---|
625 | if (!last_slash) |
---|
626 | { |
---|
627 | buf[0] = 0; |
---|
628 | name = filename; |
---|
629 | } |
---|
630 | else |
---|
631 | { |
---|
632 | gint len = MIN (MAXPATHLEN - 1, last_slash - filename + 1); |
---|
633 | |
---|
634 | strncpy (buf, filename, len); |
---|
635 | buf[len] = 0; |
---|
636 | |
---|
637 | name = last_slash + 1; |
---|
638 | } |
---|
639 | |
---|
640 | gtk_file_selection_populate (filesel, buf, FALSE); |
---|
641 | |
---|
642 | if (filesel->selection_entry) |
---|
643 | gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name); |
---|
644 | } |
---|
645 | |
---|
646 | gchar* |
---|
647 | gtk_file_selection_get_filename (GtkFileSelection *filesel) |
---|
648 | { |
---|
649 | static char nothing[2] = ""; |
---|
650 | char *text; |
---|
651 | char *filename; |
---|
652 | |
---|
653 | g_return_val_if_fail (filesel != NULL, nothing); |
---|
654 | g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing); |
---|
655 | |
---|
656 | text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry)); |
---|
657 | if (text) |
---|
658 | { |
---|
659 | filename = cmpl_completion_fullname (text, filesel->cmpl_state); |
---|
660 | return filename; |
---|
661 | } |
---|
662 | |
---|
663 | return nothing; |
---|
664 | } |
---|
665 | |
---|
666 | void |
---|
667 | gtk_file_selection_complete (GtkFileSelection *filesel, |
---|
668 | const gchar *pattern) |
---|
669 | { |
---|
670 | g_return_if_fail (filesel != NULL); |
---|
671 | g_return_if_fail (GTK_IS_FILE_SELECTION (filesel)); |
---|
672 | g_return_if_fail (pattern != NULL); |
---|
673 | |
---|
674 | if (filesel->selection_entry) |
---|
675 | gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern); |
---|
676 | gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE); |
---|
677 | } |
---|
678 | |
---|
679 | static void |
---|
680 | gtk_file_selection_destroy (GtkObject *object) |
---|
681 | { |
---|
682 | GtkFileSelection *filesel; |
---|
683 | GList *list; |
---|
684 | HistoryCallbackArg *callback_arg; |
---|
685 | |
---|
686 | g_return_if_fail (object != NULL); |
---|
687 | g_return_if_fail (GTK_IS_FILE_SELECTION (object)); |
---|
688 | |
---|
689 | filesel = GTK_FILE_SELECTION (object); |
---|
690 | |
---|
691 | if (filesel->fileop_dialog) |
---|
692 | gtk_widget_destroy (filesel->fileop_dialog); |
---|
693 | |
---|
694 | if (filesel->history_list) |
---|
695 | { |
---|
696 | list = filesel->history_list; |
---|
697 | while (list) |
---|
698 | { |
---|
699 | callback_arg = list->data; |
---|
700 | g_free (callback_arg->directory); |
---|
701 | g_free (callback_arg); |
---|
702 | list = list->next; |
---|
703 | } |
---|
704 | g_list_free (filesel->history_list); |
---|
705 | filesel->history_list = NULL; |
---|
706 | } |
---|
707 | |
---|
708 | cmpl_free_state (filesel->cmpl_state); |
---|
709 | filesel->cmpl_state = NULL; |
---|
710 | |
---|
711 | if (GTK_OBJECT_CLASS (parent_class)->destroy) |
---|
712 | (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); |
---|
713 | } |
---|
714 | |
---|
715 | /* Begin file operations callbacks */ |
---|
716 | |
---|
717 | static void |
---|
718 | gtk_file_selection_fileop_error (GtkFileSelection *fs, gchar *error_message) |
---|
719 | { |
---|
720 | GtkWidget *label; |
---|
721 | GtkWidget *vbox; |
---|
722 | GtkWidget *button; |
---|
723 | GtkWidget *dialog; |
---|
724 | |
---|
725 | g_return_if_fail (error_message != NULL); |
---|
726 | |
---|
727 | /* main dialog */ |
---|
728 | dialog = gtk_dialog_new (); |
---|
729 | /* |
---|
730 | gtk_signal_connect (GTK_OBJECT (dialog), "destroy", |
---|
731 | (GtkSignalFunc) gtk_file_selection_fileop_destroy, |
---|
732 | (gpointer) fs); |
---|
733 | */ |
---|
734 | gtk_window_set_title (GTK_WINDOW (dialog), _("Error")); |
---|
735 | gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); |
---|
736 | |
---|
737 | /* If file dialog is grabbed, make this dialog modal too */ |
---|
738 | /* When error dialog is closed, file dialog will be grabbed again */ |
---|
739 | if (GTK_WINDOW(fs)->modal) |
---|
740 | gtk_window_set_modal (GTK_WINDOW(dialog), TRUE); |
---|
741 | |
---|
742 | vbox = gtk_vbox_new(FALSE, 0); |
---|
743 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); |
---|
744 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, |
---|
745 | FALSE, FALSE, 0); |
---|
746 | gtk_widget_show(vbox); |
---|
747 | |
---|
748 | label = gtk_label_new(error_message); |
---|
749 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
---|
750 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); |
---|
751 | gtk_widget_show(label); |
---|
752 | |
---|
753 | /* yes, we free it */ |
---|
754 | g_free (error_message); |
---|
755 | |
---|
756 | /* close button */ |
---|
757 | button = gtk_button_new_with_label (_("Close")); |
---|
758 | gtk_signal_connect_object (GTK_OBJECT (button), "clicked", |
---|
759 | (GtkSignalFunc) gtk_widget_destroy, |
---|
760 | (gpointer) dialog); |
---|
761 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
762 | button, TRUE, TRUE, 0); |
---|
763 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
764 | gtk_widget_grab_default(button); |
---|
765 | gtk_widget_show (button); |
---|
766 | |
---|
767 | gtk_widget_show (dialog); |
---|
768 | } |
---|
769 | |
---|
770 | static void |
---|
771 | gtk_file_selection_fileop_destroy (GtkWidget *widget, gpointer data) |
---|
772 | { |
---|
773 | GtkFileSelection *fs = data; |
---|
774 | |
---|
775 | g_return_if_fail (fs != NULL); |
---|
776 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
777 | |
---|
778 | fs->fileop_dialog = NULL; |
---|
779 | } |
---|
780 | |
---|
781 | |
---|
782 | static void |
---|
783 | gtk_file_selection_create_dir_confirmed (GtkWidget *widget, gpointer data) |
---|
784 | { |
---|
785 | GtkFileSelection *fs = data; |
---|
786 | gchar *dirname; |
---|
787 | gchar *path; |
---|
788 | gchar *full_path; |
---|
789 | gchar *buf; |
---|
790 | CompletionState *cmpl_state; |
---|
791 | |
---|
792 | g_return_if_fail (fs != NULL); |
---|
793 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
794 | |
---|
795 | dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry)); |
---|
796 | cmpl_state = (CompletionState*) fs->cmpl_state; |
---|
797 | path = cmpl_reference_position (cmpl_state); |
---|
798 | |
---|
799 | full_path = g_strconcat (path, "/", dirname, NULL); |
---|
800 | if ( (mkdir (full_path, 0755) < 0) ) |
---|
801 | { |
---|
802 | buf = g_strconcat ("Error creating directory \"", dirname, "\": ", |
---|
803 | g_strerror(errno), NULL); |
---|
804 | gtk_file_selection_fileop_error (fs, buf); |
---|
805 | } |
---|
806 | g_free (full_path); |
---|
807 | |
---|
808 | gtk_widget_destroy (fs->fileop_dialog); |
---|
809 | gtk_file_selection_populate (fs, "", FALSE); |
---|
810 | } |
---|
811 | |
---|
812 | static void |
---|
813 | gtk_file_selection_create_dir (GtkWidget *widget, gpointer data) |
---|
814 | { |
---|
815 | GtkFileSelection *fs = data; |
---|
816 | GtkWidget *label; |
---|
817 | GtkWidget *dialog; |
---|
818 | GtkWidget *vbox; |
---|
819 | GtkWidget *button; |
---|
820 | |
---|
821 | g_return_if_fail (fs != NULL); |
---|
822 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
823 | |
---|
824 | if (fs->fileop_dialog) |
---|
825 | return; |
---|
826 | |
---|
827 | /* main dialog */ |
---|
828 | fs->fileop_dialog = dialog = gtk_dialog_new (); |
---|
829 | gtk_signal_connect (GTK_OBJECT (dialog), "destroy", |
---|
830 | (GtkSignalFunc) gtk_file_selection_fileop_destroy, |
---|
831 | (gpointer) fs); |
---|
832 | gtk_window_set_title (GTK_WINDOW (dialog), _("Create Directory")); |
---|
833 | gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); |
---|
834 | |
---|
835 | /* If file dialog is grabbed, grab option dialog */ |
---|
836 | /* When option dialog is closed, file dialog will be grabbed again */ |
---|
837 | if (GTK_WINDOW(fs)->modal) |
---|
838 | gtk_window_set_modal (GTK_WINDOW(dialog), TRUE); |
---|
839 | |
---|
840 | vbox = gtk_vbox_new(FALSE, 0); |
---|
841 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); |
---|
842 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, |
---|
843 | FALSE, FALSE, 0); |
---|
844 | gtk_widget_show(vbox); |
---|
845 | |
---|
846 | label = gtk_label_new(_("Directory name:")); |
---|
847 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
---|
848 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); |
---|
849 | gtk_widget_show(label); |
---|
850 | |
---|
851 | /* The directory entry widget */ |
---|
852 | fs->fileop_entry = gtk_entry_new (); |
---|
853 | gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, |
---|
854 | TRUE, TRUE, 5); |
---|
855 | GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT); |
---|
856 | gtk_widget_show (fs->fileop_entry); |
---|
857 | |
---|
858 | /* buttons */ |
---|
859 | button = gtk_button_new_with_label (_("Create")); |
---|
860 | gtk_signal_connect (GTK_OBJECT (button), "clicked", |
---|
861 | (GtkSignalFunc) gtk_file_selection_create_dir_confirmed, |
---|
862 | (gpointer) fs); |
---|
863 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
864 | button, TRUE, TRUE, 0); |
---|
865 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
866 | gtk_widget_show(button); |
---|
867 | |
---|
868 | button = gtk_button_new_with_label (_("Cancel")); |
---|
869 | gtk_signal_connect_object (GTK_OBJECT (button), "clicked", |
---|
870 | (GtkSignalFunc) gtk_widget_destroy, |
---|
871 | (gpointer) dialog); |
---|
872 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
873 | button, TRUE, TRUE, 0); |
---|
874 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
875 | gtk_widget_grab_default(button); |
---|
876 | gtk_widget_show (button); |
---|
877 | |
---|
878 | gtk_widget_show (dialog); |
---|
879 | } |
---|
880 | |
---|
881 | static void |
---|
882 | gtk_file_selection_delete_file_confirmed (GtkWidget *widget, gpointer data) |
---|
883 | { |
---|
884 | GtkFileSelection *fs = data; |
---|
885 | CompletionState *cmpl_state; |
---|
886 | gchar *path; |
---|
887 | gchar *full_path; |
---|
888 | gchar *buf; |
---|
889 | |
---|
890 | g_return_if_fail (fs != NULL); |
---|
891 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
892 | |
---|
893 | cmpl_state = (CompletionState*) fs->cmpl_state; |
---|
894 | path = cmpl_reference_position (cmpl_state); |
---|
895 | |
---|
896 | full_path = g_strconcat (path, "/", fs->fileop_file, NULL); |
---|
897 | if ( (unlink (full_path) < 0) ) |
---|
898 | { |
---|
899 | buf = g_strconcat ("Error deleting file \"", fs->fileop_file, "\": ", |
---|
900 | g_strerror(errno), NULL); |
---|
901 | gtk_file_selection_fileop_error (fs, buf); |
---|
902 | } |
---|
903 | g_free (full_path); |
---|
904 | |
---|
905 | gtk_widget_destroy (fs->fileop_dialog); |
---|
906 | gtk_file_selection_populate (fs, "", FALSE); |
---|
907 | } |
---|
908 | |
---|
909 | static void |
---|
910 | gtk_file_selection_delete_file (GtkWidget *widget, gpointer data) |
---|
911 | { |
---|
912 | GtkFileSelection *fs = data; |
---|
913 | GtkWidget *label; |
---|
914 | GtkWidget *vbox; |
---|
915 | GtkWidget *button; |
---|
916 | GtkWidget *dialog; |
---|
917 | gchar *filename; |
---|
918 | gchar *buf; |
---|
919 | |
---|
920 | g_return_if_fail (fs != NULL); |
---|
921 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
922 | |
---|
923 | if (fs->fileop_dialog) |
---|
924 | return; |
---|
925 | |
---|
926 | filename = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); |
---|
927 | if (strlen(filename) < 1) |
---|
928 | return; |
---|
929 | |
---|
930 | fs->fileop_file = filename; |
---|
931 | |
---|
932 | /* main dialog */ |
---|
933 | fs->fileop_dialog = dialog = gtk_dialog_new (); |
---|
934 | gtk_signal_connect (GTK_OBJECT (dialog), "destroy", |
---|
935 | (GtkSignalFunc) gtk_file_selection_fileop_destroy, |
---|
936 | (gpointer) fs); |
---|
937 | gtk_window_set_title (GTK_WINDOW (dialog), _("Delete File")); |
---|
938 | gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); |
---|
939 | |
---|
940 | /* If file dialog is grabbed, grab option dialog */ |
---|
941 | /* When option dialog is closed, file dialog will be grabbed again */ |
---|
942 | if (GTK_WINDOW(fs)->modal) |
---|
943 | gtk_window_set_modal (GTK_WINDOW(dialog), TRUE); |
---|
944 | |
---|
945 | vbox = gtk_vbox_new(FALSE, 0); |
---|
946 | gtk_container_set_border_width(GTK_CONTAINER(vbox), 8); |
---|
947 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, |
---|
948 | FALSE, FALSE, 0); |
---|
949 | gtk_widget_show(vbox); |
---|
950 | |
---|
951 | buf = g_strconcat ("Really delete file \"", filename, "\" ?", NULL); |
---|
952 | label = gtk_label_new(buf); |
---|
953 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
---|
954 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); |
---|
955 | gtk_widget_show(label); |
---|
956 | g_free(buf); |
---|
957 | |
---|
958 | /* buttons */ |
---|
959 | button = gtk_button_new_with_label (_("Delete")); |
---|
960 | gtk_signal_connect (GTK_OBJECT (button), "clicked", |
---|
961 | (GtkSignalFunc) gtk_file_selection_delete_file_confirmed, |
---|
962 | (gpointer) fs); |
---|
963 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
964 | button, TRUE, TRUE, 0); |
---|
965 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
966 | gtk_widget_show(button); |
---|
967 | |
---|
968 | button = gtk_button_new_with_label (_("Cancel")); |
---|
969 | gtk_signal_connect_object (GTK_OBJECT (button), "clicked", |
---|
970 | (GtkSignalFunc) gtk_widget_destroy, |
---|
971 | (gpointer) dialog); |
---|
972 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
973 | button, TRUE, TRUE, 0); |
---|
974 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
975 | gtk_widget_grab_default(button); |
---|
976 | gtk_widget_show (button); |
---|
977 | |
---|
978 | gtk_widget_show (dialog); |
---|
979 | |
---|
980 | } |
---|
981 | |
---|
982 | static void |
---|
983 | gtk_file_selection_rename_file_confirmed (GtkWidget *widget, gpointer data) |
---|
984 | { |
---|
985 | GtkFileSelection *fs = data; |
---|
986 | gchar *buf; |
---|
987 | gchar *file; |
---|
988 | gchar *path; |
---|
989 | gchar *new_filename; |
---|
990 | gchar *old_filename; |
---|
991 | CompletionState *cmpl_state; |
---|
992 | |
---|
993 | g_return_if_fail (fs != NULL); |
---|
994 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
995 | |
---|
996 | file = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry)); |
---|
997 | cmpl_state = (CompletionState*) fs->cmpl_state; |
---|
998 | path = cmpl_reference_position (cmpl_state); |
---|
999 | |
---|
1000 | new_filename = g_strconcat (path, "/", file, NULL); |
---|
1001 | old_filename = g_strconcat (path, "/", fs->fileop_file, NULL); |
---|
1002 | |
---|
1003 | if ( (rename (old_filename, new_filename)) < 0) |
---|
1004 | { |
---|
1005 | buf = g_strconcat ("Error renaming file \"", file, "\": ", |
---|
1006 | g_strerror(errno), NULL); |
---|
1007 | gtk_file_selection_fileop_error (fs, buf); |
---|
1008 | } |
---|
1009 | g_free (new_filename); |
---|
1010 | g_free (old_filename); |
---|
1011 | |
---|
1012 | gtk_widget_destroy (fs->fileop_dialog); |
---|
1013 | gtk_file_selection_populate (fs, "", FALSE); |
---|
1014 | } |
---|
1015 | |
---|
1016 | static void |
---|
1017 | gtk_file_selection_rename_file (GtkWidget *widget, gpointer data) |
---|
1018 | { |
---|
1019 | GtkFileSelection *fs = data; |
---|
1020 | GtkWidget *label; |
---|
1021 | GtkWidget *dialog; |
---|
1022 | GtkWidget *vbox; |
---|
1023 | GtkWidget *button; |
---|
1024 | gchar *buf; |
---|
1025 | |
---|
1026 | g_return_if_fail (fs != NULL); |
---|
1027 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1028 | |
---|
1029 | if (fs->fileop_dialog) |
---|
1030 | return; |
---|
1031 | |
---|
1032 | fs->fileop_file = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); |
---|
1033 | if (strlen(fs->fileop_file) < 1) |
---|
1034 | return; |
---|
1035 | |
---|
1036 | /* main dialog */ |
---|
1037 | fs->fileop_dialog = dialog = gtk_dialog_new (); |
---|
1038 | gtk_signal_connect (GTK_OBJECT (dialog), "destroy", |
---|
1039 | (GtkSignalFunc) gtk_file_selection_fileop_destroy, |
---|
1040 | (gpointer) fs); |
---|
1041 | gtk_window_set_title (GTK_WINDOW (dialog), _("Rename File")); |
---|
1042 | gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); |
---|
1043 | |
---|
1044 | /* If file dialog is grabbed, grab option dialog */ |
---|
1045 | /* When option dialog closed, file dialog will be grabbed again */ |
---|
1046 | if (GTK_WINDOW(fs)->modal) |
---|
1047 | gtk_window_set_modal (GTK_WINDOW(dialog), TRUE); |
---|
1048 | |
---|
1049 | vbox = gtk_vbox_new(FALSE, 0); |
---|
1050 | gtk_container_set_border_width (GTK_CONTAINER(vbox), 8); |
---|
1051 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), vbox, |
---|
1052 | FALSE, FALSE, 0); |
---|
1053 | gtk_widget_show(vbox); |
---|
1054 | |
---|
1055 | buf = g_strconcat ("Rename file \"", fs->fileop_file, "\" to:", NULL); |
---|
1056 | label = gtk_label_new(buf); |
---|
1057 | gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); |
---|
1058 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5); |
---|
1059 | gtk_widget_show(label); |
---|
1060 | g_free(buf); |
---|
1061 | |
---|
1062 | /* New filename entry */ |
---|
1063 | fs->fileop_entry = gtk_entry_new (); |
---|
1064 | gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, |
---|
1065 | TRUE, TRUE, 5); |
---|
1066 | GTK_WIDGET_SET_FLAGS(fs->fileop_entry, GTK_CAN_DEFAULT); |
---|
1067 | gtk_widget_show (fs->fileop_entry); |
---|
1068 | |
---|
1069 | gtk_entry_set_text (GTK_ENTRY (fs->fileop_entry), fs->fileop_file); |
---|
1070 | gtk_editable_select_region (GTK_EDITABLE (fs->fileop_entry), |
---|
1071 | 0, strlen (fs->fileop_file)); |
---|
1072 | |
---|
1073 | /* buttons */ |
---|
1074 | button = gtk_button_new_with_label (_("Rename")); |
---|
1075 | gtk_signal_connect (GTK_OBJECT (button), "clicked", |
---|
1076 | (GtkSignalFunc) gtk_file_selection_rename_file_confirmed, |
---|
1077 | (gpointer) fs); |
---|
1078 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
1079 | button, TRUE, TRUE, 0); |
---|
1080 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
1081 | gtk_widget_show(button); |
---|
1082 | |
---|
1083 | button = gtk_button_new_with_label (_("Cancel")); |
---|
1084 | gtk_signal_connect_object (GTK_OBJECT (button), "clicked", |
---|
1085 | (GtkSignalFunc) gtk_widget_destroy, |
---|
1086 | (gpointer) dialog); |
---|
1087 | gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), |
---|
1088 | button, TRUE, TRUE, 0); |
---|
1089 | GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); |
---|
1090 | gtk_widget_grab_default(button); |
---|
1091 | gtk_widget_show (button); |
---|
1092 | |
---|
1093 | gtk_widget_show (dialog); |
---|
1094 | } |
---|
1095 | |
---|
1096 | |
---|
1097 | static gint |
---|
1098 | gtk_file_selection_key_press (GtkWidget *widget, |
---|
1099 | GdkEventKey *event, |
---|
1100 | gpointer user_data) |
---|
1101 | { |
---|
1102 | GtkFileSelection *fs; |
---|
1103 | char *text; |
---|
1104 | |
---|
1105 | g_return_val_if_fail (widget != NULL, FALSE); |
---|
1106 | g_return_val_if_fail (event != NULL, FALSE); |
---|
1107 | |
---|
1108 | if (event->keyval == GDK_Tab) |
---|
1109 | { |
---|
1110 | fs = GTK_FILE_SELECTION (user_data); |
---|
1111 | text = gtk_entry_get_text (GTK_ENTRY (fs->selection_entry)); |
---|
1112 | |
---|
1113 | text = g_strdup (text); |
---|
1114 | |
---|
1115 | gtk_file_selection_populate (fs, text, TRUE); |
---|
1116 | |
---|
1117 | g_free (text); |
---|
1118 | |
---|
1119 | gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event"); |
---|
1120 | |
---|
1121 | return TRUE; |
---|
1122 | } |
---|
1123 | |
---|
1124 | return FALSE; |
---|
1125 | } |
---|
1126 | |
---|
1127 | |
---|
1128 | static void |
---|
1129 | gtk_file_selection_history_callback (GtkWidget *widget, gpointer data) |
---|
1130 | { |
---|
1131 | GtkFileSelection *fs = data; |
---|
1132 | HistoryCallbackArg *callback_arg; |
---|
1133 | GList *list; |
---|
1134 | |
---|
1135 | g_return_if_fail (fs != NULL); |
---|
1136 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1137 | |
---|
1138 | list = fs->history_list; |
---|
1139 | |
---|
1140 | while (list) { |
---|
1141 | callback_arg = list->data; |
---|
1142 | |
---|
1143 | if (callback_arg->menu_item == widget) |
---|
1144 | { |
---|
1145 | gtk_file_selection_populate (fs, callback_arg->directory, FALSE); |
---|
1146 | break; |
---|
1147 | } |
---|
1148 | |
---|
1149 | list = list->next; |
---|
1150 | } |
---|
1151 | } |
---|
1152 | |
---|
1153 | static void |
---|
1154 | gtk_file_selection_update_history_menu (GtkFileSelection *fs, |
---|
1155 | gchar *current_directory) |
---|
1156 | { |
---|
1157 | HistoryCallbackArg *callback_arg; |
---|
1158 | GtkWidget *menu_item; |
---|
1159 | GList *list; |
---|
1160 | gchar *current_dir; |
---|
1161 | gint dir_len; |
---|
1162 | gint i; |
---|
1163 | |
---|
1164 | g_return_if_fail (fs != NULL); |
---|
1165 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1166 | g_return_if_fail (current_directory != NULL); |
---|
1167 | |
---|
1168 | list = fs->history_list; |
---|
1169 | |
---|
1170 | if (fs->history_menu) |
---|
1171 | { |
---|
1172 | while (list) { |
---|
1173 | callback_arg = list->data; |
---|
1174 | g_free (callback_arg->directory); |
---|
1175 | g_free (callback_arg); |
---|
1176 | list = list->next; |
---|
1177 | } |
---|
1178 | g_list_free (fs->history_list); |
---|
1179 | fs->history_list = NULL; |
---|
1180 | |
---|
1181 | gtk_widget_destroy (fs->history_menu); |
---|
1182 | } |
---|
1183 | |
---|
1184 | fs->history_menu = gtk_menu_new(); |
---|
1185 | |
---|
1186 | current_dir = g_strdup (current_directory); |
---|
1187 | |
---|
1188 | dir_len = strlen (current_dir); |
---|
1189 | |
---|
1190 | for (i = dir_len; i >= 0; i--) |
---|
1191 | { |
---|
1192 | /* the i == dir_len is to catch the full path for the first |
---|
1193 | * entry. */ |
---|
1194 | if ( (current_dir[i] == '/') || (i == dir_len)) |
---|
1195 | { |
---|
1196 | /* another small hack to catch the full path */ |
---|
1197 | if (i != dir_len) |
---|
1198 | current_dir[i + 1] = '\0'; |
---|
1199 | menu_item = gtk_menu_item_new_with_label (current_dir); |
---|
1200 | |
---|
1201 | callback_arg = g_new (HistoryCallbackArg, 1); |
---|
1202 | callback_arg->menu_item = menu_item; |
---|
1203 | |
---|
1204 | /* since the autocompletion gets confused if you don't |
---|
1205 | * supply a trailing '/' on a dir entry, set the full |
---|
1206 | * (current) path to "" which just refreshes the filesel */ |
---|
1207 | if (dir_len == i) { |
---|
1208 | callback_arg->directory = g_strdup (""); |
---|
1209 | } else { |
---|
1210 | callback_arg->directory = g_strdup (current_dir); |
---|
1211 | } |
---|
1212 | |
---|
1213 | fs->history_list = g_list_append (fs->history_list, callback_arg); |
---|
1214 | |
---|
1215 | gtk_signal_connect (GTK_OBJECT (menu_item), "activate", |
---|
1216 | (GtkSignalFunc) gtk_file_selection_history_callback, |
---|
1217 | (gpointer) fs); |
---|
1218 | gtk_menu_append (GTK_MENU (fs->history_menu), menu_item); |
---|
1219 | gtk_widget_show (menu_item); |
---|
1220 | } |
---|
1221 | } |
---|
1222 | |
---|
1223 | gtk_option_menu_set_menu (GTK_OPTION_MENU (fs->history_pulldown), |
---|
1224 | fs->history_menu); |
---|
1225 | g_free (current_dir); |
---|
1226 | } |
---|
1227 | |
---|
1228 | static void |
---|
1229 | gtk_file_selection_file_button (GtkWidget *widget, |
---|
1230 | gint row, |
---|
1231 | gint column, |
---|
1232 | GdkEventButton *bevent, |
---|
1233 | gpointer user_data) |
---|
1234 | { |
---|
1235 | GtkFileSelection *fs = NULL; |
---|
1236 | gchar *filename, *temp = NULL; |
---|
1237 | |
---|
1238 | g_return_if_fail (GTK_IS_CLIST (widget)); |
---|
1239 | |
---|
1240 | fs = user_data; |
---|
1241 | g_return_if_fail (fs != NULL); |
---|
1242 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1243 | |
---|
1244 | gtk_clist_get_text (GTK_CLIST (fs->file_list), row, 0, &temp); |
---|
1245 | filename = g_strdup (temp); |
---|
1246 | |
---|
1247 | if (filename) |
---|
1248 | { |
---|
1249 | if (bevent) |
---|
1250 | switch (bevent->type) |
---|
1251 | { |
---|
1252 | case GDK_2BUTTON_PRESS: |
---|
1253 | gtk_button_clicked (GTK_BUTTON (fs->ok_button)); |
---|
1254 | break; |
---|
1255 | |
---|
1256 | default: |
---|
1257 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); |
---|
1258 | break; |
---|
1259 | } |
---|
1260 | else |
---|
1261 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); |
---|
1262 | |
---|
1263 | g_free (filename); |
---|
1264 | } |
---|
1265 | } |
---|
1266 | |
---|
1267 | static void |
---|
1268 | gtk_file_selection_dir_button (GtkWidget *widget, |
---|
1269 | gint row, |
---|
1270 | gint column, |
---|
1271 | GdkEventButton *bevent, |
---|
1272 | gpointer user_data) |
---|
1273 | { |
---|
1274 | GtkFileSelection *fs = NULL; |
---|
1275 | gchar *filename, *temp = NULL; |
---|
1276 | |
---|
1277 | g_return_if_fail (GTK_IS_CLIST (widget)); |
---|
1278 | |
---|
1279 | fs = GTK_FILE_SELECTION (user_data); |
---|
1280 | g_return_if_fail (fs != NULL); |
---|
1281 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1282 | |
---|
1283 | gtk_clist_get_text (GTK_CLIST (fs->dir_list), row, 0, &temp); |
---|
1284 | filename = g_strdup (temp); |
---|
1285 | |
---|
1286 | if (filename) |
---|
1287 | { |
---|
1288 | if (bevent) |
---|
1289 | switch (bevent->type) |
---|
1290 | { |
---|
1291 | case GDK_2BUTTON_PRESS: |
---|
1292 | gtk_file_selection_populate (fs, filename, FALSE); |
---|
1293 | break; |
---|
1294 | |
---|
1295 | default: |
---|
1296 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); |
---|
1297 | break; |
---|
1298 | } |
---|
1299 | else |
---|
1300 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), filename); |
---|
1301 | |
---|
1302 | g_free (filename); |
---|
1303 | } |
---|
1304 | } |
---|
1305 | |
---|
1306 | static void |
---|
1307 | gtk_file_selection_populate (GtkFileSelection *fs, |
---|
1308 | gchar *rel_path, |
---|
1309 | gint try_complete) |
---|
1310 | { |
---|
1311 | CompletionState *cmpl_state; |
---|
1312 | PossibleCompletion* poss; |
---|
1313 | gchar* filename; |
---|
1314 | gint row; |
---|
1315 | gchar* rem_path = rel_path; |
---|
1316 | gchar* sel_text; |
---|
1317 | gchar* text[2]; |
---|
1318 | gint did_recurse = FALSE; |
---|
1319 | gint possible_count = 0; |
---|
1320 | gint selection_index = -1; |
---|
1321 | gint file_list_width; |
---|
1322 | gint dir_list_width; |
---|
1323 | |
---|
1324 | g_return_if_fail (fs != NULL); |
---|
1325 | g_return_if_fail (GTK_IS_FILE_SELECTION (fs)); |
---|
1326 | |
---|
1327 | cmpl_state = (CompletionState*) fs->cmpl_state; |
---|
1328 | poss = cmpl_completion_matches (rel_path, &rem_path, cmpl_state); |
---|
1329 | |
---|
1330 | if (!cmpl_state_okay (cmpl_state)) |
---|
1331 | { |
---|
1332 | /* Something went wrong. */ |
---|
1333 | gtk_file_selection_abort (fs); |
---|
1334 | return; |
---|
1335 | } |
---|
1336 | |
---|
1337 | g_assert (cmpl_state->reference_dir); |
---|
1338 | |
---|
1339 | gtk_clist_freeze (GTK_CLIST (fs->dir_list)); |
---|
1340 | gtk_clist_clear (GTK_CLIST (fs->dir_list)); |
---|
1341 | gtk_clist_freeze (GTK_CLIST (fs->file_list)); |
---|
1342 | gtk_clist_clear (GTK_CLIST (fs->file_list)); |
---|
1343 | |
---|
1344 | /* Set the dir_list to include ./ and ../ */ |
---|
1345 | text[1] = NULL; |
---|
1346 | text[0] = "./"; |
---|
1347 | row = gtk_clist_append (GTK_CLIST (fs->dir_list), text); |
---|
1348 | |
---|
1349 | text[0] = "../"; |
---|
1350 | row = gtk_clist_append (GTK_CLIST (fs->dir_list), text); |
---|
1351 | |
---|
1352 | /*reset the max widths of the lists*/ |
---|
1353 | dir_list_width = gdk_string_width(fs->dir_list->style->font,"../"); |
---|
1354 | gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0,dir_list_width); |
---|
1355 | file_list_width = 1; |
---|
1356 | gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0,file_list_width); |
---|
1357 | |
---|
1358 | while (poss) |
---|
1359 | { |
---|
1360 | if (cmpl_is_a_completion (poss)) |
---|
1361 | { |
---|
1362 | possible_count += 1; |
---|
1363 | |
---|
1364 | filename = cmpl_this_completion (poss); |
---|
1365 | |
---|
1366 | text[0] = filename; |
---|
1367 | |
---|
1368 | if (cmpl_is_directory (poss)) |
---|
1369 | { |
---|
1370 | if (strcmp (filename, "./") != 0 && |
---|
1371 | strcmp (filename, "../") != 0) |
---|
1372 | { |
---|
1373 | int width = gdk_string_width(fs->dir_list->style->font, |
---|
1374 | filename); |
---|
1375 | row = gtk_clist_append (GTK_CLIST (fs->dir_list), text); |
---|
1376 | if(width > dir_list_width) |
---|
1377 | { |
---|
1378 | dir_list_width = width; |
---|
1379 | gtk_clist_set_column_width(GTK_CLIST(fs->dir_list),0, |
---|
1380 | width); |
---|
1381 | } |
---|
1382 | } |
---|
1383 | } |
---|
1384 | else |
---|
1385 | { |
---|
1386 | int width = gdk_string_width(fs->file_list->style->font, |
---|
1387 | filename); |
---|
1388 | row = gtk_clist_append (GTK_CLIST (fs->file_list), text); |
---|
1389 | if(width > file_list_width) |
---|
1390 | { |
---|
1391 | file_list_width = width; |
---|
1392 | gtk_clist_set_column_width(GTK_CLIST(fs->file_list),0, |
---|
1393 | width); |
---|
1394 | } |
---|
1395 | } |
---|
1396 | } |
---|
1397 | |
---|
1398 | poss = cmpl_next_completion (cmpl_state); |
---|
1399 | } |
---|
1400 | |
---|
1401 | gtk_clist_thaw (GTK_CLIST (fs->dir_list)); |
---|
1402 | gtk_clist_thaw (GTK_CLIST (fs->file_list)); |
---|
1403 | |
---|
1404 | /* File lists are set. */ |
---|
1405 | |
---|
1406 | g_assert (cmpl_state->reference_dir); |
---|
1407 | |
---|
1408 | if (try_complete) |
---|
1409 | { |
---|
1410 | |
---|
1411 | /* User is trying to complete filenames, so advance the user's input |
---|
1412 | * string to the updated_text, which is the common leading substring |
---|
1413 | * of all possible completions, and if its a directory attempt |
---|
1414 | * attempt completions in it. */ |
---|
1415 | |
---|
1416 | if (cmpl_updated_text (cmpl_state)[0]) |
---|
1417 | { |
---|
1418 | |
---|
1419 | if (cmpl_updated_dir (cmpl_state)) |
---|
1420 | { |
---|
1421 | gchar* dir_name = g_strdup (cmpl_updated_text (cmpl_state)); |
---|
1422 | |
---|
1423 | did_recurse = TRUE; |
---|
1424 | |
---|
1425 | gtk_file_selection_populate (fs, dir_name, TRUE); |
---|
1426 | |
---|
1427 | g_free (dir_name); |
---|
1428 | } |
---|
1429 | else |
---|
1430 | { |
---|
1431 | if (fs->selection_entry) |
---|
1432 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), |
---|
1433 | cmpl_updated_text (cmpl_state)); |
---|
1434 | } |
---|
1435 | } |
---|
1436 | else |
---|
1437 | { |
---|
1438 | selection_index = cmpl_last_valid_char (cmpl_state) - |
---|
1439 | (strlen (rel_path) - strlen (rem_path)); |
---|
1440 | if (fs->selection_entry) |
---|
1441 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), rem_path); |
---|
1442 | } |
---|
1443 | } |
---|
1444 | else |
---|
1445 | { |
---|
1446 | if (fs->selection_entry) |
---|
1447 | gtk_entry_set_text (GTK_ENTRY (fs->selection_entry), ""); |
---|
1448 | } |
---|
1449 | |
---|
1450 | if (!did_recurse) |
---|
1451 | { |
---|
1452 | if (fs->selection_entry) |
---|
1453 | gtk_entry_set_position (GTK_ENTRY (fs->selection_entry), selection_index); |
---|
1454 | |
---|
1455 | if (fs->selection_entry) |
---|
1456 | { |
---|
1457 | sel_text = g_strconcat (_("Selection: "), |
---|
1458 | cmpl_reference_position (cmpl_state), |
---|
1459 | NULL); |
---|
1460 | |
---|
1461 | gtk_label_set_text (GTK_LABEL (fs->selection_text), sel_text); |
---|
1462 | g_free (sel_text); |
---|
1463 | } |
---|
1464 | |
---|
1465 | if (fs->history_pulldown) |
---|
1466 | { |
---|
1467 | gtk_file_selection_update_history_menu (fs, cmpl_reference_position (cmpl_state)); |
---|
1468 | } |
---|
1469 | |
---|
1470 | } |
---|
1471 | } |
---|
1472 | |
---|
1473 | static void |
---|
1474 | gtk_file_selection_abort (GtkFileSelection *fs) |
---|
1475 | { |
---|
1476 | gchar err_buf[256]; |
---|
1477 | |
---|
1478 | sprintf (err_buf, _("Directory unreadable: %s"), cmpl_strerror (cmpl_errno)); |
---|
1479 | |
---|
1480 | /* BEEP gdk_beep(); */ |
---|
1481 | |
---|
1482 | if (fs->selection_entry) |
---|
1483 | gtk_label_set_text (GTK_LABEL (fs->selection_text), err_buf); |
---|
1484 | } |
---|
1485 | |
---|
1486 | /**********************************************************************/ |
---|
1487 | /* External Interface */ |
---|
1488 | /**********************************************************************/ |
---|
1489 | |
---|
1490 | /* The four completion state selectors |
---|
1491 | */ |
---|
1492 | static gchar* |
---|
1493 | cmpl_updated_text (CompletionState* cmpl_state) |
---|
1494 | { |
---|
1495 | return cmpl_state->updated_text; |
---|
1496 | } |
---|
1497 | |
---|
1498 | static gint |
---|
1499 | cmpl_updated_dir (CompletionState* cmpl_state) |
---|
1500 | { |
---|
1501 | return cmpl_state->re_complete; |
---|
1502 | } |
---|
1503 | |
---|
1504 | static gchar* |
---|
1505 | cmpl_reference_position (CompletionState* cmpl_state) |
---|
1506 | { |
---|
1507 | return cmpl_state->reference_dir->fullname; |
---|
1508 | } |
---|
1509 | |
---|
1510 | static gint |
---|
1511 | cmpl_last_valid_char (CompletionState* cmpl_state) |
---|
1512 | { |
---|
1513 | return cmpl_state->last_valid_char; |
---|
1514 | } |
---|
1515 | |
---|
1516 | static gchar* |
---|
1517 | cmpl_completion_fullname (gchar* text, CompletionState* cmpl_state) |
---|
1518 | { |
---|
1519 | static char nothing[2] = ""; |
---|
1520 | |
---|
1521 | if (!cmpl_state_okay (cmpl_state)) |
---|
1522 | { |
---|
1523 | return nothing; |
---|
1524 | } |
---|
1525 | else if (text[0] == '/') |
---|
1526 | { |
---|
1527 | strcpy (cmpl_state->updated_text, text); |
---|
1528 | } |
---|
1529 | else if (text[0] == '~') |
---|
1530 | { |
---|
1531 | CompletionDir* dir; |
---|
1532 | char* slash; |
---|
1533 | |
---|
1534 | dir = open_user_dir (text, cmpl_state); |
---|
1535 | |
---|
1536 | if (!dir) |
---|
1537 | { |
---|
1538 | /* spencer says just return ~something, so |
---|
1539 | * for now just do it. */ |
---|
1540 | strcpy (cmpl_state->updated_text, text); |
---|
1541 | } |
---|
1542 | else |
---|
1543 | { |
---|
1544 | |
---|
1545 | strcpy (cmpl_state->updated_text, dir->fullname); |
---|
1546 | |
---|
1547 | slash = strchr (text, '/'); |
---|
1548 | |
---|
1549 | if (slash) |
---|
1550 | strcat (cmpl_state->updated_text, slash); |
---|
1551 | } |
---|
1552 | } |
---|
1553 | else |
---|
1554 | { |
---|
1555 | strcpy (cmpl_state->updated_text, cmpl_state->reference_dir->fullname); |
---|
1556 | if (strcmp (cmpl_state->reference_dir->fullname, "/") != 0) |
---|
1557 | strcat (cmpl_state->updated_text, "/"); |
---|
1558 | strcat (cmpl_state->updated_text, text); |
---|
1559 | } |
---|
1560 | |
---|
1561 | return cmpl_state->updated_text; |
---|
1562 | } |
---|
1563 | |
---|
1564 | /* The three completion selectors |
---|
1565 | */ |
---|
1566 | static gchar* |
---|
1567 | cmpl_this_completion (PossibleCompletion* pc) |
---|
1568 | { |
---|
1569 | return pc->text; |
---|
1570 | } |
---|
1571 | |
---|
1572 | static gint |
---|
1573 | cmpl_is_directory (PossibleCompletion* pc) |
---|
1574 | { |
---|
1575 | return pc->is_directory; |
---|
1576 | } |
---|
1577 | |
---|
1578 | static gint |
---|
1579 | cmpl_is_a_completion (PossibleCompletion* pc) |
---|
1580 | { |
---|
1581 | return pc->is_a_completion; |
---|
1582 | } |
---|
1583 | |
---|
1584 | /**********************************************************************/ |
---|
1585 | /* Construction, deletion */ |
---|
1586 | /**********************************************************************/ |
---|
1587 | |
---|
1588 | static CompletionState* |
---|
1589 | cmpl_init_state (void) |
---|
1590 | { |
---|
1591 | gchar getcwd_buf[2*MAXPATHLEN]; |
---|
1592 | CompletionState *new_state; |
---|
1593 | |
---|
1594 | new_state = g_new (CompletionState, 1); |
---|
1595 | |
---|
1596 | /* We don't use getcwd() on SUNOS, because, it does a popen("pwd") |
---|
1597 | * and, if that wasn't bad enough, hangs in doing so. |
---|
1598 | */ |
---|
1599 | #if defined(sun) && !defined(__SVR4) |
---|
1600 | if (!getwd (getcwd_buf)) |
---|
1601 | #else |
---|
1602 | if (!getcwd (getcwd_buf, MAXPATHLEN)) |
---|
1603 | #endif |
---|
1604 | { |
---|
1605 | /* Oh joy, we can't get the current directory. Um..., we should have |
---|
1606 | * a root directory, right? Right? (Probably not portable to non-Unix) |
---|
1607 | */ |
---|
1608 | strcpy (getcwd_buf, "/"); |
---|
1609 | } |
---|
1610 | |
---|
1611 | tryagain: |
---|
1612 | |
---|
1613 | new_state->reference_dir = NULL; |
---|
1614 | new_state->completion_dir = NULL; |
---|
1615 | new_state->active_completion_dir = NULL; |
---|
1616 | new_state->directory_storage = NULL; |
---|
1617 | new_state->directory_sent_storage = NULL; |
---|
1618 | new_state->last_valid_char = 0; |
---|
1619 | new_state->updated_text = g_new (gchar, MAXPATHLEN); |
---|
1620 | new_state->updated_text_alloc = MAXPATHLEN; |
---|
1621 | new_state->the_completion.text = g_new (gchar, MAXPATHLEN); |
---|
1622 | new_state->the_completion.text_alloc = MAXPATHLEN; |
---|
1623 | new_state->user_dir_name_buffer = NULL; |
---|
1624 | new_state->user_directories = NULL; |
---|
1625 | |
---|
1626 | new_state->reference_dir = open_dir (getcwd_buf, new_state); |
---|
1627 | |
---|
1628 | if (!new_state->reference_dir) |
---|
1629 | { |
---|
1630 | /* Directories changing from underneath us, grumble */ |
---|
1631 | strcpy (getcwd_buf, "/"); |
---|
1632 | goto tryagain; |
---|
1633 | } |
---|
1634 | |
---|
1635 | return new_state; |
---|
1636 | } |
---|
1637 | |
---|
1638 | static void |
---|
1639 | cmpl_free_dir_list(GList* dp0) |
---|
1640 | { |
---|
1641 | GList *dp = dp0; |
---|
1642 | |
---|
1643 | while (dp) { |
---|
1644 | free_dir (dp->data); |
---|
1645 | dp = dp->next; |
---|
1646 | } |
---|
1647 | |
---|
1648 | g_list_free(dp0); |
---|
1649 | } |
---|
1650 | |
---|
1651 | static void |
---|
1652 | cmpl_free_dir_sent_list(GList* dp0) |
---|
1653 | { |
---|
1654 | GList *dp = dp0; |
---|
1655 | |
---|
1656 | while (dp) { |
---|
1657 | free_dir_sent (dp->data); |
---|
1658 | dp = dp->next; |
---|
1659 | } |
---|
1660 | |
---|
1661 | g_list_free(dp0); |
---|
1662 | } |
---|
1663 | |
---|
1664 | static void |
---|
1665 | cmpl_free_state (CompletionState* cmpl_state) |
---|
1666 | { |
---|
1667 | cmpl_free_dir_list (cmpl_state->directory_storage); |
---|
1668 | cmpl_free_dir_sent_list (cmpl_state->directory_sent_storage); |
---|
1669 | |
---|
1670 | if (cmpl_state->user_dir_name_buffer) |
---|
1671 | g_free (cmpl_state->user_dir_name_buffer); |
---|
1672 | if (cmpl_state->user_directories) |
---|
1673 | g_free (cmpl_state->user_directories); |
---|
1674 | if (cmpl_state->the_completion.text) |
---|
1675 | g_free (cmpl_state->the_completion.text); |
---|
1676 | if (cmpl_state->updated_text) |
---|
1677 | g_free (cmpl_state->updated_text); |
---|
1678 | |
---|
1679 | g_free (cmpl_state); |
---|
1680 | } |
---|
1681 | |
---|
1682 | static void |
---|
1683 | free_dir(CompletionDir* dir) |
---|
1684 | { |
---|
1685 | g_free(dir->fullname); |
---|
1686 | g_free(dir); |
---|
1687 | } |
---|
1688 | |
---|
1689 | static void |
---|
1690 | free_dir_sent(CompletionDirSent* sent) |
---|
1691 | { |
---|
1692 | g_free(sent->name_buffer); |
---|
1693 | g_free(sent->entries); |
---|
1694 | g_free(sent); |
---|
1695 | } |
---|
1696 | |
---|
1697 | static void |
---|
1698 | prune_memory_usage(CompletionState *cmpl_state) |
---|
1699 | { |
---|
1700 | GList* cdsl = cmpl_state->directory_sent_storage; |
---|
1701 | GList* cdl = cmpl_state->directory_storage; |
---|
1702 | GList* cdl0 = cdl; |
---|
1703 | gint len = 0; |
---|
1704 | |
---|
1705 | for(; cdsl && len < CMPL_DIRECTORY_CACHE_SIZE; len += 1) |
---|
1706 | cdsl = cdsl->next; |
---|
1707 | |
---|
1708 | if (cdsl) { |
---|
1709 | cmpl_free_dir_sent_list(cdsl->next); |
---|
1710 | cdsl->next = NULL; |
---|
1711 | } |
---|
1712 | |
---|
1713 | cmpl_state->directory_storage = NULL; |
---|
1714 | while (cdl) { |
---|
1715 | if (cdl->data == cmpl_state->reference_dir) |
---|
1716 | cmpl_state->directory_storage = g_list_prepend(NULL, cdl->data); |
---|
1717 | else |
---|
1718 | free_dir (cdl->data); |
---|
1719 | cdl = cdl->next; |
---|
1720 | } |
---|
1721 | |
---|
1722 | g_list_free(cdl0); |
---|
1723 | } |
---|
1724 | |
---|
1725 | /**********************************************************************/ |
---|
1726 | /* The main entrances. */ |
---|
1727 | /**********************************************************************/ |
---|
1728 | |
---|
1729 | static PossibleCompletion* |
---|
1730 | cmpl_completion_matches (gchar* text_to_complete, |
---|
1731 | gchar** remaining_text, |
---|
1732 | CompletionState* cmpl_state) |
---|
1733 | { |
---|
1734 | gchar* first_slash; |
---|
1735 | PossibleCompletion *poss; |
---|
1736 | |
---|
1737 | prune_memory_usage(cmpl_state); |
---|
1738 | |
---|
1739 | g_assert (text_to_complete != NULL); |
---|
1740 | |
---|
1741 | cmpl_state->user_completion_index = -1; |
---|
1742 | cmpl_state->last_completion_text = text_to_complete; |
---|
1743 | cmpl_state->the_completion.text[0] = 0; |
---|
1744 | cmpl_state->last_valid_char = 0; |
---|
1745 | cmpl_state->updated_text_len = -1; |
---|
1746 | cmpl_state->updated_text[0] = 0; |
---|
1747 | cmpl_state->re_complete = FALSE; |
---|
1748 | |
---|
1749 | first_slash = strchr (text_to_complete, '/'); |
---|
1750 | |
---|
1751 | if (text_to_complete[0] == '~' && !first_slash) |
---|
1752 | { |
---|
1753 | /* Text starts with ~ and there is no slash, show all the |
---|
1754 | * home directory completions. |
---|
1755 | */ |
---|
1756 | poss = attempt_homedir_completion (text_to_complete, cmpl_state); |
---|
1757 | |
---|
1758 | update_cmpl(poss, cmpl_state); |
---|
1759 | |
---|
1760 | return poss; |
---|
1761 | } |
---|
1762 | |
---|
1763 | cmpl_state->reference_dir = |
---|
1764 | open_ref_dir (text_to_complete, remaining_text, cmpl_state); |
---|
1765 | |
---|
1766 | if(!cmpl_state->reference_dir) |
---|
1767 | return NULL; |
---|
1768 | |
---|
1769 | cmpl_state->completion_dir = |
---|
1770 | find_completion_dir (*remaining_text, remaining_text, cmpl_state); |
---|
1771 | |
---|
1772 | cmpl_state->last_valid_char = *remaining_text - text_to_complete; |
---|
1773 | |
---|
1774 | if(!cmpl_state->completion_dir) |
---|
1775 | return NULL; |
---|
1776 | |
---|
1777 | cmpl_state->completion_dir->cmpl_index = -1; |
---|
1778 | cmpl_state->completion_dir->cmpl_parent = NULL; |
---|
1779 | cmpl_state->completion_dir->cmpl_text = *remaining_text; |
---|
1780 | |
---|
1781 | cmpl_state->active_completion_dir = cmpl_state->completion_dir; |
---|
1782 | |
---|
1783 | cmpl_state->reference_dir = cmpl_state->completion_dir; |
---|
1784 | |
---|
1785 | poss = attempt_file_completion(cmpl_state); |
---|
1786 | |
---|
1787 | update_cmpl(poss, cmpl_state); |
---|
1788 | |
---|
1789 | return poss; |
---|
1790 | } |
---|
1791 | |
---|
1792 | static PossibleCompletion* |
---|
1793 | cmpl_next_completion (CompletionState* cmpl_state) |
---|
1794 | { |
---|
1795 | PossibleCompletion* poss = NULL; |
---|
1796 | |
---|
1797 | cmpl_state->the_completion.text[0] = 0; |
---|
1798 | |
---|
1799 | if(cmpl_state->user_completion_index >= 0) |
---|
1800 | poss = attempt_homedir_completion(cmpl_state->last_completion_text, cmpl_state); |
---|
1801 | else |
---|
1802 | poss = attempt_file_completion(cmpl_state); |
---|
1803 | |
---|
1804 | update_cmpl(poss, cmpl_state); |
---|
1805 | |
---|
1806 | return poss; |
---|
1807 | } |
---|
1808 | |
---|
1809 | /**********************************************************************/ |
---|
1810 | /* Directory Operations */ |
---|
1811 | /**********************************************************************/ |
---|
1812 | |
---|
1813 | /* Open the directory where completion will begin from, if possible. */ |
---|
1814 | static CompletionDir* |
---|
1815 | open_ref_dir(gchar* text_to_complete, |
---|
1816 | gchar** remaining_text, |
---|
1817 | CompletionState* cmpl_state) |
---|
1818 | { |
---|
1819 | gchar* first_slash; |
---|
1820 | CompletionDir *new_dir; |
---|
1821 | |
---|
1822 | first_slash = strchr(text_to_complete, '/'); |
---|
1823 | |
---|
1824 | if (text_to_complete[0] == '~') |
---|
1825 | { |
---|
1826 | new_dir = open_user_dir(text_to_complete, cmpl_state); |
---|
1827 | |
---|
1828 | if(new_dir) |
---|
1829 | { |
---|
1830 | if(first_slash) |
---|
1831 | *remaining_text = first_slash + 1; |
---|
1832 | else |
---|
1833 | *remaining_text = text_to_complete + strlen(text_to_complete); |
---|
1834 | } |
---|
1835 | else |
---|
1836 | { |
---|
1837 | return NULL; |
---|
1838 | } |
---|
1839 | } |
---|
1840 | else if (text_to_complete[0] == '/' || !cmpl_state->reference_dir) |
---|
1841 | { |
---|
1842 | gchar *tmp = g_strdup(text_to_complete); |
---|
1843 | gchar *p; |
---|
1844 | |
---|
1845 | p = tmp; |
---|
1846 | while (*p && *p != '*' && *p != '?') |
---|
1847 | p++; |
---|
1848 | |
---|
1849 | *p = '\0'; |
---|
1850 | p = strrchr(tmp, '/'); |
---|
1851 | if (p) |
---|
1852 | { |
---|
1853 | if (p == tmp) |
---|
1854 | p++; |
---|
1855 | |
---|
1856 | *p = '\0'; |
---|
1857 | |
---|
1858 | new_dir = open_dir(tmp, cmpl_state); |
---|
1859 | |
---|
1860 | if(new_dir) |
---|
1861 | *remaining_text = text_to_complete + |
---|
1862 | ((p == tmp + 1) ? (p - tmp) : (p + 1 - tmp)); |
---|
1863 | } |
---|
1864 | else |
---|
1865 | { |
---|
1866 | /* If no possible candidates, use the cwd */ |
---|
1867 | gchar *curdir = g_get_current_dir (); |
---|
1868 | |
---|
1869 | new_dir = open_dir(curdir, cmpl_state); |
---|
1870 | |
---|
1871 | if (new_dir) |
---|
1872 | *remaining_text = text_to_complete; |
---|
1873 | |
---|
1874 | g_free (curdir); |
---|
1875 | } |
---|
1876 | |
---|
1877 | g_free (tmp); |
---|
1878 | } |
---|
1879 | else |
---|
1880 | { |
---|
1881 | *remaining_text = text_to_complete; |
---|
1882 | |
---|
1883 | new_dir = open_dir(cmpl_state->reference_dir->fullname, cmpl_state); |
---|
1884 | } |
---|
1885 | |
---|
1886 | if(new_dir) |
---|
1887 | { |
---|
1888 | new_dir->cmpl_index = -1; |
---|
1889 | new_dir->cmpl_parent = NULL; |
---|
1890 | } |
---|
1891 | |
---|
1892 | return new_dir; |
---|
1893 | } |
---|
1894 | |
---|
1895 | /* open a directory by user name */ |
---|
1896 | static CompletionDir* |
---|
1897 | open_user_dir(gchar* text_to_complete, |
---|
1898 | CompletionState *cmpl_state) |
---|
1899 | { |
---|
1900 | gchar *first_slash; |
---|
1901 | gint cmp_len; |
---|
1902 | |
---|
1903 | g_assert(text_to_complete && text_to_complete[0] == '~'); |
---|
1904 | |
---|
1905 | first_slash = strchr(text_to_complete, '/'); |
---|
1906 | |
---|
1907 | if (first_slash) |
---|
1908 | cmp_len = first_slash - text_to_complete - 1; |
---|
1909 | else |
---|
1910 | cmp_len = strlen(text_to_complete + 1); |
---|
1911 | |
---|
1912 | if(!cmp_len) |
---|
1913 | { |
---|
1914 | /* ~/ */ |
---|
1915 | gchar *homedir = g_get_home_dir (); |
---|
1916 | |
---|
1917 | if (homedir) |
---|
1918 | return open_dir(homedir, cmpl_state); |
---|
1919 | else |
---|
1920 | return NULL; |
---|
1921 | } |
---|
1922 | else |
---|
1923 | { |
---|
1924 | /* ~user/ */ |
---|
1925 | char* copy = g_new(char, cmp_len + 1); |
---|
1926 | struct passwd *pwd; |
---|
1927 | strncpy(copy, text_to_complete + 1, cmp_len); |
---|
1928 | copy[cmp_len] = 0; |
---|
1929 | pwd = getpwnam(copy); |
---|
1930 | g_free(copy); |
---|
1931 | if (!pwd) |
---|
1932 | { |
---|
1933 | cmpl_errno = errno; |
---|
1934 | return NULL; |
---|
1935 | } |
---|
1936 | |
---|
1937 | return open_dir(pwd->pw_dir, cmpl_state); |
---|
1938 | } |
---|
1939 | } |
---|
1940 | |
---|
1941 | /* open a directory relative the the current relative directory */ |
---|
1942 | static CompletionDir* |
---|
1943 | open_relative_dir(gchar* dir_name, |
---|
1944 | CompletionDir* dir, |
---|
1945 | CompletionState *cmpl_state) |
---|
1946 | { |
---|
1947 | gchar path_buf[2*MAXPATHLEN]; |
---|
1948 | |
---|
1949 | if(dir->fullname_len + strlen(dir_name) + 2 >= MAXPATHLEN) |
---|
1950 | { |
---|
1951 | cmpl_errno = CMPL_ERRNO_TOO_LONG; |
---|
1952 | return NULL; |
---|
1953 | } |
---|
1954 | |
---|
1955 | strcpy(path_buf, dir->fullname); |
---|
1956 | |
---|
1957 | if(dir->fullname_len > 1) |
---|
1958 | { |
---|
1959 | path_buf[dir->fullname_len] = '/'; |
---|
1960 | strcpy(path_buf + dir->fullname_len + 1, dir_name); |
---|
1961 | } |
---|
1962 | else |
---|
1963 | { |
---|
1964 | strcpy(path_buf + dir->fullname_len, dir_name); |
---|
1965 | } |
---|
1966 | |
---|
1967 | return open_dir(path_buf, cmpl_state); |
---|
1968 | } |
---|
1969 | |
---|
1970 | /* after the cache lookup fails, really open a new directory */ |
---|
1971 | static CompletionDirSent* |
---|
1972 | open_new_dir(gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs) |
---|
1973 | { |
---|
1974 | CompletionDirSent* sent; |
---|
1975 | DIR* directory; |
---|
1976 | gchar *buffer_ptr; |
---|
1977 | struct dirent *dirent_ptr; |
---|
1978 | gint buffer_size = 0; |
---|
1979 | gint entry_count = 0; |
---|
1980 | gint i; |
---|
1981 | struct stat ent_sbuf; |
---|
1982 | char path_buf[MAXPATHLEN*2]; |
---|
1983 | gint path_buf_len; |
---|
1984 | |
---|
1985 | sent = g_new(CompletionDirSent, 1); |
---|
1986 | sent->mtime = sbuf->st_mtime; |
---|
1987 | sent->inode = sbuf->st_ino; |
---|
1988 | sent->device = sbuf->st_dev; |
---|
1989 | |
---|
1990 | path_buf_len = strlen(dir_name); |
---|
1991 | |
---|
1992 | if (path_buf_len > MAXPATHLEN) |
---|
1993 | { |
---|
1994 | cmpl_errno = CMPL_ERRNO_TOO_LONG; |
---|
1995 | return NULL; |
---|
1996 | } |
---|
1997 | |
---|
1998 | strcpy(path_buf, dir_name); |
---|
1999 | |
---|
2000 | directory = opendir(dir_name); |
---|
2001 | |
---|
2002 | if(!directory) |
---|
2003 | { |
---|
2004 | cmpl_errno = errno; |
---|
2005 | return NULL; |
---|
2006 | } |
---|
2007 | |
---|
2008 | while((dirent_ptr = readdir(directory)) != NULL) |
---|
2009 | { |
---|
2010 | int entry_len = strlen(dirent_ptr->d_name); |
---|
2011 | buffer_size += entry_len + 1; |
---|
2012 | entry_count += 1; |
---|
2013 | |
---|
2014 | if(path_buf_len + entry_len + 2 >= MAXPATHLEN) |
---|
2015 | { |
---|
2016 | cmpl_errno = CMPL_ERRNO_TOO_LONG; |
---|
2017 | closedir(directory); |
---|
2018 | return NULL; |
---|
2019 | } |
---|
2020 | } |
---|
2021 | |
---|
2022 | sent->name_buffer = g_new(gchar, buffer_size); |
---|
2023 | sent->entries = g_new(CompletionDirEntry, entry_count); |
---|
2024 | sent->entry_count = entry_count; |
---|
2025 | |
---|
2026 | buffer_ptr = sent->name_buffer; |
---|
2027 | |
---|
2028 | rewinddir(directory); |
---|
2029 | |
---|
2030 | for(i = 0; i < entry_count; i += 1) |
---|
2031 | { |
---|
2032 | dirent_ptr = readdir(directory); |
---|
2033 | |
---|
2034 | if(!dirent_ptr) |
---|
2035 | { |
---|
2036 | cmpl_errno = errno; |
---|
2037 | closedir(directory); |
---|
2038 | return NULL; |
---|
2039 | } |
---|
2040 | |
---|
2041 | strcpy(buffer_ptr, dirent_ptr->d_name); |
---|
2042 | sent->entries[i].entry_name = buffer_ptr; |
---|
2043 | buffer_ptr += strlen(dirent_ptr->d_name); |
---|
2044 | *buffer_ptr = 0; |
---|
2045 | buffer_ptr += 1; |
---|
2046 | |
---|
2047 | path_buf[path_buf_len] = '/'; |
---|
2048 | strcpy(path_buf + path_buf_len + 1, dirent_ptr->d_name); |
---|
2049 | |
---|
2050 | if (stat_subdirs) |
---|
2051 | { |
---|
2052 | if(stat(path_buf, &ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode)) |
---|
2053 | sent->entries[i].is_dir = 1; |
---|
2054 | else |
---|
2055 | /* stat may fail, and we don't mind, since it could be a |
---|
2056 | * dangling symlink. */ |
---|
2057 | sent->entries[i].is_dir = 0; |
---|
2058 | } |
---|
2059 | else |
---|
2060 | sent->entries[i].is_dir = 1; |
---|
2061 | } |
---|
2062 | |
---|
2063 | qsort(sent->entries, sent->entry_count, sizeof(CompletionDirEntry), compare_cmpl_dir); |
---|
2064 | |
---|
2065 | closedir(directory); |
---|
2066 | |
---|
2067 | return sent; |
---|
2068 | } |
---|
2069 | |
---|
2070 | static gboolean |
---|
2071 | check_dir(gchar *dir_name, struct stat *result, gboolean *stat_subdirs) |
---|
2072 | { |
---|
2073 | /* A list of directories that we know only contain other directories. |
---|
2074 | * Trying to stat every file in these directories would be very |
---|
2075 | * expensive. |
---|
2076 | */ |
---|
2077 | |
---|
2078 | static struct { |
---|
2079 | gchar *name; |
---|
2080 | gboolean present; |
---|
2081 | struct stat statbuf; |
---|
2082 | } no_stat_dirs[] = { |
---|
2083 | { "/afs", FALSE, { 0 } }, |
---|
2084 | { "/net", FALSE, { 0 } } |
---|
2085 | }; |
---|
2086 | |
---|
2087 | static const gint n_no_stat_dirs = sizeof(no_stat_dirs) / sizeof(no_stat_dirs[0]); |
---|
2088 | static gboolean initialized = FALSE; |
---|
2089 | |
---|
2090 | gint i; |
---|
2091 | |
---|
2092 | if (!initialized) |
---|
2093 | { |
---|
2094 | initialized = TRUE; |
---|
2095 | for (i = 0; i < n_no_stat_dirs; i++) |
---|
2096 | { |
---|
2097 | if (stat (no_stat_dirs[i].name, &no_stat_dirs[i].statbuf) == 0) |
---|
2098 | no_stat_dirs[i].present = TRUE; |
---|
2099 | } |
---|
2100 | } |
---|
2101 | |
---|
2102 | if(stat(dir_name, result) < 0) |
---|
2103 | { |
---|
2104 | cmpl_errno = errno; |
---|
2105 | return FALSE; |
---|
2106 | } |
---|
2107 | |
---|
2108 | *stat_subdirs = TRUE; |
---|
2109 | for (i=0; i<n_no_stat_dirs; i++) |
---|
2110 | { |
---|
2111 | if (no_stat_dirs[i].present && |
---|
2112 | (no_stat_dirs[i].statbuf.st_dev == result->st_dev) && |
---|
2113 | (no_stat_dirs[i].statbuf.st_ino == result->st_ino)) |
---|
2114 | { |
---|
2115 | *stat_subdirs = FALSE; |
---|
2116 | break; |
---|
2117 | } |
---|
2118 | } |
---|
2119 | |
---|
2120 | return TRUE; |
---|
2121 | } |
---|
2122 | |
---|
2123 | /* open a directory by absolute pathname */ |
---|
2124 | static CompletionDir* |
---|
2125 | open_dir(gchar* dir_name, CompletionState* cmpl_state) |
---|
2126 | { |
---|
2127 | struct stat sbuf; |
---|
2128 | gboolean stat_subdirs; |
---|
2129 | CompletionDirSent *sent; |
---|
2130 | GList* cdsl; |
---|
2131 | |
---|
2132 | if (!check_dir (dir_name, &sbuf, &stat_subdirs)) |
---|
2133 | return NULL; |
---|
2134 | |
---|
2135 | cdsl = cmpl_state->directory_sent_storage; |
---|
2136 | |
---|
2137 | while (cdsl) |
---|
2138 | { |
---|
2139 | sent = cdsl->data; |
---|
2140 | |
---|
2141 | if(sent->inode == sbuf.st_ino && |
---|
2142 | sent->mtime == sbuf.st_mtime && |
---|
2143 | sent->device == sbuf.st_dev) |
---|
2144 | return attach_dir(sent, dir_name, cmpl_state); |
---|
2145 | |
---|
2146 | cdsl = cdsl->next; |
---|
2147 | } |
---|
2148 | |
---|
2149 | sent = open_new_dir(dir_name, &sbuf, stat_subdirs); |
---|
2150 | |
---|
2151 | if (sent) { |
---|
2152 | cmpl_state->directory_sent_storage = |
---|
2153 | g_list_prepend(cmpl_state->directory_sent_storage, sent); |
---|
2154 | |
---|
2155 | return attach_dir(sent, dir_name, cmpl_state); |
---|
2156 | } |
---|
2157 | |
---|
2158 | return NULL; |
---|
2159 | } |
---|
2160 | |
---|
2161 | static CompletionDir* |
---|
2162 | attach_dir(CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state) |
---|
2163 | { |
---|
2164 | CompletionDir* new_dir; |
---|
2165 | |
---|
2166 | new_dir = g_new(CompletionDir, 1); |
---|
2167 | |
---|
2168 | cmpl_state->directory_storage = |
---|
2169 | g_list_prepend(cmpl_state->directory_storage, new_dir); |
---|
2170 | |
---|
2171 | new_dir->sent = sent; |
---|
2172 | new_dir->fullname = g_strdup(dir_name); |
---|
2173 | new_dir->fullname_len = strlen(dir_name); |
---|
2174 | |
---|
2175 | return new_dir; |
---|
2176 | } |
---|
2177 | |
---|
2178 | static gint |
---|
2179 | correct_dir_fullname(CompletionDir* cmpl_dir) |
---|
2180 | { |
---|
2181 | gint length = strlen(cmpl_dir->fullname); |
---|
2182 | struct stat sbuf; |
---|
2183 | |
---|
2184 | if (strcmp(cmpl_dir->fullname + length - 2, "/.") == 0) |
---|
2185 | { |
---|
2186 | if (length == 2) |
---|
2187 | { |
---|
2188 | strcpy(cmpl_dir->fullname, "/"); |
---|
2189 | cmpl_dir->fullname_len = 1; |
---|
2190 | return TRUE; |
---|
2191 | } else { |
---|
2192 | cmpl_dir->fullname[length - 2] = 0; |
---|
2193 | } |
---|
2194 | } |
---|
2195 | else if (strcmp(cmpl_dir->fullname + length - 3, "/./") == 0) |
---|
2196 | cmpl_dir->fullname[length - 2] = 0; |
---|
2197 | else if (strcmp(cmpl_dir->fullname + length - 3, "/..") == 0) |
---|
2198 | { |
---|
2199 | if(length == 3) |
---|
2200 | { |
---|
2201 | strcpy(cmpl_dir->fullname, "/"); |
---|
2202 | cmpl_dir->fullname_len = 1; |
---|
2203 | return TRUE; |
---|
2204 | } |
---|
2205 | |
---|
2206 | if(stat(cmpl_dir->fullname, &sbuf) < 0) |
---|
2207 | { |
---|
2208 | cmpl_errno = errno; |
---|
2209 | return FALSE; |
---|
2210 | } |
---|
2211 | |
---|
2212 | cmpl_dir->fullname[length - 2] = 0; |
---|
2213 | |
---|
2214 | if(!correct_parent(cmpl_dir, &sbuf)) |
---|
2215 | return FALSE; |
---|
2216 | } |
---|
2217 | else if (strcmp(cmpl_dir->fullname + length - 4, "/../") == 0) |
---|
2218 | { |
---|
2219 | if(length == 4) |
---|
2220 | { |
---|
2221 | strcpy(cmpl_dir->fullname, "/"); |
---|
2222 | cmpl_dir->fullname_len = 1; |
---|
2223 | return TRUE; |
---|
2224 | } |
---|
2225 | |
---|
2226 | if(stat(cmpl_dir->fullname, &sbuf) < 0) |
---|
2227 | { |
---|
2228 | cmpl_errno = errno; |
---|
2229 | return FALSE; |
---|
2230 | } |
---|
2231 | |
---|
2232 | cmpl_dir->fullname[length - 3] = 0; |
---|
2233 | |
---|
2234 | if(!correct_parent(cmpl_dir, &sbuf)) |
---|
2235 | return FALSE; |
---|
2236 | } |
---|
2237 | |
---|
2238 | cmpl_dir->fullname_len = strlen(cmpl_dir->fullname); |
---|
2239 | |
---|
2240 | return TRUE; |
---|
2241 | } |
---|
2242 | |
---|
2243 | static gint |
---|
2244 | correct_parent(CompletionDir* cmpl_dir, struct stat *sbuf) |
---|
2245 | { |
---|
2246 | struct stat parbuf; |
---|
2247 | gchar *last_slash; |
---|
2248 | gchar *new_name; |
---|
2249 | gchar c = 0; |
---|
2250 | |
---|
2251 | last_slash = strrchr(cmpl_dir->fullname, '/'); |
---|
2252 | |
---|
2253 | g_assert(last_slash); |
---|
2254 | |
---|
2255 | if(last_slash != cmpl_dir->fullname) |
---|
2256 | { /* last_slash[0] = 0; */ } |
---|
2257 | else |
---|
2258 | { |
---|
2259 | c = last_slash[1]; |
---|
2260 | last_slash[1] = 0; |
---|
2261 | } |
---|
2262 | |
---|
2263 | if (stat(cmpl_dir->fullname, &parbuf) < 0) |
---|
2264 | { |
---|
2265 | cmpl_errno = errno; |
---|
2266 | return FALSE; |
---|
2267 | } |
---|
2268 | |
---|
2269 | if (parbuf.st_ino == sbuf->st_ino && parbuf.st_dev == sbuf->st_dev) |
---|
2270 | /* it wasn't a link */ |
---|
2271 | return TRUE; |
---|
2272 | |
---|
2273 | if(c) |
---|
2274 | last_slash[1] = c; |
---|
2275 | /* else |
---|
2276 | last_slash[0] = '/'; */ |
---|
2277 | |
---|
2278 | /* it was a link, have to figure it out the hard way */ |
---|
2279 | |
---|
2280 | new_name = find_parent_dir_fullname(cmpl_dir->fullname); |
---|
2281 | |
---|
2282 | if (!new_name) |
---|
2283 | return FALSE; |
---|
2284 | |
---|
2285 | g_free(cmpl_dir->fullname); |
---|
2286 | |
---|
2287 | cmpl_dir->fullname = new_name; |
---|
2288 | |
---|
2289 | return TRUE; |
---|
2290 | } |
---|
2291 | |
---|
2292 | static gchar* |
---|
2293 | find_parent_dir_fullname(gchar* dirname) |
---|
2294 | { |
---|
2295 | gchar buffer[MAXPATHLEN]; |
---|
2296 | gchar buffer2[MAXPATHLEN]; |
---|
2297 | |
---|
2298 | #if defined(sun) && !defined(__SVR4) |
---|
2299 | if(!getwd(buffer)) |
---|
2300 | #else |
---|
2301 | if(!getcwd(buffer, MAXPATHLEN)) |
---|
2302 | #endif |
---|
2303 | { |
---|
2304 | cmpl_errno = errno; |
---|
2305 | return NULL; |
---|
2306 | } |
---|
2307 | |
---|
2308 | if(chdir(dirname) != 0 || chdir("..") != 0) |
---|
2309 | { |
---|
2310 | cmpl_errno = errno; |
---|
2311 | return NULL; |
---|
2312 | } |
---|
2313 | |
---|
2314 | #if defined(sun) && !defined(__SVR4) |
---|
2315 | if(!getwd(buffer2)) |
---|
2316 | #else |
---|
2317 | if(!getcwd(buffer2, MAXPATHLEN)) |
---|
2318 | #endif |
---|
2319 | { |
---|
2320 | chdir(buffer); |
---|
2321 | cmpl_errno = errno; |
---|
2322 | |
---|
2323 | return NULL; |
---|
2324 | } |
---|
2325 | |
---|
2326 | if(chdir(buffer) != 0) |
---|
2327 | { |
---|
2328 | cmpl_errno = errno; |
---|
2329 | return NULL; |
---|
2330 | } |
---|
2331 | |
---|
2332 | return g_strdup(buffer2); |
---|
2333 | } |
---|
2334 | |
---|
2335 | /**********************************************************************/ |
---|
2336 | /* Completion Operations */ |
---|
2337 | /**********************************************************************/ |
---|
2338 | |
---|
2339 | static PossibleCompletion* |
---|
2340 | attempt_homedir_completion(gchar* text_to_complete, |
---|
2341 | CompletionState *cmpl_state) |
---|
2342 | { |
---|
2343 | gint index, length; |
---|
2344 | |
---|
2345 | if (!cmpl_state->user_dir_name_buffer && |
---|
2346 | !get_pwdb(cmpl_state)) |
---|
2347 | return NULL; |
---|
2348 | length = strlen(text_to_complete) - 1; |
---|
2349 | |
---|
2350 | cmpl_state->user_completion_index += 1; |
---|
2351 | |
---|
2352 | while(cmpl_state->user_completion_index < cmpl_state->user_directories_len) |
---|
2353 | { |
---|
2354 | index = first_diff_index(text_to_complete + 1, |
---|
2355 | cmpl_state->user_directories |
---|
2356 | [cmpl_state->user_completion_index].login); |
---|
2357 | |
---|
2358 | switch(index) |
---|
2359 | { |
---|
2360 | case PATTERN_MATCH: |
---|
2361 | break; |
---|
2362 | default: |
---|
2363 | if(cmpl_state->last_valid_char < (index + 1)) |
---|
2364 | cmpl_state->last_valid_char = index + 1; |
---|
2365 | cmpl_state->user_completion_index += 1; |
---|
2366 | continue; |
---|
2367 | } |
---|
2368 | |
---|
2369 | cmpl_state->the_completion.is_a_completion = 1; |
---|
2370 | cmpl_state->the_completion.is_directory = 1; |
---|
2371 | |
---|
2372 | append_completion_text("~", cmpl_state); |
---|
2373 | |
---|
2374 | append_completion_text(cmpl_state-> |
---|
2375 | user_directories[cmpl_state->user_completion_index].login, |
---|
2376 | cmpl_state); |
---|
2377 | |
---|
2378 | return append_completion_text("/", cmpl_state); |
---|
2379 | } |
---|
2380 | |
---|
2381 | if(text_to_complete[1] || |
---|
2382 | cmpl_state->user_completion_index > cmpl_state->user_directories_len) |
---|
2383 | { |
---|
2384 | cmpl_state->user_completion_index = -1; |
---|
2385 | return NULL; |
---|
2386 | } |
---|
2387 | else |
---|
2388 | { |
---|
2389 | cmpl_state->user_completion_index += 1; |
---|
2390 | cmpl_state->the_completion.is_a_completion = 1; |
---|
2391 | cmpl_state->the_completion.is_directory = 1; |
---|
2392 | |
---|
2393 | return append_completion_text("~/", cmpl_state); |
---|
2394 | } |
---|
2395 | } |
---|
2396 | |
---|
2397 | /* returns the index (>= 0) of the first differing character, |
---|
2398 | * PATTERN_MATCH if the completion matches */ |
---|
2399 | static gint |
---|
2400 | first_diff_index(gchar* pat, gchar* text) |
---|
2401 | { |
---|
2402 | gint diff = 0; |
---|
2403 | |
---|
2404 | while(*pat && *text && *text == *pat) |
---|
2405 | { |
---|
2406 | pat += 1; |
---|
2407 | text += 1; |
---|
2408 | diff += 1; |
---|
2409 | } |
---|
2410 | |
---|
2411 | if(*pat) |
---|
2412 | return diff; |
---|
2413 | |
---|
2414 | return PATTERN_MATCH; |
---|
2415 | } |
---|
2416 | |
---|
2417 | static PossibleCompletion* |
---|
2418 | append_completion_text(gchar* text, CompletionState* cmpl_state) |
---|
2419 | { |
---|
2420 | gint len, i = 1; |
---|
2421 | |
---|
2422 | if(!cmpl_state->the_completion.text) |
---|
2423 | return NULL; |
---|
2424 | |
---|
2425 | len = strlen(text) + strlen(cmpl_state->the_completion.text) + 1; |
---|
2426 | |
---|
2427 | if(cmpl_state->the_completion.text_alloc > len) |
---|
2428 | { |
---|
2429 | strcat(cmpl_state->the_completion.text, text); |
---|
2430 | return &cmpl_state->the_completion; |
---|
2431 | } |
---|
2432 | |
---|
2433 | while(i < len) { i <<= 1; } |
---|
2434 | |
---|
2435 | cmpl_state->the_completion.text_alloc = i; |
---|
2436 | |
---|
2437 | cmpl_state->the_completion.text = (gchar*)g_realloc(cmpl_state->the_completion.text, i); |
---|
2438 | |
---|
2439 | if(!cmpl_state->the_completion.text) |
---|
2440 | return NULL; |
---|
2441 | else |
---|
2442 | { |
---|
2443 | strcat(cmpl_state->the_completion.text, text); |
---|
2444 | return &cmpl_state->the_completion; |
---|
2445 | } |
---|
2446 | } |
---|
2447 | |
---|
2448 | static CompletionDir* |
---|
2449 | find_completion_dir(gchar* text_to_complete, |
---|
2450 | gchar** remaining_text, |
---|
2451 | CompletionState* cmpl_state) |
---|
2452 | { |
---|
2453 | gchar* first_slash = strchr(text_to_complete, '/'); |
---|
2454 | CompletionDir* dir = cmpl_state->reference_dir; |
---|
2455 | CompletionDir* next; |
---|
2456 | *remaining_text = text_to_complete; |
---|
2457 | |
---|
2458 | while(first_slash) |
---|
2459 | { |
---|
2460 | gint len = first_slash - *remaining_text; |
---|
2461 | gint found = 0; |
---|
2462 | gchar *found_name = NULL; /* Quiet gcc */ |
---|
2463 | gint i; |
---|
2464 | gchar* pat_buf = g_new (gchar, len + 1); |
---|
2465 | |
---|
2466 | strncpy(pat_buf, *remaining_text, len); |
---|
2467 | pat_buf[len] = 0; |
---|
2468 | |
---|
2469 | for(i = 0; i < dir->sent->entry_count; i += 1) |
---|
2470 | { |
---|
2471 | if(dir->sent->entries[i].is_dir && |
---|
2472 | fnmatch(pat_buf, dir->sent->entries[i].entry_name, |
---|
2473 | FNMATCH_FLAGS)!= FNM_NOMATCH) |
---|
2474 | { |
---|
2475 | if(found) |
---|
2476 | { |
---|
2477 | g_free (pat_buf); |
---|
2478 | return dir; |
---|
2479 | } |
---|
2480 | else |
---|
2481 | { |
---|
2482 | found = 1; |
---|
2483 | found_name = dir->sent->entries[i].entry_name; |
---|
2484 | } |
---|
2485 | } |
---|
2486 | } |
---|
2487 | |
---|
2488 | if (!found) |
---|
2489 | { |
---|
2490 | /* Perhaps we are trying to open an automount directory */ |
---|
2491 | found_name = pat_buf; |
---|
2492 | } |
---|
2493 | |
---|
2494 | next = open_relative_dir(found_name, dir, cmpl_state); |
---|
2495 | |
---|
2496 | if(!next) |
---|
2497 | { |
---|
2498 | g_free (pat_buf); |
---|
2499 | return NULL; |
---|
2500 | } |
---|
2501 | |
---|
2502 | next->cmpl_parent = dir; |
---|
2503 | |
---|
2504 | dir = next; |
---|
2505 | |
---|
2506 | if(!correct_dir_fullname(dir)) |
---|
2507 | { |
---|
2508 | g_free(pat_buf); |
---|
2509 | return NULL; |
---|
2510 | } |
---|
2511 | |
---|
2512 | *remaining_text = first_slash + 1; |
---|
2513 | first_slash = strchr(*remaining_text, '/'); |
---|
2514 | |
---|
2515 | g_free (pat_buf); |
---|
2516 | } |
---|
2517 | |
---|
2518 | return dir; |
---|
2519 | } |
---|
2520 | |
---|
2521 | static void |
---|
2522 | update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state) |
---|
2523 | { |
---|
2524 | gint cmpl_len; |
---|
2525 | |
---|
2526 | if(!poss || !cmpl_is_a_completion(poss)) |
---|
2527 | return; |
---|
2528 | |
---|
2529 | cmpl_len = strlen(cmpl_this_completion(poss)); |
---|
2530 | |
---|
2531 | if(cmpl_state->updated_text_alloc < cmpl_len + 1) |
---|
2532 | { |
---|
2533 | cmpl_state->updated_text = |
---|
2534 | (gchar*)g_realloc(cmpl_state->updated_text, |
---|
2535 | cmpl_state->updated_text_alloc); |
---|
2536 | cmpl_state->updated_text_alloc = 2*cmpl_len; |
---|
2537 | } |
---|
2538 | |
---|
2539 | if(cmpl_state->updated_text_len < 0) |
---|
2540 | { |
---|
2541 | strcpy(cmpl_state->updated_text, cmpl_this_completion(poss)); |
---|
2542 | cmpl_state->updated_text_len = cmpl_len; |
---|
2543 | cmpl_state->re_complete = cmpl_is_directory(poss); |
---|
2544 | } |
---|
2545 | else if(cmpl_state->updated_text_len == 0) |
---|
2546 | { |
---|
2547 | cmpl_state->re_complete = FALSE; |
---|
2548 | } |
---|
2549 | else |
---|
2550 | { |
---|
2551 | gint first_diff = |
---|
2552 | first_diff_index(cmpl_state->updated_text, |
---|
2553 | cmpl_this_completion(poss)); |
---|
2554 | |
---|
2555 | cmpl_state->re_complete = FALSE; |
---|
2556 | |
---|
2557 | if(first_diff == PATTERN_MATCH) |
---|
2558 | return; |
---|
2559 | |
---|
2560 | if(first_diff > cmpl_state->updated_text_len) |
---|
2561 | strcpy(cmpl_state->updated_text, cmpl_this_completion(poss)); |
---|
2562 | |
---|
2563 | cmpl_state->updated_text_len = first_diff; |
---|
2564 | cmpl_state->updated_text[first_diff] = 0; |
---|
2565 | } |
---|
2566 | } |
---|
2567 | |
---|
2568 | static PossibleCompletion* |
---|
2569 | attempt_file_completion(CompletionState *cmpl_state) |
---|
2570 | { |
---|
2571 | gchar *pat_buf, *first_slash; |
---|
2572 | CompletionDir *dir = cmpl_state->active_completion_dir; |
---|
2573 | |
---|
2574 | dir->cmpl_index += 1; |
---|
2575 | |
---|
2576 | if(dir->cmpl_index == dir->sent->entry_count) |
---|
2577 | { |
---|
2578 | if(dir->cmpl_parent == NULL) |
---|
2579 | { |
---|
2580 | cmpl_state->active_completion_dir = NULL; |
---|
2581 | |
---|
2582 | return NULL; |
---|
2583 | } |
---|
2584 | else |
---|
2585 | { |
---|
2586 | cmpl_state->active_completion_dir = dir->cmpl_parent; |
---|
2587 | |
---|
2588 | return attempt_file_completion(cmpl_state); |
---|
2589 | } |
---|
2590 | } |
---|
2591 | |
---|
2592 | g_assert(dir->cmpl_text); |
---|
2593 | |
---|
2594 | first_slash = strchr(dir->cmpl_text, '/'); |
---|
2595 | |
---|
2596 | if(first_slash) |
---|
2597 | { |
---|
2598 | gint len = first_slash - dir->cmpl_text; |
---|
2599 | |
---|
2600 | pat_buf = g_new (gchar, len + 1); |
---|
2601 | strncpy(pat_buf, dir->cmpl_text, len); |
---|
2602 | pat_buf[len] = 0; |
---|
2603 | } |
---|
2604 | else |
---|
2605 | { |
---|
2606 | gint len = strlen(dir->cmpl_text); |
---|
2607 | |
---|
2608 | pat_buf = g_new (gchar, len + 2); |
---|
2609 | strcpy(pat_buf, dir->cmpl_text); |
---|
2610 | strcpy(pat_buf + len, "*"); |
---|
2611 | } |
---|
2612 | |
---|
2613 | if(first_slash) |
---|
2614 | { |
---|
2615 | if(dir->sent->entries[dir->cmpl_index].is_dir) |
---|
2616 | { |
---|
2617 | if(fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, |
---|
2618 | FNMATCH_FLAGS) != FNM_NOMATCH) |
---|
2619 | { |
---|
2620 | CompletionDir* new_dir; |
---|
2621 | |
---|
2622 | new_dir = open_relative_dir(dir->sent->entries[dir->cmpl_index].entry_name, |
---|
2623 | dir, cmpl_state); |
---|
2624 | |
---|
2625 | if(!new_dir) |
---|
2626 | { |
---|
2627 | g_free (pat_buf); |
---|
2628 | return NULL; |
---|
2629 | } |
---|
2630 | |
---|
2631 | new_dir->cmpl_parent = dir; |
---|
2632 | |
---|
2633 | new_dir->cmpl_index = -1; |
---|
2634 | new_dir->cmpl_text = first_slash + 1; |
---|
2635 | |
---|
2636 | cmpl_state->active_completion_dir = new_dir; |
---|
2637 | |
---|
2638 | g_free (pat_buf); |
---|
2639 | return attempt_file_completion(cmpl_state); |
---|
2640 | } |
---|
2641 | else |
---|
2642 | { |
---|
2643 | g_free (pat_buf); |
---|
2644 | return attempt_file_completion(cmpl_state); |
---|
2645 | } |
---|
2646 | } |
---|
2647 | else |
---|
2648 | { |
---|
2649 | g_free (pat_buf); |
---|
2650 | return attempt_file_completion(cmpl_state); |
---|
2651 | } |
---|
2652 | } |
---|
2653 | else |
---|
2654 | { |
---|
2655 | if(dir->cmpl_parent != NULL) |
---|
2656 | { |
---|
2657 | append_completion_text(dir->fullname + |
---|
2658 | strlen(cmpl_state->completion_dir->fullname) + 1, |
---|
2659 | cmpl_state); |
---|
2660 | append_completion_text("/", cmpl_state); |
---|
2661 | } |
---|
2662 | |
---|
2663 | append_completion_text(dir->sent->entries[dir->cmpl_index].entry_name, cmpl_state); |
---|
2664 | |
---|
2665 | cmpl_state->the_completion.is_a_completion = |
---|
2666 | (fnmatch(pat_buf, dir->sent->entries[dir->cmpl_index].entry_name, |
---|
2667 | FNMATCH_FLAGS) != FNM_NOMATCH); |
---|
2668 | |
---|
2669 | cmpl_state->the_completion.is_directory = dir->sent->entries[dir->cmpl_index].is_dir; |
---|
2670 | if(dir->sent->entries[dir->cmpl_index].is_dir) |
---|
2671 | append_completion_text("/", cmpl_state); |
---|
2672 | |
---|
2673 | g_free (pat_buf); |
---|
2674 | return &cmpl_state->the_completion; |
---|
2675 | } |
---|
2676 | } |
---|
2677 | |
---|
2678 | |
---|
2679 | static gint |
---|
2680 | get_pwdb(CompletionState* cmpl_state) |
---|
2681 | { |
---|
2682 | struct passwd *pwd_ptr; |
---|
2683 | gchar* buf_ptr; |
---|
2684 | gint len = 0, i, count = 0; |
---|
2685 | |
---|
2686 | if(cmpl_state->user_dir_name_buffer) |
---|
2687 | return TRUE; |
---|
2688 | setpwent (); |
---|
2689 | |
---|
2690 | while ((pwd_ptr = getpwent()) != NULL) |
---|
2691 | { |
---|
2692 | len += strlen(pwd_ptr->pw_name); |
---|
2693 | len += strlen(pwd_ptr->pw_dir); |
---|
2694 | len += 2; |
---|
2695 | count += 1; |
---|
2696 | } |
---|
2697 | |
---|
2698 | setpwent (); |
---|
2699 | |
---|
2700 | cmpl_state->user_dir_name_buffer = g_new(gchar, len); |
---|
2701 | cmpl_state->user_directories = g_new(CompletionUserDir, count); |
---|
2702 | cmpl_state->user_directories_len = count; |
---|
2703 | |
---|
2704 | buf_ptr = cmpl_state->user_dir_name_buffer; |
---|
2705 | |
---|
2706 | for(i = 0; i < count; i += 1) |
---|
2707 | { |
---|
2708 | pwd_ptr = getpwent(); |
---|
2709 | if(!pwd_ptr) |
---|
2710 | { |
---|
2711 | cmpl_errno = errno; |
---|
2712 | goto error; |
---|
2713 | } |
---|
2714 | |
---|
2715 | strcpy(buf_ptr, pwd_ptr->pw_name); |
---|
2716 | cmpl_state->user_directories[i].login = buf_ptr; |
---|
2717 | buf_ptr += strlen(buf_ptr); |
---|
2718 | buf_ptr += 1; |
---|
2719 | strcpy(buf_ptr, pwd_ptr->pw_dir); |
---|
2720 | cmpl_state->user_directories[i].homedir = buf_ptr; |
---|
2721 | buf_ptr += strlen(buf_ptr); |
---|
2722 | buf_ptr += 1; |
---|
2723 | } |
---|
2724 | |
---|
2725 | qsort(cmpl_state->user_directories, |
---|
2726 | cmpl_state->user_directories_len, |
---|
2727 | sizeof(CompletionUserDir), |
---|
2728 | compare_user_dir); |
---|
2729 | |
---|
2730 | endpwent(); |
---|
2731 | |
---|
2732 | return TRUE; |
---|
2733 | |
---|
2734 | error: |
---|
2735 | |
---|
2736 | if(cmpl_state->user_dir_name_buffer) |
---|
2737 | g_free(cmpl_state->user_dir_name_buffer); |
---|
2738 | if(cmpl_state->user_directories) |
---|
2739 | g_free(cmpl_state->user_directories); |
---|
2740 | |
---|
2741 | cmpl_state->user_dir_name_buffer = NULL; |
---|
2742 | cmpl_state->user_directories = NULL; |
---|
2743 | |
---|
2744 | return FALSE; |
---|
2745 | } |
---|
2746 | |
---|
2747 | static gint |
---|
2748 | compare_user_dir(const void* a, const void* b) |
---|
2749 | { |
---|
2750 | return strcmp((((CompletionUserDir*)a))->login, |
---|
2751 | (((CompletionUserDir*)b))->login); |
---|
2752 | } |
---|
2753 | |
---|
2754 | static gint |
---|
2755 | compare_cmpl_dir(const void* a, const void* b) |
---|
2756 | { |
---|
2757 | return strcmp((((CompletionDirEntry*)a))->entry_name, |
---|
2758 | (((CompletionDirEntry*)b))->entry_name); |
---|
2759 | } |
---|
2760 | |
---|
2761 | static gint |
---|
2762 | cmpl_state_okay(CompletionState* cmpl_state) |
---|
2763 | { |
---|
2764 | return cmpl_state && cmpl_state->reference_dir; |
---|
2765 | } |
---|
2766 | |
---|
2767 | static gchar* |
---|
2768 | cmpl_strerror(gint err) |
---|
2769 | { |
---|
2770 | if(err == CMPL_ERRNO_TOO_LONG) |
---|
2771 | return "Name too long"; |
---|
2772 | else |
---|
2773 | return g_strerror (err); |
---|
2774 | } |
---|