r246: Added support for i18n. No translations yet, though!
[rox-filer/ma.git] / ROX-Filer / src / minibuffer.c
blobe1eabdd40ddd9d68d44a686069f7bcc9f2928093
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>
28 #include <ctype.h>
29 #include <glob.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
34 #include "collection.h"
35 #include "gui_support.h"
36 #include "support.h"
37 #include "minibuffer.h"
38 #include "filer.h"
39 #include "main.h"
41 static GList *shell_history = NULL;
43 /* Static prototypes */
44 static gint key_press_event(GtkWidget *widget,
45 GdkEventKey *event,
46 FilerWindow *filer_window);
47 static void changed(GtkEditable *mini, FilerWindow *filer_window);
48 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
49 static gboolean matches(Collection *collection, int item, char *pattern);
50 static void search_in_dir(FilerWindow *filer_window, int dir);
51 static guchar *mini_contents(FilerWindow *filer_window);
54 /****************************************************************
55 * EXTERNAL INTERFACE *
56 ****************************************************************/
59 GtkWidget *create_minibuffer(FilerWindow *filer_window)
61 GtkWidget *mini;
63 mini = gtk_entry_new();
64 gtk_widget_set_style(mini, fixed_style);
65 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
66 GTK_SIGNAL_FUNC(key_press_event), filer_window);
67 gtk_signal_connect(GTK_OBJECT(mini), "changed",
68 GTK_SIGNAL_FUNC(changed), filer_window);
70 return mini;
73 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
75 Collection *collection;
76 GtkEntry *mini;
78 g_return_if_fail(filer_window != NULL);
79 g_return_if_fail(filer_window->minibuffer != NULL);
81 mini = GTK_ENTRY(filer_window->minibuffer);
83 filer_window->mini_type = MINI_NONE;
85 switch (mini_type)
87 case MINI_PATH:
88 collection = filer_window->collection;
89 filer_window->mini_cursor_base =
90 MAX(collection->cursor_item, 0);
91 gtk_entry_set_text(mini,
92 make_path(filer_window->path, "")->str);
93 break;
94 case MINI_SHELL:
95 filer_window->mini_cursor_base = -1; /* History */
96 gtk_entry_set_text(mini, "");
97 break;
98 default:
99 g_warning("Bad minibuffer type\n");
100 return;
103 filer_window->mini_type = mini_type;
105 gtk_entry_set_position(mini, -1);
107 gtk_widget_show(filer_window->minibuffer);
109 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
110 filer_window->minibuffer);
113 void minibuffer_hide(FilerWindow *filer_window)
115 filer_window->mini_type = MINI_NONE;
117 gtk_widget_hide(filer_window->minibuffer);
118 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
119 GTK_WIDGET(filer_window->collection));
122 /* Insert this leafname at the cursor (replacing the selection, if any).
123 * Must be in SHELL mode.
125 void minibuffer_add(FilerWindow *filer_window, guchar *leafname)
127 guchar *esc;
128 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
129 GtkEntry *entry = GTK_ENTRY(edit);
130 int pos;
132 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
134 esc = shell_escape(leafname);
136 gtk_editable_delete_selection(edit);
137 pos = gtk_editable_get_position(edit);
139 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
140 gtk_editable_insert_text(edit, " ", 1, &pos);
142 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
143 gtk_editable_set_position(edit, pos);
145 g_free(esc);
149 /****************************************************************
150 * INTERNAL FUNCTIONS *
151 ****************************************************************/
153 /* PATH ENTRY */
155 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
157 Collection *collection = filer_window->collection;
158 int item = collection->cursor_item;
159 char *path, *pattern;
160 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
162 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
163 pattern = strrchr(path, '/');
164 if (pattern)
165 pattern++;
166 else
167 pattern = path;
169 if (item == -1 || !matches(collection, item, pattern))
171 gdk_beep();
172 return;
175 if ((event->state & GDK_SHIFT_MASK) != 0)
176 flags |= OPEN_SHIFT;
178 filer_openitem(filer_window, item, flags);
181 /* Use the cursor item to fill in the minibuffer.
182 * If there are multiple matches the fill in as much as possible and beep.
184 static void complete(FilerWindow *filer_window)
186 GtkEntry *entry;
187 Collection *collection = filer_window->collection;
188 int cursor = collection->cursor_item;
189 DirItem *item;
190 int shortest_stem = -1;
191 int current_stem;
192 int other;
193 guchar *text, *leaf;
195 if (cursor < 0 || cursor >= collection->number_of_items)
197 gdk_beep();
198 return;
201 entry = GTK_ENTRY(filer_window->minibuffer);
203 item = (DirItem *) collection->items[cursor].data;
205 text = gtk_entry_get_text(entry);
206 leaf = strrchr(text, '/');
207 if (!leaf)
209 gdk_beep();
210 return;
213 leaf++;
214 if (!matches(collection, cursor, leaf))
216 gdk_beep();
217 return;
220 current_stem = strlen(leaf);
222 /* Find the longest other match of this name. It it's longer than
223 * the currently entered text then complete only up to that length.
225 for (other = 0; other < collection->number_of_items; other++)
227 DirItem *other_item = (DirItem *) collection->items[other].data;
228 int stem = 0;
230 if (other == cursor)
231 continue;
233 while (other_item->leafname[stem] && item->leafname[stem])
235 if (other_item->leafname[stem] != item->leafname[stem])
236 break;
237 stem++;
240 /* stem is the index of the first difference */
241 if (stem >= current_stem &&
242 (shortest_stem == -1 || stem < shortest_stem))
243 shortest_stem = stem;
246 if (current_stem == shortest_stem)
247 gdk_beep();
248 else if (current_stem < shortest_stem)
250 guchar *extra;
252 extra = g_strndup(item->leafname + current_stem,
253 shortest_stem - current_stem);
254 gtk_entry_append_text(entry, extra);
255 g_free(extra);
256 gdk_beep();
258 else
260 GString *new;
262 new = make_path(filer_window->path, item->leafname);
264 if (item->base_type == TYPE_DIRECTORY &&
265 (item->flags & ITEM_FLAG_APPDIR) == 0)
266 g_string_append_c(new, '/');
268 gtk_entry_set_text(entry, new->str);
272 static void path_changed(GtkEditable *mini, FilerWindow *filer_window)
274 char *new, *slash;
275 char *path, *real;
277 new = gtk_entry_get_text(GTK_ENTRY(mini));
278 if (*new == '/')
279 new = g_strdup(new);
280 else
281 new = g_strdup_printf("/%s", new);
283 slash = strrchr(new, '/');
284 *slash = '\0';
286 if (*new == '\0')
287 path = "/";
288 else
289 path = new;
291 real = pathdup(path);
293 if (strcmp(real, filer_window->path) != 0)
295 struct stat info;
296 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
297 filer_change_to(filer_window, real, slash + 1);
298 else
299 gdk_beep();
301 else
303 Collection *collection = filer_window->collection;
304 int item;
306 find_next_match(filer_window, slash + 1, 0);
307 item = collection->cursor_item;
308 if (item != -1 && !matches(collection, item, slash + 1))
309 gdk_beep();
312 g_free(real);
313 g_free(new);
316 /* Find the next item in the collection that matches 'pattern'. Start from
317 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
318 * -1 for backwards. 0 means forwards, but may stay the same.
320 * Does not automatically update mini_cursor_base.
322 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
324 Collection *collection = filer_window->collection;
325 int base = filer_window->mini_cursor_base;
326 int item = base;
328 if (collection->number_of_items < 1)
329 return;
331 if (base < 0 || base>= collection->number_of_items)
332 filer_window->mini_cursor_base = base = 0;
336 /* Step to the next item */
337 item += dir;
339 if (item >= collection->number_of_items)
340 item = 0;
341 else if (item < 0)
342 item = collection->number_of_items - 1;
344 if (dir == 0)
345 dir = 1;
346 else if (item == base)
347 break; /* No (other) matches at all */
350 } while (!matches(collection, item, pattern));
352 collection_set_cursor_item(collection, item);
355 static gboolean matches(Collection *collection, int item_number, char *pattern)
357 DirItem *item = (DirItem *) collection->items[item_number].data;
359 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
362 /* Find next match and set base for future matches. */
363 static void search_in_dir(FilerWindow *filer_window, int dir)
365 char *path, *pattern;
367 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
368 pattern = strrchr(path, '/');
369 if (pattern)
370 pattern++;
371 else
372 pattern = path;
374 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
375 find_next_match(filer_window, pattern, dir);
376 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
379 /* SHELL COMMANDS */
381 static void add_to_history(guchar *line)
383 guchar *last;
385 last = shell_history ? (guchar *) shell_history->data : NULL;
387 if (last && strcmp(last, line) == 0)
388 return; /* Duplicating last entry */
390 shell_history = g_list_prepend(shell_history, g_strdup(line));
393 static void shell_done(FilerWindow *filer_window)
395 if (filer_exists(filer_window))
396 filer_update_dir(filer_window, TRUE);
399 /* Given a list of matches, return the longest stem. g_free() the result.
400 * Special chars are escaped. If there is only a single (non-dir) match
401 * then a trailing space is added.
403 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
405 gchar *first = matches->gl_pathv[0];
406 int i;
407 int longest, path_len;
408 guchar *path, *tmp;
410 longest = strlen(first);
412 for (i = 1; i < matches->gl_pathc; i++)
414 int j;
415 guchar *m = matches->gl_pathv[i];
417 for (j = 0; j < longest; j++)
418 if (m[j] != first[j])
419 longest = j;
422 path_len = strlen(filer_window->path);
423 if (strncmp(filer_window->path, first, path_len) == 0 &&
424 first[path_len] == '/' && first[path_len + 1])
426 path = g_strndup(first + path_len + 1, longest - path_len - 1);
428 else
429 path = g_strndup(first, longest);
431 tmp = shell_escape(path);
432 g_free(path);
434 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
436 path = g_strdup_printf("%s ", tmp);
437 g_free(tmp);
438 return path;
441 return tmp;
444 static void shell_tab(FilerWindow *filer_window)
446 int i;
447 guchar *entry;
448 guchar quote;
449 int pos;
450 GString *leaf;
451 glob_t matches;
452 int leaf_start;
454 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
455 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
456 leaf = g_string_new(NULL);
458 quote = '\0';
459 for (i = 0; i < pos; i++)
461 guchar c = entry[i];
463 if (leaf->len == 0)
464 leaf_start = i;
466 if (c == ' ')
468 g_string_truncate(leaf, 0);
469 continue;
471 else if (c == '\\' && i + 1 < pos)
472 c = entry[++i];
473 else if (c == '"' || c == '\'')
475 guchar cc;
477 for (++i; i < pos; i++)
479 cc = entry[i];
481 if (cc == '\\' && i + 1 < pos)
482 cc = entry[++i];
483 else if (entry[i] == c)
484 break;
485 g_string_append_c(leaf, entry[i]);
487 continue;
490 g_string_append_c(leaf, c);
493 if (leaf->len == 0)
494 leaf_start = pos;
496 if (leaf->str[0] != '/')
498 g_string_prepend_c(leaf, '/');
499 g_string_prepend(leaf, filer_window->path);
502 g_string_append_c(leaf, '*');
504 if (glob(leaf->str,
505 #ifdef GLOB_TILDE
506 GLOB_TILDE |
507 #endif
508 GLOB_MARK, NULL, &matches) == 0)
510 if (matches.gl_pathc > 0)
512 guchar *best;
513 GtkEditable *edit =
514 GTK_EDITABLE(filer_window->minibuffer);
516 best = best_match(filer_window, &matches);
518 gtk_editable_delete_text(edit, leaf_start, pos);
519 gtk_editable_insert_text(edit, best, strlen(best),
520 &leaf_start);
521 gtk_editable_set_position(edit, leaf_start);
523 g_free(best);
525 if (matches.gl_pathc != 1)
526 gdk_beep();
528 globfree(&matches);
531 g_string_free(leaf, TRUE);
534 static void shell_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
536 GPtrArray *argv;
537 int i;
538 guchar *entry;
539 Collection *collection = filer_window->collection;
540 int child;
542 entry = mini_contents(filer_window);
544 if (!entry)
545 goto out;
547 add_to_history(entry);
549 argv = g_ptr_array_new();
550 g_ptr_array_add(argv, "sh");
551 g_ptr_array_add(argv, "-c");
552 g_ptr_array_add(argv, entry);
553 g_ptr_array_add(argv, "sh");
555 for (i = 0; i < collection->number_of_items; i++)
557 DirItem *item = (DirItem *) collection->items[i].data;
558 if (collection->items[i].selected)
559 g_ptr_array_add(argv, item->leafname);
562 g_ptr_array_add(argv, NULL);
564 child = fork();
566 switch (child)
568 case -1:
569 delayed_error("ROX-Filer", "Failed to create "
570 "child process");
571 break;
572 case 0: /* Child */
573 dup2(to_error_log, STDOUT_FILENO);
574 close_on_exec(STDOUT_FILENO, FALSE);
575 dup2(to_error_log, STDERR_FILENO);
576 close_on_exec(STDERR_FILENO, FALSE);
577 if (chdir(filer_window->path))
578 g_printerr("chdir(%s) failed: %s\n",
579 filer_window->path,
580 g_strerror(errno));
581 execvp((char *) argv->pdata[0],
582 (char **) argv->pdata);
583 g_printerr("execvp(%s, ...) failed: %s\n",
584 (char *) argv->pdata[0],
585 g_strerror(errno));
586 _exit(0);
587 default:
588 on_child_death(child,
589 (CallbackFn) shell_done, filer_window);
590 break;
593 g_ptr_array_free(argv, TRUE);
595 out:
596 minibuffer_hide(filer_window);
599 /* Move through the shell history */
600 static void shell_recall(FilerWindow *filer_window, int dir)
602 guchar *command;
603 int pos = filer_window->mini_cursor_base;
605 pos += dir;
606 if (pos >= 0)
608 command = g_list_nth_data(shell_history, pos);
609 if (!command)
610 return;
612 else
613 command = "";
615 if (pos < -1)
616 pos = -1;
617 filer_window->mini_cursor_base = pos;
619 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
622 /* EVENT HANDLERS */
624 static gint key_press_event(GtkWidget *widget,
625 GdkEventKey *event,
626 FilerWindow *filer_window)
628 if (event->keyval == GDK_Escape)
630 if (filer_window->mini_type == MINI_SHELL)
632 guchar *line;
634 line = mini_contents(filer_window);
635 if (line)
636 add_to_history(line);
639 minibuffer_hide(filer_window);
640 return TRUE;
643 switch (filer_window->mini_type)
645 case MINI_PATH:
646 switch (event->keyval)
648 case GDK_Up:
649 search_in_dir(filer_window, -1);
650 break;
651 case GDK_Down:
652 search_in_dir(filer_window, 1);
653 break;
654 case GDK_Return:
655 path_return_pressed(filer_window,
656 event);
657 break;
658 case GDK_Tab:
659 complete(filer_window);
660 break;
661 default:
662 return FALSE;
664 break;
666 case MINI_SHELL:
667 switch (event->keyval)
669 case GDK_Up:
670 shell_recall(filer_window, 1);
671 break;
672 case GDK_Down:
673 shell_recall(filer_window, -1);
674 break;
675 case GDK_Tab:
676 shell_tab(filer_window);
677 break;
678 case GDK_Return:
679 shell_return_pressed(filer_window,
680 event);
681 default:
682 return FALSE;
684 break;
685 default:
686 break;
689 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
690 return TRUE;
693 static void changed(GtkEditable *mini, FilerWindow *filer_window)
695 switch (filer_window->mini_type)
697 case MINI_PATH:
698 path_changed(mini, filer_window);
699 return;
700 case MINI_SHELL:
701 break;
702 default:
703 break;
707 /* Returns a string (which must NOT be freed), or NULL if the buffer
708 * is blank (whitespace only).
710 static guchar *mini_contents(FilerWindow *filer_window)
712 guchar *entry, *c;
714 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
716 for (c = entry; *c; c++)
717 if (!isspace(*c))
718 return entry;
720 return NULL;