r235: Added the Shell Command feature to the minibuffer.
[rox-filer/ma.git] / ROX-Filer / src / minibuffer.c
blob157268fc637db7b088a79d33cda7d5095e8e0eb9
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <string.h>
27 #include <errno.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
32 #include "collection.h"
33 #include "gui_support.h"
34 #include "support.h"
35 #include "minibuffer.h"
36 #include "filer.h"
37 #include "main.h"
39 static GList *shell_history = NULL;
41 /* Static prototypes */
42 static gint key_press_event(GtkWidget *widget,
43 GdkEventKey *event,
44 FilerWindow *filer_window);
45 static void changed(GtkEditable *mini, FilerWindow *filer_window);
46 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
47 static gboolean matches(Collection *collection, int item, char *pattern);
48 static void search_in_dir(FilerWindow *filer_window, int dir);
51 /****************************************************************
52 * EXTERNAL INTERFACE *
53 ****************************************************************/
56 GtkWidget *create_minibuffer(FilerWindow *filer_window)
58 GtkWidget *mini;
60 mini = gtk_entry_new();
61 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
62 GTK_SIGNAL_FUNC(key_press_event), filer_window);
63 gtk_signal_connect(GTK_OBJECT(mini), "changed",
64 GTK_SIGNAL_FUNC(changed), filer_window);
66 return mini;
69 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
71 Collection *collection;
72 GtkEntry *mini;
74 g_return_if_fail(filer_window != NULL);
75 g_return_if_fail(filer_window->minibuffer != NULL);
77 mini = GTK_ENTRY(filer_window->minibuffer);
79 filer_window->mini_type = MINI_NONE;
81 switch (mini_type)
83 case MINI_PATH:
84 collection = filer_window->collection;
85 filer_window->mini_cursor_base =
86 MAX(collection->cursor_item, 0);
87 gtk_entry_set_text(mini,
88 make_path(filer_window->path, "")->str);
89 break;
90 case MINI_SHELL:
91 filer_window->mini_cursor_base = -1; /* History */
92 gtk_entry_set_text(mini, "");
93 break;
94 default:
95 g_warning("Bad minibuffer type\n");
96 return;
99 filer_window->mini_type = mini_type;
101 gtk_entry_set_position(mini, -1);
103 gtk_widget_show(filer_window->minibuffer);
105 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
106 filer_window->minibuffer);
109 void minibuffer_hide(FilerWindow *filer_window)
111 filer_window->mini_type = MINI_NONE;
113 gtk_widget_hide(filer_window->minibuffer);
114 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
115 GTK_WIDGET(filer_window->collection));
119 /****************************************************************
120 * INTERNAL FUNCTIONS *
121 ****************************************************************/
123 /* PATH ENTRY */
125 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
127 Collection *collection = filer_window->collection;
128 int item = collection->cursor_item;
129 char *path, *pattern;
130 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
132 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
133 pattern = strrchr(path, '/');
134 if (pattern)
135 pattern++;
136 else
137 pattern = path;
139 if (item == -1 || !matches(collection, item, pattern))
141 gdk_beep();
142 return;
145 if ((event->state & GDK_SHIFT_MASK) != 0)
146 flags |= OPEN_SHIFT;
148 filer_openitem(filer_window, item, flags);
151 /* Use the cursor item to fill in the minibuffer.
152 * If there are multiple matches the fill in as much as possible and beep.
154 static void complete(FilerWindow *filer_window)
156 GtkEntry *entry;
157 Collection *collection = filer_window->collection;
158 int cursor = collection->cursor_item;
159 DirItem *item;
160 int shortest_stem = -1;
161 int current_stem;
162 int other;
163 guchar *text, *leaf;
165 if (cursor < 0 || cursor >= collection->number_of_items)
167 gdk_beep();
168 return;
171 entry = GTK_ENTRY(filer_window->minibuffer);
173 item = (DirItem *) collection->items[cursor].data;
175 text = gtk_entry_get_text(entry);
176 leaf = strrchr(text, '/');
177 if (!leaf)
179 gdk_beep();
180 return;
183 leaf++;
184 if (!matches(collection, cursor, leaf))
186 gdk_beep();
187 return;
190 current_stem = strlen(leaf);
192 /* Find the longest other match of this name. It it's longer than
193 * the currently entered text then complete only up to that length.
195 for (other = 0; other < collection->number_of_items; other++)
197 DirItem *other_item = (DirItem *) collection->items[other].data;
198 int stem = 0;
200 if (other == cursor)
201 continue;
203 while (other_item->leafname[stem] && item->leafname[stem])
205 if (other_item->leafname[stem] != item->leafname[stem])
206 break;
207 stem++;
210 /* stem is the index of the first difference */
211 if (stem >= current_stem &&
212 (shortest_stem == -1 || stem < shortest_stem))
213 shortest_stem = stem;
216 if (current_stem == shortest_stem)
217 gdk_beep();
218 else if (current_stem < shortest_stem)
220 guchar *extra;
222 extra = g_strndup(item->leafname + current_stem,
223 shortest_stem - current_stem);
224 gtk_entry_append_text(entry, extra);
225 g_free(extra);
226 gdk_beep();
228 else
230 GString *new;
232 new = make_path(filer_window->path, item->leafname);
234 if (item->base_type == TYPE_DIRECTORY &&
235 (item->flags & ITEM_FLAG_APPDIR) == 0)
236 g_string_append_c(new, '/');
238 gtk_entry_set_text(entry, new->str);
242 static void path_changed(GtkEditable *mini, FilerWindow *filer_window)
244 char *new, *slash;
245 char *path, *real;
247 new = gtk_entry_get_text(GTK_ENTRY(mini));
248 if (*new == '/')
249 new = g_strdup(new);
250 else
251 new = g_strdup_printf("/%s", new);
253 slash = strrchr(new, '/');
254 *slash = '\0';
256 if (*new == '\0')
257 path = "/";
258 else
259 path = new;
261 real = pathdup(path);
263 if (strcmp(real, filer_window->path) != 0)
265 struct stat info;
266 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
267 filer_change_to(filer_window, real, slash + 1);
268 else
269 gdk_beep();
271 else
273 Collection *collection = filer_window->collection;
274 int item;
276 find_next_match(filer_window, slash + 1, 0);
277 item = collection->cursor_item;
278 if (item != -1 && !matches(collection, item, slash + 1))
279 gdk_beep();
282 g_free(real);
283 g_free(new);
286 /* Find the next item in the collection that matches 'pattern'. Start from
287 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
288 * -1 for backwards. 0 means forwards, but may stay the same.
290 * Does not automatically update mini_cursor_base.
292 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
294 Collection *collection = filer_window->collection;
295 int base = filer_window->mini_cursor_base;
296 int item = base;
298 if (collection->number_of_items < 1)
299 return;
301 if (base < 0 || base>= collection->number_of_items)
302 filer_window->mini_cursor_base = base = 0;
306 /* Step to the next item */
307 item += dir;
309 if (item >= collection->number_of_items)
310 item = 0;
311 else if (item < 0)
312 item = collection->number_of_items - 1;
314 if (dir == 0)
315 dir = 1;
316 else if (item == base)
317 break; /* No (other) matches at all */
320 } while (!matches(collection, item, pattern));
322 collection_set_cursor_item(collection, item);
325 static gboolean matches(Collection *collection, int item_number, char *pattern)
327 DirItem *item = (DirItem *) collection->items[item_number].data;
329 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
332 /* Find next match and set base for future matches. */
333 static void search_in_dir(FilerWindow *filer_window, int dir)
335 char *path, *pattern;
337 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
338 pattern = strrchr(path, '/');
339 if (pattern)
340 pattern++;
341 else
342 pattern = path;
344 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
345 find_next_match(filer_window, pattern, dir);
346 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
349 /* SHELL COMMANDS */
351 static void shell_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
353 GPtrArray *argv;
354 int i;
355 guchar *entry;
356 Collection *collection = filer_window->collection;
358 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
359 shell_history = g_list_prepend(shell_history, g_strdup(entry));
361 argv = g_ptr_array_new();
362 g_ptr_array_add(argv, "sh");
363 g_ptr_array_add(argv, "-c");
364 g_ptr_array_add(argv, entry);
365 g_ptr_array_add(argv, "sh");
367 for (i = 0; i < collection->number_of_items; i++)
369 DirItem *item = (DirItem *) collection->items[i].data;
370 if (collection->items[i].selected)
371 g_ptr_array_add(argv, item->leafname);
374 g_ptr_array_add(argv, NULL);
376 switch (fork())
378 case -1:
379 delayed_error("ROX-Filer", "Failed to create "
380 "child process");
381 return;
382 case 0: /* Child */
383 dup2(to_error_log, STDOUT_FILENO);
384 close_on_exec(STDOUT_FILENO, FALSE);
385 dup2(to_error_log, STDERR_FILENO);
386 close_on_exec(STDERR_FILENO, FALSE);
387 if (chdir(filer_window->path))
388 g_printerr("chdir(%s) failed: %s\n",
389 filer_window->path,
390 g_strerror(errno));
391 execvp((char *) argv->pdata[0],
392 (char **) argv->pdata);
393 g_printerr("execvp(%s, ...) failed: %s\n",
394 (char *) argv->pdata[0],
395 g_strerror(errno));
396 _exit(0);
399 g_ptr_array_free(argv, TRUE);
401 minibuffer_hide(filer_window);
404 /* Move through the shell history */
405 static void shell_recall(FilerWindow *filer_window, int dir)
407 guchar *command;
408 int pos = filer_window->mini_cursor_base;
410 pos += dir;
411 if (pos >= 0)
413 command = g_list_nth_data(shell_history, pos);
414 if (!command)
415 return;
417 else
418 command = "";
420 if (pos < -1)
421 pos = -1;
422 filer_window->mini_cursor_base = pos;
424 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
427 /* EVENT HANDLERS */
429 static gint key_press_event(GtkWidget *widget,
430 GdkEventKey *event,
431 FilerWindow *filer_window)
433 if (event->keyval == GDK_Escape)
435 minibuffer_hide(filer_window);
436 return TRUE;
439 switch (filer_window->mini_type)
441 case MINI_PATH:
442 switch (event->keyval)
444 case GDK_Up:
445 search_in_dir(filer_window, -1);
446 break;
447 case GDK_Down:
448 search_in_dir(filer_window, 1);
449 break;
450 case GDK_Return:
451 path_return_pressed(filer_window,
452 event);
453 break;
454 case GDK_Tab:
455 complete(filer_window);
456 break;
457 default:
458 return FALSE;
460 break;
462 case MINI_SHELL:
463 switch (event->keyval)
465 case GDK_Up:
466 shell_recall(filer_window, 1);
467 break;
468 case GDK_Down:
469 shell_recall(filer_window, -1);
470 break;
471 case GDK_Tab:
472 break;
473 case GDK_Return:
474 shell_return_pressed(filer_window,
475 event);
476 default:
477 return FALSE;
479 break;
480 default:
481 break;
484 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
485 return TRUE;
488 static void changed(GtkEditable *mini, FilerWindow *filer_window)
490 switch (filer_window->mini_type)
492 case MINI_PATH:
493 path_changed(mini, filer_window);
494 return;
495 case MINI_SHELL:
496 break;
497 default:
498 break;