Converted README to markdown
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blob002b53e9dd2f37e7e7c6077297de3aac9ddcc4d5
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* minibuffer.c - for handling the path entry box at the bottom */
22 #include "config.h"
24 #include <fnmatch.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <glob.h>
29 #include <stdio.h>
31 #include <sys/types.h>
32 #include <pwd.h>
34 #include <gtk/gtk.h>
35 #include <gdk/gdkkeysyms.h>
37 #include "global.h"
39 #include "options.h"
40 #include "find.h"
41 #include "gui_support.h"
42 #include "support.h"
43 #include "minibuffer.h"
44 #include "filer.h"
45 #include "display.h"
46 #include "main.h"
47 #include "action.h"
48 #include "diritem.h"
49 #include "type.h"
50 #include "view_iface.h"
52 static GList *shell_history = NULL;
54 /* Static prototypes */
55 static gint key_press_event(GtkWidget *widget,
56 GdkEventKey *event,
57 FilerWindow *filer_window);
58 static void changed(GtkEditable *mini, FilerWindow *filer_window);
59 static gboolean find_next_match(FilerWindow *filer_window,
60 const char *pattern,
61 int dir);
62 static gboolean find_exact_match(FilerWindow *filer_window,
63 const gchar *pattern);
64 static gboolean matches(ViewIter *iter, const char *pattern);
65 static void search_in_dir(FilerWindow *filer_window, int dir);
66 static const gchar *mini_contents(FilerWindow *filer_window);
67 static void show_help(FilerWindow *filer_window);
68 static gboolean grab_focus(GtkWidget *minibuffer);
69 static gboolean select_if_glob(ViewIter *iter, gpointer data);
71 /****************************************************************
72 * EXTERNAL INTERFACE *
73 ****************************************************************/
75 static Option o_filer_beep_fail, o_filer_beep_multi;
77 void minibuffer_init(void)
79 option_add_int(&o_filer_beep_fail, "filer_beep_fail", 1);
80 option_add_int(&o_filer_beep_multi, "filer_beep_multi", 1);
83 /* Creates the minibuffer widgets, setting the appropriate fields
84 * in filer_window.
86 void create_minibuffer(FilerWindow *filer_window)
88 GtkWidget *hbox, *label, *mini;
90 hbox = gtk_hbox_new(FALSE, 0);
92 gtk_box_pack_start(GTK_BOX(hbox),
93 new_help_button((HelpFunc) show_help, filer_window),
94 FALSE, TRUE, 0);
96 label = gtk_label_new("");
97 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
99 mini = gtk_entry_new();
100 gtk_box_pack_start(GTK_BOX(hbox), mini, TRUE, TRUE, 0);
101 gtk_widget_set_name(mini, "fixed-style");
102 g_signal_connect(mini, "key_press_event",
103 G_CALLBACK(key_press_event), filer_window);
104 g_signal_connect(mini, "changed", G_CALLBACK(changed), filer_window);
106 /* Grabbing focus musn't select the text... */
107 g_signal_connect_swapped(mini, "grab-focus",
108 G_CALLBACK(grab_focus), mini);
110 filer_window->minibuffer = mini;
111 filer_window->minibuffer_label = label;
112 filer_window->minibuffer_area = hbox;
115 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
117 GtkEntry *mini;
118 int pos = -1;
119 ViewIter cursor;
121 g_return_if_fail(filer_window != NULL);
122 g_return_if_fail(filer_window->minibuffer != NULL);
124 mini = GTK_ENTRY(filer_window->minibuffer);
125 entry_set_error(filer_window->minibuffer, FALSE);
127 filer_window->mini_type = MINI_NONE;
128 gtk_label_set_text(GTK_LABEL(filer_window->minibuffer_label),
129 mini_type == MINI_PATH ? _("Goto:") :
130 mini_type == MINI_SHELL ? _("Shell:") :
131 mini_type == MINI_SELECT_IF ? _("Select If:") :
132 mini_type == MINI_SELECT_BY_NAME ? _("Select Named:") :
133 mini_type == MINI_FILTER ? _("Pattern:") :
134 "?");
136 switch (mini_type)
138 case MINI_PATH:
139 view_show_cursor(filer_window->view);
140 view_get_cursor(filer_window->view, &cursor);
141 view_set_base(filer_window->view, &cursor);
143 gtk_entry_set_text(mini,
144 make_path(filer_window->sym_path, ""));
145 if (filer_window->temp_show_hidden)
147 filer_window->temp_show_hidden = FALSE;
148 display_update_hidden(filer_window);
150 break;
151 case MINI_SELECT_IF:
152 gtk_entry_set_text(mini, "");
153 filer_window->mini_cursor_base = -1; /* History */
154 break;
155 case MINI_SELECT_BY_NAME:
156 view_show_cursor(filer_window->view);
157 view_get_cursor(filer_window->view, &cursor);
158 view_set_base(filer_window->view, &cursor);
160 gtk_entry_set_text(mini, "*.");
161 filer_window->mini_cursor_base = -1; /* History */
162 view_select_if(filer_window->view, select_if_glob, "*.");
163 break;
164 case MINI_FILTER:
165 if(filer_window->filter!=FILER_SHOW_GLOB ||
166 !filer_window->filter_string)
167 gtk_entry_set_text(mini, "*");
168 else
169 gtk_entry_set_text(mini,
170 filer_window->filter_string);
171 break;
172 case MINI_SHELL:
174 DirItem *item;
175 view_get_cursor(filer_window->view, &cursor);
176 item = cursor.peek(&cursor);
177 pos = 0;
178 if (view_count_selected(filer_window->view) > 0)
179 gtk_entry_set_text(mini, " \"$@\"");
180 else if (item)
182 guchar *escaped;
183 guchar *tmp;
185 escaped = shell_escape(item->leafname);
186 tmp = g_strconcat(" ", escaped, NULL);
187 g_free(escaped);
188 gtk_entry_set_text(mini, tmp);
189 g_free(tmp);
191 else
192 gtk_entry_set_text(mini, "");
193 filer_window->mini_cursor_base = -1; /* History */
194 break;
196 default:
197 g_warning("Bad minibuffer type\n");
198 return;
201 filer_window->mini_type = mini_type;
203 gtk_editable_set_position(GTK_EDITABLE(mini), pos);
205 gtk_widget_show_all(filer_window->minibuffer_area);
207 gtk_widget_grab_focus(filer_window->minibuffer);
210 void minibuffer_hide(FilerWindow *filer_window)
212 filer_window->mini_type = MINI_NONE;
214 gtk_widget_hide(filer_window->minibuffer_area);
216 gtk_widget_child_focus(filer_window->window, GTK_DIR_TAB_FORWARD);
218 if (filer_window->temp_show_hidden)
220 DirItem *item;
221 ViewIter iter;
223 view_get_cursor(filer_window->view, &iter);
224 item = iter.peek(&iter);
226 if (item == NULL || item->leafname[0] != '.')
227 display_update_hidden(filer_window);
228 filer_window->temp_show_hidden = FALSE;
232 /* Insert this leafname at the cursor (replacing the selection, if any).
233 * Must be in SHELL mode.
235 void minibuffer_add(FilerWindow *filer_window, const gchar *leafname)
237 guchar *esc;
238 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
239 GtkEntry *entry = GTK_ENTRY(edit);
240 int pos;
242 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
244 esc = shell_escape(leafname);
246 gtk_editable_delete_selection(edit);
247 pos = gtk_editable_get_position(edit);
249 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
250 gtk_editable_insert_text(edit, " ", 1, &pos);
252 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
253 gtk_editable_set_position(edit, pos);
255 g_free(esc);
259 /****************************************************************
260 * INTERNAL FUNCTIONS *
261 ****************************************************************/
263 static void show_help(FilerWindow *filer_window)
265 switch (filer_window->mini_type)
267 case MINI_PATH:
268 info_message(
269 _("Enter the name of a file and I'll display "
270 "it for you. Press Tab to fill in the longest "
271 "match. Escape to close the minibuffer."));
272 break;
273 case MINI_SHELL:
274 info_message(
275 _("Enter a shell command to execute. Click "
276 "on a file to add it to the buffer."));
277 break;
278 case MINI_SELECT_BY_NAME:
279 info_message(
280 _("Enter a file name pattern to select all matching files:\n\n"
281 "? means any character\n"
282 "* means zero or more characters\n"
283 "[aA] means 'a' or 'A'\n"
284 "[a-z] means any character from a to z (lowercase)\n"
285 "*.png means any name ending in '.png'"));
286 break;
287 case MINI_SELECT_IF:
288 show_condition_help(NULL);
289 break;
290 case MINI_FILTER:
291 info_message(
292 _("Enter a pattern to match for files to "
293 "be shown. An empty filter turns the "
294 "filter off."));
295 break;
296 default:
297 g_warning("Unknown minibuffer type!");
298 break;
303 /* PATH ENTRY */
305 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
307 const gchar *path, *pattern;
308 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
309 ViewIter iter;
310 DirItem *item;
312 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
313 pattern = g_basename(path);
315 view_get_cursor(filer_window->view, &iter);
317 item = iter.peek(&iter);
318 if (item == NULL || !matches(&iter, pattern))
320 gdk_beep();
321 return;
324 if ((event->state & GDK_SHIFT_MASK) != 0)
325 flags |= OPEN_SHIFT;
327 filer_openitem(filer_window, &iter, flags);
330 /* Use the cursor item to fill in the minibuffer.
331 * If there are multiple matches then fill in as much as possible and
332 * (possibly) beep.
334 static void complete(FilerWindow *filer_window)
336 GtkEntry *entry;
337 DirItem *item, *other;
338 int shortest_stem = -1;
339 int current_stem;
340 const gchar *text, *leaf;
341 ViewIter cursor, iter;
343 view_get_cursor(filer_window->view, &cursor);
344 item = cursor.peek(&cursor);
346 if (!item)
348 gdk_beep();
349 return;
352 entry = GTK_ENTRY(filer_window->minibuffer);
354 text = gtk_entry_get_text(entry);
355 leaf = strrchr(text, '/');
356 if (!leaf)
358 gdk_beep();
359 return;
362 leaf++;
363 if (!matches(&cursor, leaf))
365 gdk_beep();
366 return;
369 current_stem = strlen(leaf);
371 /* Find the longest other match of this name. If it's longer than
372 * the currently entered text then complete only up to that length.
374 view_get_iter(filer_window->view, &iter, 0);
375 while ((other = iter.next(&iter)))
377 int stem = 0;
379 if (iter.i == cursor.i) /* XXX */
380 continue;
382 while (other->leafname[stem] && item->leafname[stem])
384 gchar a, b;
385 /* Like the matches() function below, the comparison of
386 * leafs must be case-insensitive.
388 a = g_ascii_tolower(item->leafname[stem]);
389 b = g_ascii_tolower(other->leafname[stem]);
390 if (a != b)
391 break;
392 stem++;
395 /* stem is the index of the first difference */
396 if (stem >= current_stem &&
397 (shortest_stem == -1 || stem < shortest_stem))
398 shortest_stem = stem;
401 if (current_stem == shortest_stem)
403 /* We didn't add anything... */
404 if (o_filer_beep_fail.int_value)
405 gdk_beep();
407 else if (current_stem < shortest_stem)
409 gint tmp_pos;
411 /* Have to replace the leafname text in the minibuffer rather
412 * than just append to it. Here's an example:
413 * Suppose we have two dirs, /tmp and /TMP.
414 * The user enters /t in the minibuffer and hits Tab.
415 * With the g_ascii_tolower() code above, this would result
416 * in /tMP being bisplayed in the minibuffer which isn't
417 * intuitive. Therefore all text after the / must be replaced.
419 tmp_pos = leaf - text; /* index of start of leaf */
420 gtk_editable_delete_text(GTK_EDITABLE(entry),
421 tmp_pos, entry->text_length);
422 gtk_editable_insert_text(GTK_EDITABLE(entry),
423 item->leafname, shortest_stem,
424 &tmp_pos);
426 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
428 if (o_filer_beep_multi.int_value)
429 gdk_beep();
431 else
433 const guchar *new;
435 new = make_path(filer_window->sym_path, item->leafname);
437 if (item->base_type == TYPE_DIRECTORY &&
438 (item->flags & ITEM_FLAG_APPDIR) == 0)
439 new = make_path(new, "");
441 gtk_entry_set_text(entry, new);
442 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
446 static void path_changed(FilerWindow *filer_window)
448 GtkWidget *mini = filer_window->minibuffer;
449 const char *rawnew, *leaf;
450 char *path;
451 char *new = NULL;
452 gboolean error = FALSE;
454 rawnew = gtk_entry_get_text(GTK_ENTRY(mini));
455 if (!*rawnew)
457 /* Entry may be blank because we're in the middle of changing
458 * to something else...
460 entry_set_error(mini, FALSE);
461 return;
464 switch (rawnew[0])
466 case '/':
467 new=g_strdup(rawnew);
468 break;
470 case '~':
471 if (!rawnew[1] || rawnew[1]=='/')
473 new=g_strconcat(g_get_home_dir(), "/",
474 rawnew[1]? rawnew+2: "", "/",
475 NULL);
477 else
479 const char *sl;
480 gchar *username;
481 struct passwd *passwd;
484 /* Need to lookup user name */
485 for(sl=rawnew+2; *sl && *sl!='/'; sl++)
487 username=g_strndup(rawnew+1, sl-rawnew-1);
488 passwd=getpwnam(username);
489 g_free(username);
491 if(passwd)
493 new=g_strconcat(passwd->pw_dir, "/",
494 sl+1, "/",
495 NULL);
497 else
498 new=g_strdup(rawnew);
500 break;
502 default:
503 new=g_strdup(rawnew);
504 break;
508 leaf = g_basename(new);
509 if (leaf == new)
510 path = g_strdup("/");
511 else
512 path = g_path_get_dirname(new);
514 if (strcmp(path, filer_window->sym_path) != 0)
516 /* The new path is in a different directory */
517 struct stat info;
519 if (stat_with_timeout(path, &info) == 0 &&
520 S_ISDIR(info.st_mode))
522 filer_change_to(filer_window, path, leaf);
524 else
525 error = TRUE;
527 else
529 if (*leaf == '.' && !filer_window->temp_show_hidden)
531 filer_window->temp_show_hidden = TRUE;
532 display_update_hidden(filer_window);
535 if (find_exact_match(filer_window, leaf) == FALSE &&
536 find_next_match(filer_window, leaf, 0) == FALSE)
537 error = TRUE;
540 g_free(new);
541 g_free(path);
543 entry_set_error(mini, error);
546 /* Look for an exact match, and move the cursor to it if found.
547 * TRUE on success.
549 static gboolean find_exact_match(FilerWindow *filer_window,
550 const gchar *pattern)
552 DirItem *item;
553 ViewIter iter;
554 ViewIface *view = filer_window->view;
556 view_get_iter(view, &iter, 0);
558 while ((item = iter.next(&iter)))
560 if (strcmp(item->leafname, pattern) == 0)
562 view_cursor_to_iter(view, &iter);
563 return TRUE;
567 return FALSE;
570 /* Find the next item in the view that matches 'pattern'. Start from
571 * cursor_base and loop at either end. dir is 1 for a forward search,
572 * -1 for backwards. 0 means forwards, but may stay the same.
574 * Does not automatically update cursor_base.
576 * Returns TRUE if a match is found.
578 static gboolean find_next_match(FilerWindow *filer_window,
579 const char *pattern,
580 int dir)
582 ViewIface *view = filer_window->view;
583 ViewIter iter;
585 if (view_count_items(view) < 1)
586 return FALSE;
588 view_get_iter(view, &iter,
589 VIEW_ITER_FROM_BASE |
590 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
592 if (dir != 0)
593 iter.next(&iter); /* Don't look at the base itself */
595 while (iter.next(&iter))
597 if (matches(&iter, pattern))
599 view_cursor_to_iter(view, &iter);
600 return TRUE;
604 /* No matches (except possibly base itself) */
605 view_get_iter(view, &iter,
606 VIEW_ITER_FROM_BASE | VIEW_ITER_ONE_ONLY |
607 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
609 view_cursor_to_iter(view, &iter);
611 return FALSE;
614 static gboolean matches(ViewIter *iter, const char *pattern)
616 DirItem *item;
618 item = iter->peek(iter);
620 return strncasecmp(item->leafname, pattern, strlen(pattern)) == 0;
623 /* Find next match and set base for future matches. */
624 static void search_in_dir(FilerWindow *filer_window, int dir)
626 const char *path, *pattern;
627 ViewIter iter;
629 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
630 pattern = g_basename(path);
632 view_get_cursor(filer_window->view, &iter);
633 view_set_base(filer_window->view, &iter);
634 find_next_match(filer_window, pattern, dir);
635 view_get_cursor(filer_window->view, &iter);
636 view_set_base(filer_window->view, &iter);
639 /* SHELL COMMANDS */
641 static void add_to_history(const gchar *line)
643 guchar *last;
645 last = shell_history ? (guchar *) shell_history->data : NULL;
647 if (last && strcmp(last, line) == 0)
648 return; /* Duplicating last entry */
650 shell_history = g_list_prepend(shell_history, g_strdup(line));
653 static void shell_done(FilerWindow *filer_window)
655 if (filer_exists(filer_window))
656 filer_update_dir(filer_window, TRUE);
659 /* Given a list of matches, return the longest stem. g_free() the result.
660 * Special chars are escaped. If there is only a single (non-dir) match
661 * then a trailing space is added.
663 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
665 gchar *first = matches->gl_pathv[0];
666 int i;
667 int longest, path_len;
668 guchar *path, *tmp;
670 longest = strlen(first);
672 for (i = 1; i < matches->gl_pathc; i++)
674 int j;
675 guchar *m = matches->gl_pathv[i];
677 for (j = 0; j < longest; j++)
678 if (m[j] != first[j])
679 longest = j;
682 path_len = strlen(filer_window->sym_path);
683 if (strncmp(filer_window->sym_path, first, path_len) == 0 &&
684 first[path_len] == '/' && first[path_len + 1])
686 path = g_strndup(first + path_len + 1, longest - path_len - 1);
688 else
689 path = g_strndup(first, longest);
691 tmp = shell_escape(path);
692 g_free(path);
694 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
696 path = g_strdup_printf("%s ", tmp);
697 g_free(tmp);
698 return path;
701 return tmp;
704 static void shell_tab(FilerWindow *filer_window)
706 const gchar *entry;
707 int i;
708 int pos;
709 GString *leaf;
710 glob_t matches;
711 int leaf_start;
713 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
714 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
715 leaf = g_string_new(NULL);
717 for (i = 0; i < pos; i++)
719 guchar c = entry[i];
721 if (leaf->len == 0)
722 leaf_start = i;
724 if (c == ' ')
726 g_string_truncate(leaf, 0);
727 continue;
729 else if (c == '\\' && i + 1 < pos)
730 c = entry[++i];
731 else if (c == '"' || c == '\'')
733 for (++i; i < pos; i++)
735 guchar cc = entry[i];
737 if (cc == '\\' && i + 1 < pos)
738 cc = entry[++i];
739 else if (cc == c)
740 break;
741 g_string_append_c(leaf, cc);
743 continue;
746 g_string_append_c(leaf, c);
749 if (leaf->len == 0)
750 leaf_start = pos;
752 if (leaf->str[0] != '/' && leaf->str[0] != '~')
754 g_string_prepend_c(leaf, '/');
755 g_string_prepend(leaf, filer_window->sym_path);
758 g_string_append_c(leaf, '*');
760 if (glob(leaf->str,
761 #ifdef GLOB_TILDE
762 GLOB_TILDE |
763 #endif
764 GLOB_MARK, NULL, &matches) == 0)
766 if (matches.gl_pathc > 0)
768 guchar *best;
769 GtkEditable *edit =
770 GTK_EDITABLE(filer_window->minibuffer);
772 best = best_match(filer_window, &matches);
774 gtk_editable_delete_text(edit, leaf_start, pos);
775 gtk_editable_insert_text(edit, best, strlen(best),
776 &leaf_start);
777 gtk_editable_set_position(edit, leaf_start);
779 g_free(best);
781 if (matches.gl_pathc != 1)
782 gdk_beep();
784 globfree(&matches);
787 g_string_free(leaf, TRUE);
790 static void run_child(gpointer unused)
792 /* Ensure output is noticed - send stdout to stderr */
793 dup2(STDERR_FILENO, STDOUT_FILENO);
794 close_on_exec(STDOUT_FILENO, FALSE);
797 /* Either execute the command or make it the default run action */
798 static void shell_return_pressed(FilerWindow *filer_window)
800 GPtrArray *argv;
801 const gchar *entry;
802 GError *error = NULL;
803 pid_t child;
804 DirItem *item;
805 ViewIter iter;
807 entry = mini_contents(filer_window);
809 if (!entry)
810 goto out;
812 add_to_history(entry);
814 argv = g_ptr_array_new();
815 g_ptr_array_add(argv, "sh");
816 g_ptr_array_add(argv, "-c");
817 g_ptr_array_add(argv, (gchar *) entry);
818 g_ptr_array_add(argv, "sh");
820 view_get_iter(filer_window->view, &iter, 0);
821 while ((item = iter.next(&iter)))
823 if (view_get_selected(filer_window->view, &iter))
824 g_ptr_array_add(argv, item->leafname);
827 g_ptr_array_add(argv, NULL);
829 if (!g_spawn_async_with_pipes(filer_window->sym_path,
830 (gchar **) argv->pdata, NULL,
831 G_SPAWN_DO_NOT_REAP_CHILD |
832 G_SPAWN_SEARCH_PATH,
833 run_child, NULL, /* Child setup fn */
834 &child, /* Child PID */
835 NULL, NULL, NULL, /* Standard pipes */
836 &error))
838 delayed_error("%s", error ? error->message : "(null)");
839 g_error_free(error);
841 else
842 on_child_death(child, (CallbackFn) shell_done, filer_window);
844 g_ptr_array_free(argv, TRUE);
846 out:
847 minibuffer_hide(filer_window);
850 /* Move through the shell history */
851 static void shell_recall(FilerWindow *filer_window, int dir)
853 guchar *command;
854 int pos = filer_window->mini_cursor_base;
856 pos += dir;
857 if (pos >= 0)
859 command = g_list_nth_data(shell_history, pos);
860 if (!command)
861 return;
863 else
864 command = "";
866 if (pos < -1)
867 pos = -1;
868 filer_window->mini_cursor_base = pos;
870 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
873 /* SELECT IF */
875 typedef struct {
876 FindInfo info;
877 FilerWindow *filer_window;
878 FindCondition *cond;
879 } SelectData;
881 static gboolean select_if_test(ViewIter *iter, gpointer user_data)
883 DirItem *item;
884 SelectData *data = user_data;
886 item = iter->peek(iter);
887 g_return_val_if_fail(item != NULL, FALSE);
889 data->info.leaf = item->leafname;
890 data->info.fullpath = make_path(data->filer_window->sym_path,
891 data->info.leaf);
893 return mc_lstat(data->info.fullpath, &data->info.stats) == 0 &&
894 find_test_condition(data->cond, &data->info);
897 static void select_return_pressed(FilerWindow *filer_window, guint etime)
899 const gchar *entry;
900 SelectData data;
902 entry = mini_contents(filer_window);
904 if (!entry)
905 goto out;
907 add_to_history(entry);
909 data.cond = find_compile(entry);
910 if (!data.cond)
912 delayed_error(_("Invalid Find condition"));
913 return;
916 data.info.now = time(NULL);
917 data.info.prune = FALSE; /* (don't care) */
918 data.filer_window = filer_window;
920 view_select_if(filer_window->view, select_if_test, &data);
922 find_condition_free(data.cond);
923 out:
924 minibuffer_hide(filer_window);
927 static void filter_return_pressed(FilerWindow *filer_window, guint etime)
929 const gchar *entry;
931 entry = mini_contents(filer_window);
933 if (entry && *entry && strcmp(entry, "*")!=0) {
934 display_set_filter(filer_window, FILER_SHOW_GLOB,
935 entry);
936 } else {
937 display_set_filter(filer_window, FILER_SHOW_ALL, NULL);
939 minibuffer_hide(filer_window);
943 /* EVENT HANDLERS */
945 static gint key_press_event(GtkWidget *widget,
946 GdkEventKey *event,
947 FilerWindow *filer_window)
949 if (event->keyval == GDK_Escape)
951 if (filer_window->mini_type == MINI_SHELL)
953 const gchar *line;
955 line = mini_contents(filer_window);
956 if (line)
957 add_to_history(line);
960 minibuffer_hide(filer_window);
961 return TRUE;
964 switch (filer_window->mini_type)
966 case MINI_PATH:
967 switch (event->keyval)
969 case GDK_Up:
970 search_in_dir(filer_window, -1);
971 break;
972 case GDK_Down:
973 search_in_dir(filer_window, 1);
974 break;
975 case GDK_Return:
976 case GDK_KP_Enter:
977 path_return_pressed(filer_window,
978 event);
979 break;
980 case GDK_Tab:
981 complete(filer_window);
982 break;
983 default:
984 return FALSE;
986 break;
988 case MINI_SHELL:
989 switch (event->keyval)
991 case GDK_Up:
992 shell_recall(filer_window, 1);
993 break;
994 case GDK_Down:
995 shell_recall(filer_window, -1);
996 break;
997 case GDK_Tab:
998 shell_tab(filer_window);
999 break;
1000 case GDK_Return:
1001 case GDK_KP_Enter:
1002 shell_return_pressed(filer_window);
1003 break;
1004 default:
1005 return FALSE;
1007 break;
1008 case MINI_SELECT_IF:
1009 switch (event->keyval)
1011 case GDK_Up:
1012 shell_recall(filer_window, 1);
1013 break;
1014 case GDK_Down:
1015 shell_recall(filer_window, -1);
1016 break;
1017 case GDK_Tab:
1018 break;
1019 case GDK_Return:
1020 case GDK_KP_Enter:
1021 select_return_pressed(filer_window,
1022 event->time);
1023 break;
1024 default:
1025 return FALSE;
1027 break;
1028 case MINI_SELECT_BY_NAME:
1029 switch (event->keyval)
1031 case GDK_Up:
1032 filer_next_selected(filer_window, -1);
1033 break;
1034 case GDK_Down:
1035 filer_next_selected(filer_window, 1);
1036 break;
1037 case GDK_Tab:
1038 break;
1039 case GDK_Return:
1040 case GDK_KP_Enter:
1041 minibuffer_hide(filer_window);
1042 break;
1043 default:
1044 return FALSE;
1046 break;
1048 case MINI_FILTER:
1049 switch (event->keyval)
1051 case GDK_Return:
1052 case GDK_KP_Enter:
1053 filter_return_pressed(filer_window,
1054 event->time);
1055 break;
1056 default:
1057 return FALSE;
1059 break;
1060 default:
1061 break;
1064 return TRUE;
1067 static gboolean select_if_glob(ViewIter *iter, gpointer data)
1069 DirItem *item;
1070 const char *pattern = (char *) data;
1072 item = iter->peek(iter);
1073 g_return_val_if_fail(item != NULL, FALSE);
1075 return fnmatch(pattern, item->leafname, 0) == 0;
1078 static void changed(GtkEditable *mini, FilerWindow *filer_window)
1080 ViewIter iter;
1082 switch (filer_window->mini_type)
1084 case MINI_PATH:
1085 path_changed(filer_window);
1086 return;
1087 case MINI_SELECT_IF:
1088 set_find_string_colour(GTK_WIDGET(mini),
1089 gtk_entry_get_text(
1090 GTK_ENTRY(filer_window->minibuffer)));
1091 return;
1092 case MINI_SELECT_BY_NAME:
1093 view_select_if(filer_window->view,
1094 select_if_glob,
1095 (gpointer) gtk_entry_get_text(
1096 GTK_ENTRY(filer_window->minibuffer)));
1097 view_get_iter(filer_window->view, &iter,
1098 VIEW_ITER_FROM_BASE | VIEW_ITER_SELECTED);
1099 if (iter.next(&iter))
1100 view_cursor_to_iter(filer_window->view, &iter);
1101 return;
1102 default:
1103 break;
1107 /* Returns a string (which must NOT be freed), or NULL if the buffer
1108 * is blank (whitespace only).
1110 static const gchar *mini_contents(FilerWindow *filer_window)
1112 const gchar *entry, *c;
1114 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
1116 for (c = entry; *c; c++)
1117 if (!g_ascii_isspace(*c))
1118 return entry;
1120 return NULL;
1123 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
1124 * behaviour.
1126 static gboolean grab_focus(GtkWidget *minibuffer)
1128 GtkWidgetClass *class;
1130 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET));
1132 class->grab_focus(minibuffer);
1134 g_signal_stop_emission(minibuffer,
1135 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer)), 0);
1138 return 1;