Updated update-po to use the new GtkBuilder .ui file
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blob2558ea18b02f7502675b01aef7a0879a70ba8239
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 gtk_entry_set_text(mini, "*.");
157 filer_window->mini_cursor_base = -1; /* History */
158 view_select_if(filer_window->view, select_if_glob, "*.");
159 break;
160 case MINI_FILTER:
161 if(filer_window->filter!=FILER_SHOW_GLOB ||
162 !filer_window->filter_string)
163 gtk_entry_set_text(mini, "*");
164 else
165 gtk_entry_set_text(mini,
166 filer_window->filter_string);
167 break;
168 case MINI_SHELL:
170 DirItem *item;
171 view_get_cursor(filer_window->view, &cursor);
172 item = cursor.peek(&cursor);
173 pos = 0;
174 if (view_count_selected(filer_window->view) > 0)
175 gtk_entry_set_text(mini, " \"$@\"");
176 else if (item)
178 guchar *escaped;
179 guchar *tmp;
181 escaped = shell_escape(item->leafname);
182 tmp = g_strconcat(" ", escaped, NULL);
183 g_free(escaped);
184 gtk_entry_set_text(mini, tmp);
185 g_free(tmp);
187 else
188 gtk_entry_set_text(mini, "");
189 filer_window->mini_cursor_base = -1; /* History */
190 break;
192 default:
193 g_warning("Bad minibuffer type\n");
194 return;
197 filer_window->mini_type = mini_type;
199 gtk_editable_set_position(GTK_EDITABLE(mini), pos);
201 gtk_widget_show_all(filer_window->minibuffer_area);
203 gtk_widget_grab_focus(filer_window->minibuffer);
206 void minibuffer_hide(FilerWindow *filer_window)
208 filer_window->mini_type = MINI_NONE;
210 gtk_widget_hide(filer_window->minibuffer_area);
212 gtk_widget_child_focus(filer_window->window, GTK_DIR_TAB_FORWARD);
214 if (filer_window->temp_show_hidden)
216 DirItem *item;
217 ViewIter iter;
219 view_get_cursor(filer_window->view, &iter);
220 item = iter.peek(&iter);
222 if (item == NULL || item->leafname[0] != '.')
223 display_update_hidden(filer_window);
224 filer_window->temp_show_hidden = FALSE;
228 /* Insert this leafname at the cursor (replacing the selection, if any).
229 * Must be in SHELL mode.
231 void minibuffer_add(FilerWindow *filer_window, const gchar *leafname)
233 guchar *esc;
234 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
235 GtkEntry *entry = GTK_ENTRY(edit);
236 int pos;
238 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
240 esc = shell_escape(leafname);
242 gtk_editable_delete_selection(edit);
243 pos = gtk_editable_get_position(edit);
245 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
246 gtk_editable_insert_text(edit, " ", 1, &pos);
248 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
249 gtk_editable_set_position(edit, pos);
251 g_free(esc);
255 /****************************************************************
256 * INTERNAL FUNCTIONS *
257 ****************************************************************/
259 static void show_help(FilerWindow *filer_window)
261 switch (filer_window->mini_type)
263 case MINI_PATH:
264 info_message(
265 _("Enter the name of a file and I'll display "
266 "it for you. Press Tab to fill in the longest "
267 "match. Escape to close the minibuffer."));
268 break;
269 case MINI_SHELL:
270 info_message(
271 _("Enter a shell command to execute. Click "
272 "on a file to add it to the buffer."));
273 break;
274 case MINI_SELECT_BY_NAME:
275 info_message(
276 _("Enter a file name pattern to select all matching files:\n\n"
277 "? means any character\n"
278 "* means zero or more characters\n"
279 "[aA] means 'a' or 'A'\n"
280 "[a-z] means any character from a to z (lowercase)\n"
281 "*.png means any name ending in '.png'"));
282 break;
283 case MINI_SELECT_IF:
284 show_condition_help(NULL);
285 break;
286 case MINI_FILTER:
287 info_message(
288 _("Enter a pattern to match for files to "
289 "be shown. An empty filter turns the "
290 "filter off."));
291 break;
292 default:
293 g_warning("Unknown minibuffer type!");
294 break;
299 /* PATH ENTRY */
301 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
303 const gchar *path, *pattern;
304 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
305 ViewIter iter;
306 DirItem *item;
308 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
309 pattern = g_basename(path);
311 view_get_cursor(filer_window->view, &iter);
313 item = iter.peek(&iter);
314 if (item == NULL || !matches(&iter, pattern))
316 gdk_beep();
317 return;
320 if ((event->state & GDK_SHIFT_MASK) != 0)
321 flags |= OPEN_SHIFT;
323 filer_openitem(filer_window, &iter, flags);
326 /* Use the cursor item to fill in the minibuffer.
327 * If there are multiple matches then fill in as much as possible and
328 * (possibly) beep.
330 static void complete(FilerWindow *filer_window)
332 GtkEntry *entry;
333 DirItem *item, *other;
334 int shortest_stem = -1;
335 int current_stem;
336 const gchar *text, *leaf;
337 ViewIter cursor, iter;
339 view_get_cursor(filer_window->view, &cursor);
340 item = cursor.peek(&cursor);
342 if (!item)
344 gdk_beep();
345 return;
348 entry = GTK_ENTRY(filer_window->minibuffer);
350 text = gtk_entry_get_text(entry);
351 leaf = strrchr(text, '/');
352 if (!leaf)
354 gdk_beep();
355 return;
358 leaf++;
359 if (!matches(&cursor, leaf))
361 gdk_beep();
362 return;
365 current_stem = strlen(leaf);
367 /* Find the longest other match of this name. If it's longer than
368 * the currently entered text then complete only up to that length.
370 view_get_iter(filer_window->view, &iter, 0);
371 while ((other = iter.next(&iter)))
373 int stem = 0;
375 if (iter.i == cursor.i) /* XXX */
376 continue;
378 while (other->leafname[stem] && item->leafname[stem])
380 gchar a, b;
381 /* Like the matches() function below, the comparison of
382 * leafs must be case-insensitive.
384 a = g_ascii_tolower(item->leafname[stem]);
385 b = g_ascii_tolower(other->leafname[stem]);
386 if (a != b)
387 break;
388 stem++;
391 /* stem is the index of the first difference */
392 if (stem >= current_stem &&
393 (shortest_stem == -1 || stem < shortest_stem))
394 shortest_stem = stem;
397 if (current_stem == shortest_stem)
399 /* We didn't add anything... */
400 if (o_filer_beep_fail.int_value)
401 gdk_beep();
403 else if (current_stem < shortest_stem)
405 gint tmp_pos;
407 /* Have to replace the leafname text in the minibuffer rather
408 * than just append to it. Here's an example:
409 * Suppose we have two dirs, /tmp and /TMP.
410 * The user enters /t in the minibuffer and hits Tab.
411 * With the g_ascii_tolower() code above, this would result
412 * in /tMP being bisplayed in the minibuffer which isn't
413 * intuitive. Therefore all text after the / must be replaced.
415 tmp_pos = leaf - text; /* index of start of leaf */
416 gtk_editable_delete_text(GTK_EDITABLE(entry),
417 tmp_pos, entry->text_length);
418 gtk_editable_insert_text(GTK_EDITABLE(entry),
419 item->leafname, shortest_stem,
420 &tmp_pos);
422 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
424 if (o_filer_beep_multi.int_value)
425 gdk_beep();
427 else
429 const guchar *new;
431 new = make_path(filer_window->sym_path, item->leafname);
433 if (item->base_type == TYPE_DIRECTORY &&
434 (item->flags & ITEM_FLAG_APPDIR) == 0)
435 new = make_path(new, "");
437 gtk_entry_set_text(entry, new);
438 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
442 static void path_changed(FilerWindow *filer_window)
444 GtkWidget *mini = filer_window->minibuffer;
445 const char *rawnew, *leaf;
446 char *path;
447 char *new = NULL;
448 gboolean error = FALSE;
450 rawnew = gtk_entry_get_text(GTK_ENTRY(mini));
451 if (!*rawnew)
453 /* Entry may be blank because we're in the middle of changing
454 * to something else...
456 entry_set_error(mini, FALSE);
457 return;
460 switch (rawnew[0])
462 case '/':
463 new=g_strdup(rawnew);
464 break;
466 case '~':
467 if (!rawnew[1] || rawnew[1]=='/')
469 new=g_strconcat(g_get_home_dir(), "/",
470 rawnew[1]? rawnew+2: "", "/",
471 NULL);
473 else
475 const char *sl;
476 gchar *username;
477 struct passwd *passwd;
480 /* Need to lookup user name */
481 for(sl=rawnew+2; *sl && *sl!='/'; sl++)
483 username=g_strndup(rawnew+1, sl-rawnew-1);
484 passwd=getpwnam(username);
485 g_free(username);
487 if(passwd)
489 new=g_strconcat(passwd->pw_dir, "/",
490 sl+1, "/",
491 NULL);
493 else
494 new=g_strdup(rawnew);
496 break;
498 default:
499 new=g_strdup(rawnew);
500 break;
504 leaf = g_basename(new);
505 if (leaf == new)
506 path = g_strdup("/");
507 else
508 path = g_path_get_dirname(new);
510 if (strcmp(path, filer_window->sym_path) != 0)
512 /* The new path is in a different directory */
513 struct stat info;
515 if (stat_with_timeout(path, &info) == 0 &&
516 S_ISDIR(info.st_mode))
518 filer_change_to(filer_window, path, leaf);
520 else
521 error = TRUE;
523 else
525 if (*leaf == '.' && !filer_window->temp_show_hidden)
527 filer_window->temp_show_hidden = TRUE;
528 display_update_hidden(filer_window);
531 if (find_exact_match(filer_window, leaf) == FALSE &&
532 find_next_match(filer_window, leaf, 0) == FALSE)
533 error = TRUE;
536 g_free(new);
537 g_free(path);
539 entry_set_error(mini, error);
542 /* Look for an exact match, and move the cursor to it if found.
543 * TRUE on success.
545 static gboolean find_exact_match(FilerWindow *filer_window,
546 const gchar *pattern)
548 DirItem *item;
549 ViewIter iter;
550 ViewIface *view = filer_window->view;
552 view_get_iter(view, &iter, 0);
554 while ((item = iter.next(&iter)))
556 if (strcmp(item->leafname, pattern) == 0)
558 view_cursor_to_iter(view, &iter);
559 return TRUE;
563 return FALSE;
566 /* Find the next item in the view that matches 'pattern'. Start from
567 * cursor_base and loop at either end. dir is 1 for a forward search,
568 * -1 for backwards. 0 means forwards, but may stay the same.
570 * Does not automatically update cursor_base.
572 * Returns TRUE if a match is found.
574 static gboolean find_next_match(FilerWindow *filer_window,
575 const char *pattern,
576 int dir)
578 ViewIface *view = filer_window->view;
579 ViewIter iter;
581 if (view_count_items(view) < 1)
582 return FALSE;
584 view_get_iter(view, &iter,
585 VIEW_ITER_FROM_BASE |
586 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
588 if (dir != 0)
589 iter.next(&iter); /* Don't look at the base itself */
591 while (iter.next(&iter))
593 if (matches(&iter, pattern))
595 view_cursor_to_iter(view, &iter);
596 return TRUE;
600 /* No matches (except possibly base itself) */
601 view_get_iter(view, &iter,
602 VIEW_ITER_FROM_BASE | VIEW_ITER_ONE_ONLY |
603 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
605 view_cursor_to_iter(view, &iter);
607 return FALSE;
610 static gboolean matches(ViewIter *iter, const char *pattern)
612 DirItem *item;
614 item = iter->peek(iter);
616 return strncasecmp(item->leafname, pattern, strlen(pattern)) == 0;
619 /* Find next match and set base for future matches. */
620 static void search_in_dir(FilerWindow *filer_window, int dir)
622 const char *path, *pattern;
623 ViewIter iter;
625 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
626 pattern = g_basename(path);
628 view_get_cursor(filer_window->view, &iter);
629 view_set_base(filer_window->view, &iter);
630 find_next_match(filer_window, pattern, dir);
631 view_get_cursor(filer_window->view, &iter);
632 view_set_base(filer_window->view, &iter);
635 /* SHELL COMMANDS */
637 static void add_to_history(const gchar *line)
639 guchar *last;
641 last = shell_history ? (guchar *) shell_history->data : NULL;
643 if (last && strcmp(last, line) == 0)
644 return; /* Duplicating last entry */
646 shell_history = g_list_prepend(shell_history, g_strdup(line));
649 static void shell_done(FilerWindow *filer_window)
651 if (filer_exists(filer_window))
652 filer_update_dir(filer_window, TRUE);
655 /* Given a list of matches, return the longest stem. g_free() the result.
656 * Special chars are escaped. If there is only a single (non-dir) match
657 * then a trailing space is added.
659 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
661 gchar *first = matches->gl_pathv[0];
662 int i;
663 int longest, path_len;
664 guchar *path, *tmp;
666 longest = strlen(first);
668 for (i = 1; i < matches->gl_pathc; i++)
670 int j;
671 guchar *m = matches->gl_pathv[i];
673 for (j = 0; j < longest; j++)
674 if (m[j] != first[j])
675 longest = j;
678 path_len = strlen(filer_window->sym_path);
679 if (strncmp(filer_window->sym_path, first, path_len) == 0 &&
680 first[path_len] == '/' && first[path_len + 1])
682 path = g_strndup(first + path_len + 1, longest - path_len - 1);
684 else
685 path = g_strndup(first, longest);
687 tmp = shell_escape(path);
688 g_free(path);
690 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
692 path = g_strdup_printf("%s ", tmp);
693 g_free(tmp);
694 return path;
697 return tmp;
700 static void shell_tab(FilerWindow *filer_window)
702 const gchar *entry;
703 int i;
704 int pos;
705 GString *leaf;
706 glob_t matches;
707 int leaf_start;
709 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
710 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
711 leaf = g_string_new(NULL);
713 for (i = 0; i < pos; i++)
715 guchar c = entry[i];
717 if (leaf->len == 0)
718 leaf_start = i;
720 if (c == ' ')
722 g_string_truncate(leaf, 0);
723 continue;
725 else if (c == '\\' && i + 1 < pos)
726 c = entry[++i];
727 else if (c == '"' || c == '\'')
729 for (++i; i < pos; i++)
731 guchar cc = entry[i];
733 if (cc == '\\' && i + 1 < pos)
734 cc = entry[++i];
735 else if (cc == c)
736 break;
737 g_string_append_c(leaf, cc);
739 continue;
742 g_string_append_c(leaf, c);
745 if (leaf->len == 0)
746 leaf_start = pos;
748 if (leaf->str[0] != '/' && leaf->str[0] != '~')
750 g_string_prepend_c(leaf, '/');
751 g_string_prepend(leaf, filer_window->sym_path);
754 g_string_append_c(leaf, '*');
756 if (glob(leaf->str,
757 #ifdef GLOB_TILDE
758 GLOB_TILDE |
759 #endif
760 GLOB_MARK, NULL, &matches) == 0)
762 if (matches.gl_pathc > 0)
764 guchar *best;
765 GtkEditable *edit =
766 GTK_EDITABLE(filer_window->minibuffer);
768 best = best_match(filer_window, &matches);
770 gtk_editable_delete_text(edit, leaf_start, pos);
771 gtk_editable_insert_text(edit, best, strlen(best),
772 &leaf_start);
773 gtk_editable_set_position(edit, leaf_start);
775 g_free(best);
777 if (matches.gl_pathc != 1)
778 gdk_beep();
780 globfree(&matches);
783 g_string_free(leaf, TRUE);
786 static void run_child(gpointer unused)
788 /* Ensure output is noticed - send stdout to stderr */
789 dup2(STDERR_FILENO, STDOUT_FILENO);
790 close_on_exec(STDOUT_FILENO, FALSE);
793 /* Either execute the command or make it the default run action */
794 static void shell_return_pressed(FilerWindow *filer_window)
796 GPtrArray *argv;
797 const gchar *entry;
798 GError *error = NULL;
799 pid_t child;
800 DirItem *item;
801 ViewIter iter;
803 entry = mini_contents(filer_window);
805 if (!entry)
806 goto out;
808 add_to_history(entry);
810 argv = g_ptr_array_new();
811 g_ptr_array_add(argv, "sh");
812 g_ptr_array_add(argv, "-c");
813 g_ptr_array_add(argv, (gchar *) entry);
814 g_ptr_array_add(argv, "sh");
816 view_get_iter(filer_window->view, &iter, 0);
817 while ((item = iter.next(&iter)))
819 if (view_get_selected(filer_window->view, &iter))
820 g_ptr_array_add(argv, item->leafname);
823 g_ptr_array_add(argv, NULL);
825 if (!g_spawn_async_with_pipes(filer_window->sym_path,
826 (gchar **) argv->pdata, NULL,
827 G_SPAWN_DO_NOT_REAP_CHILD |
828 G_SPAWN_SEARCH_PATH,
829 run_child, NULL, /* Child setup fn */
830 &child, /* Child PID */
831 NULL, NULL, NULL, /* Standard pipes */
832 &error))
834 delayed_error("%s", error ? error->message : "(null)");
835 g_error_free(error);
837 else
838 on_child_death(child, (CallbackFn) shell_done, filer_window);
840 g_ptr_array_free(argv, TRUE);
842 out:
843 minibuffer_hide(filer_window);
846 /* Move through the shell history */
847 static void shell_recall(FilerWindow *filer_window, int dir)
849 guchar *command;
850 int pos = filer_window->mini_cursor_base;
852 pos += dir;
853 if (pos >= 0)
855 command = g_list_nth_data(shell_history, pos);
856 if (!command)
857 return;
859 else
860 command = "";
862 if (pos < -1)
863 pos = -1;
864 filer_window->mini_cursor_base = pos;
866 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
869 /* SELECT IF */
871 typedef struct {
872 FindInfo info;
873 FilerWindow *filer_window;
874 FindCondition *cond;
875 } SelectData;
877 static gboolean select_if_test(ViewIter *iter, gpointer user_data)
879 DirItem *item;
880 SelectData *data = user_data;
882 item = iter->peek(iter);
883 g_return_val_if_fail(item != NULL, FALSE);
885 data->info.leaf = item->leafname;
886 data->info.fullpath = make_path(data->filer_window->sym_path,
887 data->info.leaf);
889 return mc_lstat(data->info.fullpath, &data->info.stats) == 0 &&
890 find_test_condition(data->cond, &data->info);
893 static void select_return_pressed(FilerWindow *filer_window, guint etime)
895 const gchar *entry;
896 SelectData data;
898 entry = mini_contents(filer_window);
900 if (!entry)
901 goto out;
903 add_to_history(entry);
905 data.cond = find_compile(entry);
906 if (!data.cond)
908 delayed_error(_("Invalid Find condition"));
909 return;
912 data.info.now = time(NULL);
913 data.info.prune = FALSE; /* (don't care) */
914 data.filer_window = filer_window;
916 view_select_if(filer_window->view, select_if_test, &data);
918 find_condition_free(data.cond);
919 out:
920 minibuffer_hide(filer_window);
923 static void filter_return_pressed(FilerWindow *filer_window, guint etime)
925 const gchar *entry;
927 entry = mini_contents(filer_window);
929 if (entry && *entry && strcmp(entry, "*")!=0) {
930 display_set_filter(filer_window, FILER_SHOW_GLOB,
931 entry);
932 } else {
933 display_set_filter(filer_window, FILER_SHOW_ALL, NULL);
935 minibuffer_hide(filer_window);
939 /* EVENT HANDLERS */
941 static gint key_press_event(GtkWidget *widget,
942 GdkEventKey *event,
943 FilerWindow *filer_window)
945 if (event->keyval == GDK_Escape)
947 if (filer_window->mini_type == MINI_SHELL)
949 const gchar *line;
951 line = mini_contents(filer_window);
952 if (line)
953 add_to_history(line);
956 minibuffer_hide(filer_window);
957 return TRUE;
960 switch (filer_window->mini_type)
962 case MINI_PATH:
963 switch (event->keyval)
965 case GDK_Up:
966 search_in_dir(filer_window, -1);
967 break;
968 case GDK_Down:
969 search_in_dir(filer_window, 1);
970 break;
971 case GDK_Return:
972 case GDK_KP_Enter:
973 path_return_pressed(filer_window,
974 event);
975 break;
976 case GDK_Tab:
977 complete(filer_window);
978 break;
979 default:
980 return FALSE;
982 break;
984 case MINI_SHELL:
985 switch (event->keyval)
987 case GDK_Up:
988 shell_recall(filer_window, 1);
989 break;
990 case GDK_Down:
991 shell_recall(filer_window, -1);
992 break;
993 case GDK_Tab:
994 shell_tab(filer_window);
995 break;
996 case GDK_Return:
997 case GDK_KP_Enter:
998 shell_return_pressed(filer_window);
999 break;
1000 default:
1001 return FALSE;
1003 break;
1004 case MINI_SELECT_IF:
1005 switch (event->keyval)
1007 case GDK_Up:
1008 shell_recall(filer_window, 1);
1009 break;
1010 case GDK_Down:
1011 shell_recall(filer_window, -1);
1012 break;
1013 case GDK_Tab:
1014 break;
1015 case GDK_Return:
1016 case GDK_KP_Enter:
1017 select_return_pressed(filer_window,
1018 event->time);
1019 break;
1020 default:
1021 return FALSE;
1023 break;
1024 case MINI_SELECT_BY_NAME:
1025 switch (event->keyval)
1027 case GDK_Up:
1028 filer_next_selected(filer_window, -1);
1029 break;
1030 case GDK_Down:
1031 filer_next_selected(filer_window, 1);
1032 break;
1033 case GDK_Tab:
1034 break;
1035 case GDK_Return:
1036 case GDK_KP_Enter:
1037 minibuffer_hide(filer_window);
1038 break;
1039 default:
1040 return FALSE;
1042 break;
1044 case MINI_FILTER:
1045 switch (event->keyval)
1047 case GDK_Return:
1048 case GDK_KP_Enter:
1049 filter_return_pressed(filer_window,
1050 event->time);
1051 break;
1052 default:
1053 return FALSE;
1055 break;
1056 default:
1057 break;
1060 return TRUE;
1063 static gboolean select_if_glob(ViewIter *iter, gpointer data)
1065 DirItem *item;
1066 const char *pattern = (char *) data;
1068 item = iter->peek(iter);
1069 g_return_val_if_fail(item != NULL, FALSE);
1071 return fnmatch(pattern, item->leafname, 0) == 0;
1074 static void changed(GtkEditable *mini, FilerWindow *filer_window)
1076 switch (filer_window->mini_type)
1078 case MINI_PATH:
1079 path_changed(filer_window);
1080 return;
1081 case MINI_SELECT_IF:
1082 set_find_string_colour(GTK_WIDGET(mini),
1083 gtk_entry_get_text(
1084 GTK_ENTRY(filer_window->minibuffer)));
1085 return;
1086 case MINI_SELECT_BY_NAME:
1087 view_select_if(filer_window->view,
1088 select_if_glob,
1089 (gpointer) gtk_entry_get_text(
1090 GTK_ENTRY(filer_window->minibuffer)));
1091 return;
1092 default:
1093 break;
1097 /* Returns a string (which must NOT be freed), or NULL if the buffer
1098 * is blank (whitespace only).
1100 static const gchar *mini_contents(FilerWindow *filer_window)
1102 const gchar *entry, *c;
1104 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
1106 for (c = entry; *c; c++)
1107 if (!g_ascii_isspace(*c))
1108 return entry;
1110 return NULL;
1113 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
1114 * behaviour.
1116 static gboolean grab_focus(GtkWidget *minibuffer)
1118 GtkWidgetClass *class;
1120 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET));
1122 class->grab_focus(minibuffer);
1124 g_signal_stop_emission(minibuffer,
1125 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer)), 0);
1128 return 1;