4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* minibuffer.c - for handling the path entry box at the bottom */
31 #include <gdk/gdkkeysyms.h>
33 #include "collection.h"
34 #include "gui_support.h"
36 #include "minibuffer.h"
40 static GList
*shell_history
= NULL
;
42 /* Static prototypes */
43 static gint
key_press_event(GtkWidget
*widget
,
45 FilerWindow
*filer_window
);
46 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
);
47 static void find_next_match(FilerWindow
*filer_window
, char *pattern
, int dir
);
48 static gboolean
matches(Collection
*collection
, int item
, char *pattern
);
49 static void search_in_dir(FilerWindow
*filer_window
, int dir
);
52 /****************************************************************
53 * EXTERNAL INTERFACE *
54 ****************************************************************/
57 GtkWidget
*create_minibuffer(FilerWindow
*filer_window
)
61 mini
= gtk_entry_new();
62 gtk_widget_set_style(mini
, fixed_style
);
63 gtk_signal_connect(GTK_OBJECT(mini
), "key_press_event",
64 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
65 gtk_signal_connect(GTK_OBJECT(mini
), "changed",
66 GTK_SIGNAL_FUNC(changed
), filer_window
);
71 void minibuffer_show(FilerWindow
*filer_window
, MiniType mini_type
)
73 Collection
*collection
;
76 g_return_if_fail(filer_window
!= NULL
);
77 g_return_if_fail(filer_window
->minibuffer
!= NULL
);
79 mini
= GTK_ENTRY(filer_window
->minibuffer
);
81 filer_window
->mini_type
= MINI_NONE
;
86 collection
= filer_window
->collection
;
87 filer_window
->mini_cursor_base
=
88 MAX(collection
->cursor_item
, 0);
89 gtk_entry_set_text(mini
,
90 make_path(filer_window
->path
, "")->str
);
93 filer_window
->mini_cursor_base
= -1; /* History */
94 gtk_entry_set_text(mini
, "");
97 g_warning("Bad minibuffer type\n");
101 filer_window
->mini_type
= mini_type
;
103 gtk_entry_set_position(mini
, -1);
105 gtk_widget_show(filer_window
->minibuffer
);
107 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
108 filer_window
->minibuffer
);
111 void minibuffer_hide(FilerWindow
*filer_window
)
113 filer_window
->mini_type
= MINI_NONE
;
115 gtk_widget_hide(filer_window
->minibuffer
);
116 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
117 GTK_WIDGET(filer_window
->collection
));
120 /* Insert this leafname at the cursor (replacing the selection, if any).
121 * Must be in SHELL mode.
123 void minibuffer_add(FilerWindow
*filer_window
, guchar
*leafname
)
126 GtkEditable
*edit
= GTK_EDITABLE(filer_window
->minibuffer
);
129 g_return_if_fail(filer_window
->mini_type
== MINI_SHELL
);
131 if (strchr(leafname
, ' '))
132 esc
= g_strdup_printf(" \"%s\"", leafname
);
134 esc
= g_strdup_printf(" %s", leafname
);
136 gtk_editable_delete_selection(edit
);
137 pos
= gtk_editable_get_position(edit
);
138 gtk_editable_insert_text(edit
, esc
, strlen(esc
), &pos
);
139 gtk_editable_set_position(edit
, pos
);
145 /****************************************************************
146 * INTERNAL FUNCTIONS *
147 ****************************************************************/
151 static void path_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
153 Collection
*collection
= filer_window
->collection
;
154 int item
= collection
->cursor_item
;
155 char *path
, *pattern
;
156 int flags
= OPEN_FROM_MINI
| OPEN_SAME_WINDOW
;
158 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
159 pattern
= strrchr(path
, '/');
165 if (item
== -1 || !matches(collection
, item
, pattern
))
171 if ((event
->state
& GDK_SHIFT_MASK
) != 0)
174 filer_openitem(filer_window
, item
, flags
);
177 /* Use the cursor item to fill in the minibuffer.
178 * If there are multiple matches the fill in as much as possible and beep.
180 static void complete(FilerWindow
*filer_window
)
183 Collection
*collection
= filer_window
->collection
;
184 int cursor
= collection
->cursor_item
;
186 int shortest_stem
= -1;
191 if (cursor
< 0 || cursor
>= collection
->number_of_items
)
197 entry
= GTK_ENTRY(filer_window
->minibuffer
);
199 item
= (DirItem
*) collection
->items
[cursor
].data
;
201 text
= gtk_entry_get_text(entry
);
202 leaf
= strrchr(text
, '/');
210 if (!matches(collection
, cursor
, leaf
))
216 current_stem
= strlen(leaf
);
218 /* Find the longest other match of this name. It it's longer than
219 * the currently entered text then complete only up to that length.
221 for (other
= 0; other
< collection
->number_of_items
; other
++)
223 DirItem
*other_item
= (DirItem
*) collection
->items
[other
].data
;
229 while (other_item
->leafname
[stem
] && item
->leafname
[stem
])
231 if (other_item
->leafname
[stem
] != item
->leafname
[stem
])
236 /* stem is the index of the first difference */
237 if (stem
>= current_stem
&&
238 (shortest_stem
== -1 || stem
< shortest_stem
))
239 shortest_stem
= stem
;
242 if (current_stem
== shortest_stem
)
244 else if (current_stem
< shortest_stem
)
248 extra
= g_strndup(item
->leafname
+ current_stem
,
249 shortest_stem
- current_stem
);
250 gtk_entry_append_text(entry
, extra
);
258 new = make_path(filer_window
->path
, item
->leafname
);
260 if (item
->base_type
== TYPE_DIRECTORY
&&
261 (item
->flags
& ITEM_FLAG_APPDIR
) == 0)
262 g_string_append_c(new, '/');
264 gtk_entry_set_text(entry
, new->str
);
268 static void path_changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
273 new = gtk_entry_get_text(GTK_ENTRY(mini
));
277 new = g_strdup_printf("/%s", new);
279 slash
= strrchr(new, '/');
287 real
= pathdup(path
);
289 if (strcmp(real
, filer_window
->path
) != 0)
292 if (mc_stat(real
, &info
) == 0 && S_ISDIR(info
.st_mode
))
293 filer_change_to(filer_window
, real
, slash
+ 1);
299 Collection
*collection
= filer_window
->collection
;
302 find_next_match(filer_window
, slash
+ 1, 0);
303 item
= collection
->cursor_item
;
304 if (item
!= -1 && !matches(collection
, item
, slash
+ 1))
312 /* Find the next item in the collection that matches 'pattern'. Start from
313 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
314 * -1 for backwards. 0 means forwards, but may stay the same.
316 * Does not automatically update mini_cursor_base.
318 static void find_next_match(FilerWindow
*filer_window
, char *pattern
, int dir
)
320 Collection
*collection
= filer_window
->collection
;
321 int base
= filer_window
->mini_cursor_base
;
324 if (collection
->number_of_items
< 1)
327 if (base
< 0 || base
>= collection
->number_of_items
)
328 filer_window
->mini_cursor_base
= base
= 0;
332 /* Step to the next item */
335 if (item
>= collection
->number_of_items
)
338 item
= collection
->number_of_items
- 1;
342 else if (item
== base
)
343 break; /* No (other) matches at all */
346 } while (!matches(collection
, item
, pattern
));
348 collection_set_cursor_item(collection
, item
);
351 static gboolean
matches(Collection
*collection
, int item_number
, char *pattern
)
353 DirItem
*item
= (DirItem
*) collection
->items
[item_number
].data
;
355 return strncmp(item
->leafname
, pattern
, strlen(pattern
)) == 0;
358 /* Find next match and set base for future matches. */
359 static void search_in_dir(FilerWindow
*filer_window
, int dir
)
361 char *path
, *pattern
;
363 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
364 pattern
= strrchr(path
, '/');
370 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
371 find_next_match(filer_window
, pattern
, dir
);
372 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
377 static void add_to_history(FilerWindow
*filer_window
)
379 guchar
*line
, *last
, *c
;
381 line
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
383 for (c
= line
; *c
&& isspace(*c
); c
++)
389 last
= shell_history
? (guchar
*) shell_history
->data
: NULL
;
391 if (last
&& strcmp(last
, line
) == 0)
392 return; /* Duplicating last entry */
394 shell_history
= g_list_prepend(shell_history
, g_strdup(line
));
397 static void shell_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
402 Collection
*collection
= filer_window
->collection
;
404 add_to_history(filer_window
);
406 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
408 argv
= g_ptr_array_new();
409 g_ptr_array_add(argv
, "sh");
410 g_ptr_array_add(argv
, "-c");
411 g_ptr_array_add(argv
, entry
);
412 g_ptr_array_add(argv
, "sh");
414 for (i
= 0; i
< collection
->number_of_items
; i
++)
416 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
417 if (collection
->items
[i
].selected
)
418 g_ptr_array_add(argv
, item
->leafname
);
421 g_ptr_array_add(argv
, NULL
);
426 delayed_error("ROX-Filer", "Failed to create "
430 dup2(to_error_log
, STDOUT_FILENO
);
431 close_on_exec(STDOUT_FILENO
, FALSE
);
432 dup2(to_error_log
, STDERR_FILENO
);
433 close_on_exec(STDERR_FILENO
, FALSE
);
434 if (chdir(filer_window
->path
))
435 g_printerr("chdir(%s) failed: %s\n",
438 execvp((char *) argv
->pdata
[0],
439 (char **) argv
->pdata
);
440 g_printerr("execvp(%s, ...) failed: %s\n",
441 (char *) argv
->pdata
[0],
446 g_ptr_array_free(argv
, TRUE
);
448 minibuffer_hide(filer_window
);
451 /* Move through the shell history */
452 static void shell_recall(FilerWindow
*filer_window
, int dir
)
455 int pos
= filer_window
->mini_cursor_base
;
460 command
= g_list_nth_data(shell_history
, pos
);
469 filer_window
->mini_cursor_base
= pos
;
471 gtk_entry_set_text(GTK_ENTRY(filer_window
->minibuffer
), command
);
476 static gint
key_press_event(GtkWidget
*widget
,
478 FilerWindow
*filer_window
)
480 if (event
->keyval
== GDK_Escape
)
482 if (filer_window
->mini_type
== MINI_SHELL
)
483 add_to_history(filer_window
);
484 minibuffer_hide(filer_window
);
488 switch (filer_window
->mini_type
)
491 switch (event
->keyval
)
494 search_in_dir(filer_window
, -1);
497 search_in_dir(filer_window
, 1);
500 path_return_pressed(filer_window
,
504 complete(filer_window
);
512 switch (event
->keyval
)
515 shell_recall(filer_window
, 1);
518 shell_recall(filer_window
, -1);
523 shell_return_pressed(filer_window
,
533 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget
), "key_press_event");
537 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
539 switch (filer_window
->mini_type
)
542 path_changed(mini
, filer_window
);