r288: Removed the borders from the toolbar buttons and put the new help icon
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blob901046928a439c8b99b53d78da9917f6c25a8150
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>
30 #include <stdio.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
35 #include "collection.h"
36 #include "find.h"
37 #include "gui_support.h"
38 #include "support.h"
39 #include "minibuffer.h"
40 #include "filer.h"
41 #include "main.h"
42 #include "action.h"
44 static GList *shell_history = NULL;
46 /* Static prototypes */
47 static gint key_press_event(GtkWidget *widget,
48 GdkEventKey *event,
49 FilerWindow *filer_window);
50 static void changed(GtkEditable *mini, FilerWindow *filer_window);
51 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
52 static gboolean matches(Collection *collection, int item, char *pattern);
53 static void search_in_dir(FilerWindow *filer_window, int dir);
54 static guchar *mini_contents(FilerWindow *filer_window);
55 static void show_help(FilerWindow *filer_window);
58 /****************************************************************
59 * EXTERNAL INTERFACE *
60 ****************************************************************/
63 /* Creates the minibuffer widgets, setting the appropriate fields
64 * in filer_window.
66 void create_minibuffer(FilerWindow *filer_window)
68 GtkWidget *hbox, *label, *mini;
70 hbox = gtk_hbox_new(FALSE, 0);
72 gtk_box_pack_start(GTK_BOX(hbox),
73 new_help_button((HelpFunc) show_help, filer_window),
74 FALSE, TRUE, 0);
76 label = gtk_label_new(NULL);
77 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
79 mini = gtk_entry_new();
80 gtk_box_pack_start(GTK_BOX(hbox), mini, TRUE, TRUE, 0);
81 gtk_widget_set_style(mini, fixed_style);
82 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
83 GTK_SIGNAL_FUNC(key_press_event), filer_window);
84 gtk_signal_connect(GTK_OBJECT(mini), "changed",
85 GTK_SIGNAL_FUNC(changed), filer_window);
87 filer_window->minibuffer = mini;
88 filer_window->minibuffer_label = label;
89 filer_window->minibuffer_area = hbox;
92 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
94 Collection *collection;
95 GtkEntry *mini;
97 g_return_if_fail(filer_window != NULL);
98 g_return_if_fail(filer_window->minibuffer != NULL);
100 mini = GTK_ENTRY(filer_window->minibuffer);
102 filer_window->mini_type = MINI_NONE;
103 gtk_label_set_text(GTK_LABEL(filer_window->minibuffer_label),
104 mini_type == MINI_PATH ? _("Goto:") :
105 mini_type == MINI_SHELL ? _("Shell:") :
106 mini_type == MINI_RUN_ACTION ? _("Run Action:") :
107 mini_type == MINI_SELECT_IF ? _("Select If:") :
108 "?");
110 collection = filer_window->collection;
111 collection_move_cursor(collection, 0, 0); /* Turn the cursor on */
112 switch (mini_type)
114 case MINI_PATH:
115 filer_window->mini_cursor_base =
116 MAX(collection->cursor_item, 0);
117 gtk_entry_set_text(mini,
118 make_path(filer_window->path, "")->str);
119 break;
120 case MINI_SHELL:
121 case MINI_RUN_ACTION:
122 case MINI_SELECT_IF:
123 filer_window->mini_cursor_base = -1; /* History */
124 gtk_entry_set_text(mini, "");
125 break;
126 default:
127 g_warning("Bad minibuffer type\n");
128 return;
131 filer_window->mini_type = mini_type;
133 gtk_entry_set_position(mini, -1);
135 gtk_widget_show_all(filer_window->minibuffer_area);
137 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
138 filer_window->minibuffer);
141 void minibuffer_hide(FilerWindow *filer_window)
143 filer_window->mini_type = MINI_NONE;
145 gtk_widget_hide(filer_window->minibuffer_area);
146 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
147 GTK_WIDGET(filer_window->collection));
150 /* Insert this leafname at the cursor (replacing the selection, if any).
151 * Must be in SHELL or RUN_ACTION mode.
153 void minibuffer_add(FilerWindow *filer_window, guchar *leafname)
155 guchar *esc;
156 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
157 GtkEntry *entry = GTK_ENTRY(edit);
158 int pos;
160 g_return_if_fail(filer_window->mini_type == MINI_SHELL ||
161 filer_window->mini_type == MINI_RUN_ACTION);
163 esc = shell_escape(leafname);
165 gtk_editable_delete_selection(edit);
166 pos = gtk_editable_get_position(edit);
168 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
169 gtk_editable_insert_text(edit, " ", 1, &pos);
171 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
172 gtk_editable_set_position(edit, pos);
174 g_free(esc);
178 /****************************************************************
179 * INTERNAL FUNCTIONS *
180 ****************************************************************/
182 static void show_help(FilerWindow *filer_window)
184 guchar *message;
186 gtk_widget_grab_focus(filer_window->minibuffer);
188 switch (filer_window->mini_type)
190 case MINI_PATH:
191 message = _("Enter the name of a file and I'll display "
192 "it for you. Press Tab to fill in the longest "
193 "match. Escape to close the minibuffer.");
194 break;
195 case MINI_SHELL:
196 message = _("Enter a shell command to execute. Click "
197 "on a file to add it to the buffer.");
198 break;
199 case MINI_RUN_ACTION:
200 message =
201 _("To set the run action for a file, either:\n"
202 "- Drag a file to an application directory (eg drag an image to the "
203 "Gimp), or\n" "- Enter a shell command which contains a \"$1\""
204 "where the name of the file should go (eg ` gimp \"$1\" ')");
205 break;
206 case MINI_SELECT_IF:
207 show_condition_help(NULL);
208 return;
209 default:
210 message = "?!?";
211 break;
214 delayed_error(PROJECT, message);
218 /* PATH ENTRY */
220 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
222 Collection *collection = filer_window->collection;
223 int item = collection->cursor_item;
224 char *path, *pattern;
225 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
227 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
228 pattern = strrchr(path, '/');
229 if (pattern)
230 pattern++;
231 else
232 pattern = path;
234 if (item == -1 || item >= collection->number_of_items ||
235 !matches(collection, item, pattern))
237 gdk_beep();
238 return;
241 if ((event->state & GDK_SHIFT_MASK) != 0)
242 flags |= OPEN_SHIFT;
244 filer_openitem(filer_window, item, flags);
247 /* Use the cursor item to fill in the minibuffer.
248 * If there are multiple matches the fill in as much as possible and beep.
250 static void complete(FilerWindow *filer_window)
252 GtkEntry *entry;
253 Collection *collection = filer_window->collection;
254 int cursor = collection->cursor_item;
255 DirItem *item;
256 int shortest_stem = -1;
257 int current_stem;
258 int other;
259 guchar *text, *leaf;
261 if (cursor < 0 || cursor >= collection->number_of_items)
263 gdk_beep();
264 return;
267 entry = GTK_ENTRY(filer_window->minibuffer);
269 item = (DirItem *) collection->items[cursor].data;
271 text = gtk_entry_get_text(entry);
272 leaf = strrchr(text, '/');
273 if (!leaf)
275 gdk_beep();
276 return;
279 leaf++;
280 if (!matches(collection, cursor, leaf))
282 gdk_beep();
283 return;
286 current_stem = strlen(leaf);
288 /* Find the longest other match of this name. It it's longer than
289 * the currently entered text then complete only up to that length.
291 for (other = 0; other < collection->number_of_items; other++)
293 DirItem *other_item = (DirItem *) collection->items[other].data;
294 int stem = 0;
296 if (other == cursor)
297 continue;
299 while (other_item->leafname[stem] && item->leafname[stem])
301 if (other_item->leafname[stem] != item->leafname[stem])
302 break;
303 stem++;
306 /* stem is the index of the first difference */
307 if (stem >= current_stem &&
308 (shortest_stem == -1 || stem < shortest_stem))
309 shortest_stem = stem;
312 if (current_stem == shortest_stem)
313 gdk_beep();
314 else if (current_stem < shortest_stem)
316 guchar *extra;
318 extra = g_strndup(item->leafname + current_stem,
319 shortest_stem - current_stem);
320 gtk_entry_append_text(entry, extra);
321 g_free(extra);
322 gdk_beep();
324 else
326 GString *new;
328 new = make_path(filer_window->path, item->leafname);
330 if (item->base_type == TYPE_DIRECTORY &&
331 (item->flags & ITEM_FLAG_APPDIR) == 0)
332 g_string_append_c(new, '/');
334 gtk_entry_set_text(entry, new->str);
338 static void path_changed(GtkEditable *mini, FilerWindow *filer_window)
340 char *new, *slash;
341 char *path, *real;
343 new = gtk_entry_get_text(GTK_ENTRY(mini));
344 if (*new == '/')
345 new = g_strdup(new);
346 else
347 new = g_strdup_printf("/%s", new);
349 slash = strrchr(new, '/');
350 *slash = '\0';
352 if (*new == '\0')
353 path = "/";
354 else
355 path = new;
357 real = pathdup(path);
359 if (strcmp(real, filer_window->path) != 0)
361 struct stat info;
362 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
363 filer_change_to(filer_window, real, slash + 1);
364 else
365 gdk_beep();
367 else
369 Collection *collection = filer_window->collection;
370 int item;
372 find_next_match(filer_window, slash + 1, 0);
373 item = collection->cursor_item;
374 if (item != -1 && !matches(collection, item, slash + 1))
375 gdk_beep();
378 g_free(real);
379 g_free(new);
382 /* Find the next item in the collection that matches 'pattern'. Start from
383 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
384 * -1 for backwards. 0 means forwards, but may stay the same.
386 * Does not automatically update mini_cursor_base.
388 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
390 Collection *collection = filer_window->collection;
391 int base = filer_window->mini_cursor_base;
392 int item = base;
394 if (collection->number_of_items < 1)
395 return;
397 if (base < 0 || base>= collection->number_of_items)
398 filer_window->mini_cursor_base = base = 0;
402 /* Step to the next item */
403 item += dir;
405 if (item >= collection->number_of_items)
406 item = 0;
407 else if (item < 0)
408 item = collection->number_of_items - 1;
410 if (dir == 0)
411 dir = 1;
412 else if (item == base)
413 break; /* No (other) matches at all */
416 } while (!matches(collection, item, pattern));
418 collection_set_cursor_item(collection, item);
421 static gboolean matches(Collection *collection, int item_number, char *pattern)
423 DirItem *item = (DirItem *) collection->items[item_number].data;
425 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
428 /* Find next match and set base for future matches. */
429 static void search_in_dir(FilerWindow *filer_window, int dir)
431 char *path, *pattern;
433 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
434 pattern = strrchr(path, '/');
435 if (pattern)
436 pattern++;
437 else
438 pattern = path;
440 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
441 find_next_match(filer_window, pattern, dir);
442 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
445 /* SHELL COMMANDS */
447 static void add_to_history(guchar *line)
449 guchar *last;
451 last = shell_history ? (guchar *) shell_history->data : NULL;
453 if (last && strcmp(last, line) == 0)
454 return; /* Duplicating last entry */
456 shell_history = g_list_prepend(shell_history, g_strdup(line));
459 static void shell_done(FilerWindow *filer_window)
461 if (filer_exists(filer_window))
462 filer_update_dir(filer_window, TRUE);
465 /* Given a list of matches, return the longest stem. g_free() the result.
466 * Special chars are escaped. If there is only a single (non-dir) match
467 * then a trailing space is added.
469 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
471 gchar *first = matches->gl_pathv[0];
472 int i;
473 int longest, path_len;
474 guchar *path, *tmp;
476 longest = strlen(first);
478 for (i = 1; i < matches->gl_pathc; i++)
480 int j;
481 guchar *m = matches->gl_pathv[i];
483 for (j = 0; j < longest; j++)
484 if (m[j] != first[j])
485 longest = j;
488 path_len = strlen(filer_window->path);
489 if (strncmp(filer_window->path, first, path_len) == 0 &&
490 first[path_len] == '/' && first[path_len + 1])
492 path = g_strndup(first + path_len + 1, longest - path_len - 1);
494 else
495 path = g_strndup(first, longest);
497 tmp = shell_escape(path);
498 g_free(path);
500 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
502 path = g_strdup_printf("%s ", tmp);
503 g_free(tmp);
504 return path;
507 return tmp;
510 static void shell_tab(FilerWindow *filer_window)
512 int i;
513 guchar *entry;
514 guchar quote;
515 int pos;
516 GString *leaf;
517 glob_t matches;
518 int leaf_start;
520 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
521 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
522 leaf = g_string_new(NULL);
524 quote = '\0';
525 for (i = 0; i < pos; i++)
527 guchar c = entry[i];
529 if (leaf->len == 0)
530 leaf_start = i;
532 if (c == ' ')
534 g_string_truncate(leaf, 0);
535 continue;
537 else if (c == '\\' && i + 1 < pos)
538 c = entry[++i];
539 else if (c == '"' || c == '\'')
541 guchar cc;
543 for (++i; i < pos; i++)
545 cc = entry[i];
547 if (cc == '\\' && i + 1 < pos)
548 cc = entry[++i];
549 else if (entry[i] == c)
550 break;
551 g_string_append_c(leaf, entry[i]);
553 continue;
556 g_string_append_c(leaf, c);
559 if (leaf->len == 0)
560 leaf_start = pos;
562 if (leaf->str[0] != '/')
564 g_string_prepend_c(leaf, '/');
565 g_string_prepend(leaf, filer_window->path);
568 g_string_append_c(leaf, '*');
570 if (glob(leaf->str,
571 #ifdef GLOB_TILDE
572 GLOB_TILDE |
573 #endif
574 GLOB_MARK, NULL, &matches) == 0)
576 if (matches.gl_pathc > 0)
578 guchar *best;
579 GtkEditable *edit =
580 GTK_EDITABLE(filer_window->minibuffer);
582 best = best_match(filer_window, &matches);
584 gtk_editable_delete_text(edit, leaf_start, pos);
585 gtk_editable_insert_text(edit, best, strlen(best),
586 &leaf_start);
587 gtk_editable_set_position(edit, leaf_start);
589 g_free(best);
591 if (matches.gl_pathc != 1)
592 gdk_beep();
594 globfree(&matches);
597 g_string_free(leaf, TRUE);
600 /* This is called from shell_return_pressed() so that the command is already
601 * in the history. Returns TRUE if the buffer should be closed.
603 gboolean set_run_action(FilerWindow *filer_window, guchar *command)
605 Collection *collection = filer_window->collection;
606 int n = collection->cursor_item;
607 DirItem *item;
608 guchar *path, *tmp;
609 FILE *file;
610 int error = 0, len;
612 if (!strchr(command, '$'))
614 show_help(filer_window);
615 return FALSE;
618 if (n < 0 || n >= collection->number_of_items)
620 delayed_error(PROJECT,
621 _("You must have the cursor on the item to use for '$1'."));
622 return FALSE;
625 item = (DirItem *) collection->items[n].data;
626 path = type_ask_which_action(item->mime_type->media_type,
627 item->mime_type->subtype);
629 if (!path)
630 return TRUE;
632 tmp = g_strdup_printf("#! /bin/sh\nexec %s\n", command);
633 len = strlen(tmp);
635 file = fopen(path, "wb");
636 if (fwrite(tmp, 1, len, file) < len)
637 error = errno;
638 if (fclose(file) && error == 0)
639 error = errno;
640 if (chmod(path, 0777))
641 error = errno;
643 if (error)
644 report_error(PROJECT, g_strerror(errno));
646 g_free(tmp);
648 return TRUE;
651 /* Either execute the command or make it the default run action */
652 static void shell_return_pressed(FilerWindow *filer_window)
654 GPtrArray *argv;
655 int i;
656 guchar *entry;
657 Collection *collection = filer_window->collection;
658 int child;
660 entry = mini_contents(filer_window);
662 if (!entry)
663 goto out;
665 add_to_history(entry);
667 if (filer_window->mini_type == MINI_RUN_ACTION)
669 if (set_run_action(filer_window, entry))
670 goto out;
671 else
672 return;
675 argv = g_ptr_array_new();
676 g_ptr_array_add(argv, "sh");
677 g_ptr_array_add(argv, "-c");
678 g_ptr_array_add(argv, entry);
679 g_ptr_array_add(argv, "sh");
681 for (i = 0; i < collection->number_of_items; i++)
683 DirItem *item = (DirItem *) collection->items[i].data;
684 if (collection->items[i].selected)
685 g_ptr_array_add(argv, item->leafname);
688 g_ptr_array_add(argv, NULL);
690 child = fork();
692 switch (child)
694 case -1:
695 delayed_error(PROJECT, _("Failed to create "
696 "child process"));
697 break;
698 case 0: /* Child */
699 dup2(to_error_log, STDOUT_FILENO);
700 close_on_exec(STDOUT_FILENO, FALSE);
701 dup2(to_error_log, STDERR_FILENO);
702 close_on_exec(STDERR_FILENO, FALSE);
703 if (chdir(filer_window->path))
704 g_printerr("chdir(%s) failed: %s\n",
705 filer_window->path,
706 g_strerror(errno));
707 execvp((char *) argv->pdata[0],
708 (char **) argv->pdata);
709 g_printerr("execvp(%s, ...) failed: %s\n",
710 (char *) argv->pdata[0],
711 g_strerror(errno));
712 _exit(0);
713 default:
714 on_child_death(child,
715 (CallbackFn) shell_done, filer_window);
716 break;
719 g_ptr_array_free(argv, TRUE);
721 out:
722 minibuffer_hide(filer_window);
725 /* Move through the shell history */
726 static void shell_recall(FilerWindow *filer_window, int dir)
728 guchar *command;
729 int pos = filer_window->mini_cursor_base;
731 pos += dir;
732 if (pos >= 0)
734 command = g_list_nth_data(shell_history, pos);
735 if (!command)
736 return;
738 else
739 command = "";
741 if (pos < -1)
742 pos = -1;
743 filer_window->mini_cursor_base = pos;
745 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
748 /* SELECT IF */
750 static void select_return_pressed(FilerWindow *filer_window)
752 FindCondition *cond;
753 int i, n;
754 guchar *entry;
755 Collection *collection = filer_window->collection;
756 FindInfo info;
758 entry = mini_contents(filer_window);
760 if (!entry)
761 goto out;
763 add_to_history(entry);
765 cond = find_compile(entry);
766 if (!cond)
768 delayed_error(PROJECT, "Invalid Find condition");
769 return;
772 info.now = time(NULL);
773 info.prune = FALSE; /* (don't care) */
774 n = collection->number_of_items;
776 /* If an item at the start is selected then we could lose the
777 * primary selection after checking that item and then need to
778 * gain it again at the end. Therefore, if anything is selected
779 * then select the last item until the end of the search.
781 if (collection->number_selected)
782 collection_select_item(collection, n - 1);
784 for (i = 0; i < n; i++)
786 DirItem *item = (DirItem *) collection->items[i].data;
788 info.leaf = item->leafname;
789 info.fullpath = make_path(filer_window->path, info.leaf)->str;
791 if (lstat(info.fullpath, &info.stats) == 0 &&
792 find_test_condition(cond, &info))
793 collection_select_item(collection, i);
794 else
795 collection_unselect_item(collection, i);
798 find_condition_free(cond);
800 out:
801 minibuffer_hide(filer_window);
805 /* EVENT HANDLERS */
807 static gint key_press_event(GtkWidget *widget,
808 GdkEventKey *event,
809 FilerWindow *filer_window)
811 if (event->keyval == GDK_Escape)
813 if (filer_window->mini_type == MINI_SHELL)
815 guchar *line;
817 line = mini_contents(filer_window);
818 if (line)
819 add_to_history(line);
822 minibuffer_hide(filer_window);
823 return TRUE;
826 switch (filer_window->mini_type)
828 case MINI_PATH:
829 switch (event->keyval)
831 case GDK_Up:
832 search_in_dir(filer_window, -1);
833 break;
834 case GDK_Down:
835 search_in_dir(filer_window, 1);
836 break;
837 case GDK_Return:
838 path_return_pressed(filer_window,
839 event);
840 break;
841 case GDK_Tab:
842 complete(filer_window);
843 break;
844 default:
845 return FALSE;
847 break;
849 case MINI_SHELL:
850 case MINI_RUN_ACTION:
851 switch (event->keyval)
853 case GDK_Up:
854 shell_recall(filer_window, 1);
855 break;
856 case GDK_Down:
857 shell_recall(filer_window, -1);
858 break;
859 case GDK_Tab:
860 shell_tab(filer_window);
861 break;
862 case GDK_Return:
863 shell_return_pressed(filer_window);
864 break;
865 default:
866 return FALSE;
868 break;
869 case MINI_SELECT_IF:
870 switch (event->keyval)
872 case GDK_Up:
873 shell_recall(filer_window, 1);
874 break;
875 case GDK_Down:
876 shell_recall(filer_window, -1);
877 break;
878 case GDK_Tab:
879 break;
880 case GDK_Return:
881 select_return_pressed(filer_window);
882 break;
883 default:
884 return FALSE;
886 break;
887 default:
888 break;
891 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
892 return TRUE;
895 static void changed(GtkEditable *mini, FilerWindow *filer_window)
897 switch (filer_window->mini_type)
899 case MINI_PATH:
900 path_changed(mini, filer_window);
901 return;
902 default:
903 break;
907 /* Returns a string (which must NOT be freed), or NULL if the buffer
908 * is blank (whitespace only).
910 static guchar *mini_contents(FilerWindow *filer_window)
912 guchar *entry, *c;
914 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
916 for (c = entry; *c; c++)
917 if (!isspace(*c))
918 return entry;
920 return NULL;