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