r4370: Added Vietnamese translation (Xomhau Newnick).
[rox-filer/ma.git] / ROX-Filer / src / minibuffer.c
blobbb815dada63df447f833edd87233532ac498919e
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2005, the ROX-Filer team.
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 <fnmatch.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <glob.h>
31 #include <stdio.h>
33 #include <sys/types.h>
34 #include <pwd.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkkeysyms.h>
39 #include "global.h"
41 #include "options.h"
42 #include "find.h"
43 #include "gui_support.h"
44 #include "support.h"
45 #include "minibuffer.h"
46 #include "filer.h"
47 #include "display.h"
48 #include "main.h"
49 #include "action.h"
50 #include "diritem.h"
51 #include "type.h"
52 #include "view_iface.h"
54 static GList *shell_history = NULL;
56 /* Static prototypes */
57 static gint key_press_event(GtkWidget *widget,
58 GdkEventKey *event,
59 FilerWindow *filer_window);
60 static void changed(GtkEditable *mini, FilerWindow *filer_window);
61 static gboolean find_next_match(FilerWindow *filer_window,
62 const char *pattern,
63 int dir);
64 static gboolean find_exact_match(FilerWindow *filer_window,
65 const gchar *pattern);
66 static gboolean matches(ViewIter *iter, const char *pattern);
67 static void search_in_dir(FilerWindow *filer_window, int dir);
68 static const gchar *mini_contents(FilerWindow *filer_window);
69 static void show_help(FilerWindow *filer_window);
70 static gboolean grab_focus(GtkWidget *minibuffer);
71 static gboolean select_if_glob(ViewIter *iter, gpointer data);
73 /****************************************************************
74 * EXTERNAL INTERFACE *
75 ****************************************************************/
77 static Option o_filer_beep_fail, o_filer_beep_multi;
79 void minibuffer_init(void)
81 option_add_int(&o_filer_beep_fail, "filer_beep_fail", 1);
82 option_add_int(&o_filer_beep_multi, "filer_beep_multi", 1);
85 /* Creates the minibuffer widgets, setting the appropriate fields
86 * in filer_window.
88 void create_minibuffer(FilerWindow *filer_window)
90 GtkWidget *hbox, *label, *mini;
92 hbox = gtk_hbox_new(FALSE, 0);
94 gtk_box_pack_start(GTK_BOX(hbox),
95 new_help_button((HelpFunc) show_help, filer_window),
96 FALSE, TRUE, 0);
98 label = gtk_label_new("");
99 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
101 mini = gtk_entry_new();
102 gtk_box_pack_start(GTK_BOX(hbox), mini, TRUE, TRUE, 0);
103 gtk_widget_set_name(mini, "fixed-style");
104 g_signal_connect(mini, "key_press_event",
105 G_CALLBACK(key_press_event), filer_window);
106 g_signal_connect(mini, "changed", G_CALLBACK(changed), filer_window);
108 /* Grabbing focus musn't select the text... */
109 g_signal_connect_swapped(mini, "grab-focus",
110 G_CALLBACK(grab_focus), mini);
112 filer_window->minibuffer = mini;
113 filer_window->minibuffer_label = label;
114 filer_window->minibuffer_area = hbox;
117 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
119 GtkEntry *mini;
120 int pos = -1;
121 ViewIter cursor;
123 g_return_if_fail(filer_window != NULL);
124 g_return_if_fail(filer_window->minibuffer != NULL);
126 mini = GTK_ENTRY(filer_window->minibuffer);
127 entry_set_error(filer_window->minibuffer, FALSE);
129 filer_window->mini_type = MINI_NONE;
130 gtk_label_set_text(GTK_LABEL(filer_window->minibuffer_label),
131 mini_type == MINI_PATH ? _("Goto:") :
132 mini_type == MINI_SHELL ? _("Shell:") :
133 mini_type == MINI_SELECT_IF ? _("Select If:") :
134 mini_type == MINI_SELECT_BY_NAME ? _("Select Named:") :
135 mini_type == MINI_FILTER ? _("Pattern:") :
136 "?");
138 switch (mini_type)
140 case MINI_PATH:
141 view_show_cursor(filer_window->view);
142 view_get_cursor(filer_window->view, &cursor);
143 view_set_base(filer_window->view, &cursor);
145 gtk_entry_set_text(mini,
146 make_path(filer_window->sym_path, ""));
147 if (filer_window->temp_show_hidden)
149 filer_window->temp_show_hidden = FALSE;
150 display_update_hidden(filer_window);
152 break;
153 case MINI_SELECT_IF:
154 gtk_entry_set_text(mini, "");
155 filer_window->mini_cursor_base = -1; /* History */
156 break;
157 case MINI_SELECT_BY_NAME:
158 gtk_entry_set_text(mini, "*.");
159 filer_window->mini_cursor_base = -1; /* History */
160 view_select_if(filer_window->view, select_if_glob, "*.");
161 break;
162 case MINI_FILTER:
163 if(filer_window->filter!=FILER_SHOW_GLOB ||
164 !filer_window->filter_string)
165 gtk_entry_set_text(mini, "*");
166 else
167 gtk_entry_set_text(mini,
168 filer_window->filter_string);
169 break;
170 case MINI_SHELL:
172 DirItem *item;
173 view_get_cursor(filer_window->view, &cursor);
174 item = cursor.peek(&cursor);
175 pos = 0;
176 if (view_count_selected(filer_window->view) > 0)
177 gtk_entry_set_text(mini, " \"$@\"");
178 else if (item)
180 guchar *tmp;
182 tmp = g_strconcat(" ", item->leafname, NULL);
183 gtk_entry_set_text(mini, tmp);
184 g_free(tmp);
186 else
187 gtk_entry_set_text(mini, "");
188 filer_window->mini_cursor_base = -1; /* History */
189 break;
191 default:
192 g_warning("Bad minibuffer type\n");
193 return;
196 filer_window->mini_type = mini_type;
198 gtk_editable_set_position(GTK_EDITABLE(mini), pos);
200 gtk_widget_show_all(filer_window->minibuffer_area);
202 gtk_widget_grab_focus(filer_window->minibuffer);
205 void minibuffer_hide(FilerWindow *filer_window)
207 filer_window->mini_type = MINI_NONE;
209 gtk_widget_hide(filer_window->minibuffer_area);
211 gtk_widget_child_focus(filer_window->window, GTK_DIR_TAB_FORWARD);
213 if (filer_window->temp_show_hidden)
215 DirItem *item;
216 ViewIter iter;
218 view_get_cursor(filer_window->view, &iter);
219 item = iter.peek(&iter);
221 if (item == NULL || item->leafname[0] != '.')
222 display_update_hidden(filer_window);
223 filer_window->temp_show_hidden = FALSE;
227 /* Insert this leafname at the cursor (replacing the selection, if any).
228 * Must be in SHELL mode.
230 void minibuffer_add(FilerWindow *filer_window, const gchar *leafname)
232 guchar *esc;
233 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
234 GtkEntry *entry = GTK_ENTRY(edit);
235 int pos;
237 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
239 esc = shell_escape(leafname);
241 gtk_editable_delete_selection(edit);
242 pos = gtk_editable_get_position(edit);
244 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
245 gtk_editable_insert_text(edit, " ", 1, &pos);
247 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
248 gtk_editable_set_position(edit, pos);
250 g_free(esc);
254 /****************************************************************
255 * INTERNAL FUNCTIONS *
256 ****************************************************************/
258 static void show_help(FilerWindow *filer_window)
260 switch (filer_window->mini_type)
262 case MINI_PATH:
263 info_message(
264 _("Enter the name of a file and I'll display "
265 "it for you. Press Tab to fill in the longest "
266 "match. Escape to close the minibuffer."));
267 break;
268 case MINI_SHELL:
269 info_message(
270 _("Enter a shell command to execute. Click "
271 "on a file to add it to the buffer."));
272 break;
273 case MINI_SELECT_BY_NAME:
274 info_message(
275 _("Enter a file name pattern to select all matching files:\n\n"
276 "? means any character\n"
277 "* means zero or more characters\n"
278 "[aA] means 'a' or 'A'\n"
279 "[a-z] means any character from a to z (lowercase)\n"
280 "*.png means any name ending in '.png'"));
281 break;
282 case MINI_SELECT_IF:
283 show_condition_help(NULL);
284 break;
285 case MINI_FILTER:
286 info_message(
287 _("Enter a pattern to match for files to "
288 "be shown. An empty filter turns the "
289 "filter off."));
290 break;
291 default:
292 g_warning("Unknown minibuffer type!");
293 break;
298 /* PATH ENTRY */
300 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
302 const gchar *path, *pattern;
303 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
304 ViewIter iter;
305 DirItem *item;
307 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
308 pattern = g_basename(path);
310 view_get_cursor(filer_window->view, &iter);
312 item = iter.peek(&iter);
313 if (item == NULL || !matches(&iter, pattern))
315 gdk_beep();
316 return;
319 if ((event->state & GDK_SHIFT_MASK) != 0)
320 flags |= OPEN_SHIFT;
322 filer_openitem(filer_window, &iter, flags);
325 /* Use the cursor item to fill in the minibuffer.
326 * If there are multiple matches then fill in as much as possible and
327 * (possibly) beep.
329 static void complete(FilerWindow *filer_window)
331 GtkEntry *entry;
332 DirItem *item, *other;
333 int shortest_stem = -1;
334 int current_stem;
335 const gchar *text, *leaf;
336 ViewIter cursor, iter;
338 view_get_cursor(filer_window->view, &cursor);
339 item = cursor.peek(&cursor);
341 if (!item)
343 gdk_beep();
344 return;
347 entry = GTK_ENTRY(filer_window->minibuffer);
349 text = gtk_entry_get_text(entry);
350 leaf = strrchr(text, '/');
351 if (!leaf)
353 gdk_beep();
354 return;
357 leaf++;
358 if (!matches(&cursor, leaf))
360 gdk_beep();
361 return;
364 current_stem = strlen(leaf);
366 /* Find the longest other match of this name. If it's longer than
367 * the currently entered text then complete only up to that length.
369 view_get_iter(filer_window->view, &iter, 0);
370 while ((other = iter.next(&iter)))
372 int stem = 0;
374 if (iter.i == cursor.i) /* XXX */
375 continue;
377 while (other->leafname[stem] && item->leafname[stem])
379 gchar a, b;
380 /* Like the matches() function below, the comparison of
381 * leafs must be case-insensitive.
383 a = g_ascii_tolower(item->leafname[stem]);
384 b = g_ascii_tolower(other->leafname[stem]);
385 if (a != b)
386 break;
387 stem++;
390 /* stem is the index of the first difference */
391 if (stem >= current_stem &&
392 (shortest_stem == -1 || stem < shortest_stem))
393 shortest_stem = stem;
396 if (current_stem == shortest_stem)
398 /* We didn't add anything... */
399 if (o_filer_beep_fail.int_value)
400 gdk_beep();
402 else if (current_stem < shortest_stem)
404 gint tmp_pos;
406 /* Have to replace the leafname text in the minibuffer rather
407 * than just append to it. Here's an example:
408 * Suppose we have two dirs, /tmp and /TMP.
409 * The user enters /t in the minibuffer and hits Tab.
410 * With the g_ascii_tolower() code above, this would result
411 * in /tMP being bisplayed in the minibuffer which isn't
412 * intuitive. Therefore all text after the / must be replaced.
414 tmp_pos = leaf - text; /* index of start of leaf */
415 gtk_editable_delete_text(GTK_EDITABLE(entry),
416 tmp_pos, entry->text_length);
417 gtk_editable_insert_text(GTK_EDITABLE(entry),
418 item->leafname, shortest_stem,
419 &tmp_pos);
421 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
423 if (o_filer_beep_multi.int_value)
424 gdk_beep();
426 else
428 const guchar *new;
430 new = make_path(filer_window->sym_path, item->leafname);
432 if (item->base_type == TYPE_DIRECTORY &&
433 (item->flags & ITEM_FLAG_APPDIR) == 0)
434 new = make_path(new, "");
436 gtk_entry_set_text(entry, new);
437 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
441 static void path_changed(FilerWindow *filer_window)
443 GtkWidget *mini = filer_window->minibuffer;
444 const char *rawnew, *leaf;
445 char *path;
446 char *new = NULL;
447 gboolean error = FALSE;
449 rawnew = gtk_entry_get_text(GTK_ENTRY(mini));
450 if (!*rawnew)
452 /* Entry may be blank because we're in the middle of changing
453 * to something else...
455 entry_set_error(mini, FALSE);
456 return;
459 switch (rawnew[0])
461 case '/':
462 new=g_strdup(rawnew);
463 break;
465 case '~':
466 if (!rawnew[1] || rawnew[1]=='/')
468 new=g_strconcat(g_get_home_dir(), "/",
469 rawnew[1]? rawnew+2: "", "/",
470 NULL);
472 else
474 const char *sl;
475 gchar *username;
476 struct passwd *passwd;
479 /* Need to lookup user name */
480 for(sl=rawnew+2; *sl && *sl!='/'; sl++)
482 username=g_strndup(rawnew+1, sl-rawnew-1);
483 passwd=getpwnam(username);
484 g_free(username);
486 if(passwd)
488 new=g_strconcat(passwd->pw_dir, "/",
489 sl+1, "/",
490 NULL);
492 else
493 new=g_strdup(rawnew);
495 break;
497 default:
498 new=g_strdup(rawnew);
499 break;
503 leaf = g_basename(new);
504 if (leaf == new)
505 path = g_strdup("/");
506 else
507 path = g_path_get_dirname(new);
509 if (strcmp(path, filer_window->sym_path) != 0)
511 /* The new path is in a different directory */
512 struct stat info;
514 if (stat_with_timeout(path, &info) == 0 &&
515 S_ISDIR(info.st_mode))
517 filer_change_to(filer_window, path, leaf);
519 else
520 error = TRUE;
522 else
524 if (*leaf == '.' && !filer_window->temp_show_hidden)
526 filer_window->temp_show_hidden = TRUE;
527 display_update_hidden(filer_window);
530 if (find_exact_match(filer_window, leaf) == FALSE &&
531 find_next_match(filer_window, leaf, 0) == FALSE)
532 error = TRUE;
535 g_free(new);
536 g_free(path);
538 entry_set_error(mini, error);
541 /* Look for an exact match, and move the cursor to it if found.
542 * TRUE on success.
544 static gboolean find_exact_match(FilerWindow *filer_window,
545 const gchar *pattern)
547 DirItem *item;
548 ViewIter iter;
549 ViewIface *view = filer_window->view;
551 view_get_iter(view, &iter, 0);
553 while ((item = iter.next(&iter)))
555 if (strcmp(item->leafname, pattern) == 0)
557 view_cursor_to_iter(view, &iter);
558 return TRUE;
562 return FALSE;
565 /* Find the next item in the view that matches 'pattern'. Start from
566 * cursor_base and loop at either end. dir is 1 for a forward search,
567 * -1 for backwards. 0 means forwards, but may stay the same.
569 * Does not automatically update cursor_base.
571 * Returns TRUE if a match is found.
573 static gboolean find_next_match(FilerWindow *filer_window,
574 const char *pattern,
575 int dir)
577 ViewIface *view = filer_window->view;
578 ViewIter iter;
580 if (view_count_items(view) < 1)
581 return FALSE;
583 view_get_iter(view, &iter,
584 VIEW_ITER_FROM_BASE |
585 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
587 if (dir != 0)
588 iter.next(&iter); /* Don't look at the base itself */
590 while (iter.next(&iter))
592 if (matches(&iter, pattern))
594 view_cursor_to_iter(view, &iter);
595 return TRUE;
599 /* No matches (except possibly base itself) */
600 view_get_iter(view, &iter,
601 VIEW_ITER_FROM_BASE | VIEW_ITER_ONE_ONLY |
602 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
604 view_cursor_to_iter(view, &iter);
606 return FALSE;
609 static gboolean matches(ViewIter *iter, const char *pattern)
611 DirItem *item;
613 item = iter->peek(iter);
615 return strncasecmp(item->leafname, pattern, strlen(pattern)) == 0;
618 /* Find next match and set base for future matches. */
619 static void search_in_dir(FilerWindow *filer_window, int dir)
621 const char *path, *pattern;
622 ViewIter iter;
624 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
625 pattern = g_basename(path);
627 view_get_cursor(filer_window->view, &iter);
628 view_set_base(filer_window->view, &iter);
629 find_next_match(filer_window, pattern, dir);
630 view_get_cursor(filer_window->view, &iter);
631 view_set_base(filer_window->view, &iter);
634 /* SHELL COMMANDS */
636 static void add_to_history(const gchar *line)
638 guchar *last;
640 last = shell_history ? (guchar *) shell_history->data : NULL;
642 if (last && strcmp(last, line) == 0)
643 return; /* Duplicating last entry */
645 shell_history = g_list_prepend(shell_history, g_strdup(line));
648 static void shell_done(FilerWindow *filer_window)
650 if (filer_exists(filer_window))
651 filer_update_dir(filer_window, TRUE);
654 /* Given a list of matches, return the longest stem. g_free() the result.
655 * Special chars are escaped. If there is only a single (non-dir) match
656 * then a trailing space is added.
658 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
660 gchar *first = matches->gl_pathv[0];
661 int i;
662 int longest, path_len;
663 guchar *path, *tmp;
665 longest = strlen(first);
667 for (i = 1; i < matches->gl_pathc; i++)
669 int j;
670 guchar *m = matches->gl_pathv[i];
672 for (j = 0; j < longest; j++)
673 if (m[j] != first[j])
674 longest = j;
677 path_len = strlen(filer_window->sym_path);
678 if (strncmp(filer_window->sym_path, first, path_len) == 0 &&
679 first[path_len] == '/' && first[path_len + 1])
681 path = g_strndup(first + path_len + 1, longest - path_len - 1);
683 else
684 path = g_strndup(first, longest);
686 tmp = shell_escape(path);
687 g_free(path);
689 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
691 path = g_strdup_printf("%s ", tmp);
692 g_free(tmp);
693 return path;
696 return tmp;
699 static void shell_tab(FilerWindow *filer_window)
701 const gchar *entry;
702 int i;
703 int pos;
704 GString *leaf;
705 glob_t matches;
706 int leaf_start;
708 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
709 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
710 leaf = g_string_new(NULL);
712 for (i = 0; i < pos; i++)
714 guchar c = entry[i];
716 if (leaf->len == 0)
717 leaf_start = i;
719 if (c == ' ')
721 g_string_truncate(leaf, 0);
722 continue;
724 else if (c == '\\' && i + 1 < pos)
725 c = entry[++i];
726 else if (c == '"' || c == '\'')
728 for (++i; i < pos; i++)
730 guchar cc = entry[i];
732 if (cc == '\\' && i + 1 < pos)
733 cc = entry[++i];
734 else if (cc == c)
735 break;
736 g_string_append_c(leaf, cc);
738 continue;
741 g_string_append_c(leaf, c);
744 if (leaf->len == 0)
745 leaf_start = pos;
747 if (leaf->str[0] != '/' && leaf->str[0] != '~')
749 g_string_prepend_c(leaf, '/');
750 g_string_prepend(leaf, filer_window->sym_path);
753 g_string_append_c(leaf, '*');
755 if (glob(leaf->str,
756 #ifdef GLOB_TILDE
757 GLOB_TILDE |
758 #endif
759 GLOB_MARK, NULL, &matches) == 0)
761 if (matches.gl_pathc > 0)
763 guchar *best;
764 GtkEditable *edit =
765 GTK_EDITABLE(filer_window->minibuffer);
767 best = best_match(filer_window, &matches);
769 gtk_editable_delete_text(edit, leaf_start, pos);
770 gtk_editable_insert_text(edit, best, strlen(best),
771 &leaf_start);
772 gtk_editable_set_position(edit, leaf_start);
774 g_free(best);
776 if (matches.gl_pathc != 1)
777 gdk_beep();
779 globfree(&matches);
782 g_string_free(leaf, TRUE);
785 static void run_child(gpointer unused)
787 /* Ensure output is noticed - send stdout to stderr */
788 dup2(STDERR_FILENO, STDOUT_FILENO);
789 close_on_exec(STDOUT_FILENO, FALSE);
792 /* Either execute the command or make it the default run action */
793 static void shell_return_pressed(FilerWindow *filer_window)
795 GPtrArray *argv;
796 const gchar *entry;
797 GError *error = NULL;
798 pid_t child;
799 DirItem *item;
800 ViewIter iter;
802 entry = mini_contents(filer_window);
804 if (!entry)
805 goto out;
807 add_to_history(entry);
809 argv = g_ptr_array_new();
810 g_ptr_array_add(argv, "sh");
811 g_ptr_array_add(argv, "-c");
812 g_ptr_array_add(argv, (gchar *) entry);
813 g_ptr_array_add(argv, "sh");
815 view_get_iter(filer_window->view, &iter, 0);
816 while ((item = iter.next(&iter)))
818 if (view_get_selected(filer_window->view, &iter))
819 g_ptr_array_add(argv, item->leafname);
822 g_ptr_array_add(argv, NULL);
824 if (!g_spawn_async_with_pipes(filer_window->sym_path,
825 (gchar **) argv->pdata, NULL,
826 G_SPAWN_DO_NOT_REAP_CHILD |
827 G_SPAWN_SEARCH_PATH,
828 run_child, NULL, /* Child setup fn */
829 &child, /* Child PID */
830 NULL, NULL, NULL, /* Standard pipes */
831 &error))
833 delayed_error("%s", error ? error->message : "(null)");
834 g_error_free(error);
836 else
837 on_child_death(child, (CallbackFn) shell_done, filer_window);
839 g_ptr_array_free(argv, TRUE);
841 out:
842 minibuffer_hide(filer_window);
845 /* Move through the shell history */
846 static void shell_recall(FilerWindow *filer_window, int dir)
848 guchar *command;
849 int pos = filer_window->mini_cursor_base;
851 pos += dir;
852 if (pos >= 0)
854 command = g_list_nth_data(shell_history, pos);
855 if (!command)
856 return;
858 else
859 command = "";
861 if (pos < -1)
862 pos = -1;
863 filer_window->mini_cursor_base = pos;
865 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
868 /* SELECT IF */
870 typedef struct {
871 FindInfo info;
872 FilerWindow *filer_window;
873 FindCondition *cond;
874 } SelectData;
876 static gboolean select_if_test(ViewIter *iter, gpointer user_data)
878 DirItem *item;
879 SelectData *data = user_data;
881 item = iter->peek(iter);
882 g_return_val_if_fail(item != NULL, FALSE);
884 data->info.leaf = item->leafname;
885 data->info.fullpath = make_path(data->filer_window->sym_path,
886 data->info.leaf);
888 return mc_lstat(data->info.fullpath, &data->info.stats) == 0 &&
889 find_test_condition(data->cond, &data->info);
892 static void select_return_pressed(FilerWindow *filer_window, guint etime)
894 const gchar *entry;
895 SelectData data;
897 entry = mini_contents(filer_window);
899 if (!entry)
900 goto out;
902 add_to_history(entry);
904 data.cond = find_compile(entry);
905 if (!data.cond)
907 delayed_error(_("Invalid Find condition"));
908 return;
911 data.info.now = time(NULL);
912 data.info.prune = FALSE; /* (don't care) */
913 data.filer_window = filer_window;
915 view_select_if(filer_window->view, select_if_test, &data);
917 find_condition_free(data.cond);
918 out:
919 minibuffer_hide(filer_window);
922 static void filter_return_pressed(FilerWindow *filer_window, guint etime)
924 const gchar *entry;
926 entry = mini_contents(filer_window);
928 if (entry && *entry && strcmp(entry, "*")!=0) {
929 display_set_filter(filer_window, FILER_SHOW_GLOB,
930 entry);
931 } else {
932 display_set_filter(filer_window, FILER_SHOW_ALL, NULL);
934 minibuffer_hide(filer_window);
938 /* EVENT HANDLERS */
940 static gint key_press_event(GtkWidget *widget,
941 GdkEventKey *event,
942 FilerWindow *filer_window)
944 if (event->keyval == GDK_Escape)
946 if (filer_window->mini_type == MINI_SHELL)
948 const gchar *line;
950 line = mini_contents(filer_window);
951 if (line)
952 add_to_history(line);
955 minibuffer_hide(filer_window);
956 return TRUE;
959 switch (filer_window->mini_type)
961 case MINI_PATH:
962 switch (event->keyval)
964 case GDK_Up:
965 search_in_dir(filer_window, -1);
966 break;
967 case GDK_Down:
968 search_in_dir(filer_window, 1);
969 break;
970 case GDK_Return:
971 case GDK_KP_Enter:
972 path_return_pressed(filer_window,
973 event);
974 break;
975 case GDK_Tab:
976 complete(filer_window);
977 break;
978 default:
979 return FALSE;
981 break;
983 case MINI_SHELL:
984 switch (event->keyval)
986 case GDK_Up:
987 shell_recall(filer_window, 1);
988 break;
989 case GDK_Down:
990 shell_recall(filer_window, -1);
991 break;
992 case GDK_Tab:
993 shell_tab(filer_window);
994 break;
995 case GDK_Return:
996 case GDK_KP_Enter:
997 shell_return_pressed(filer_window);
998 break;
999 default:
1000 return FALSE;
1002 break;
1003 case MINI_SELECT_IF:
1004 switch (event->keyval)
1006 case GDK_Up:
1007 shell_recall(filer_window, 1);
1008 break;
1009 case GDK_Down:
1010 shell_recall(filer_window, -1);
1011 break;
1012 case GDK_Tab:
1013 break;
1014 case GDK_Return:
1015 case GDK_KP_Enter:
1016 select_return_pressed(filer_window,
1017 event->time);
1018 break;
1019 default:
1020 return FALSE;
1022 break;
1023 case MINI_SELECT_BY_NAME:
1024 switch (event->keyval)
1026 case GDK_Up:
1027 filer_next_selected(filer_window, -1);
1028 break;
1029 case GDK_Down:
1030 filer_next_selected(filer_window, 1);
1031 break;
1032 case GDK_Tab:
1033 break;
1034 case GDK_Return:
1035 case GDK_KP_Enter:
1036 minibuffer_hide(filer_window);
1037 break;
1038 default:
1039 return FALSE;
1041 break;
1043 case MINI_FILTER:
1044 switch (event->keyval)
1046 case GDK_Return:
1047 case GDK_KP_Enter:
1048 filter_return_pressed(filer_window,
1049 event->time);
1050 break;
1051 default:
1052 return FALSE;
1054 break;
1055 default:
1056 break;
1059 return TRUE;
1062 static gboolean select_if_glob(ViewIter *iter, gpointer data)
1064 DirItem *item;
1065 const char *pattern = (char *) data;
1067 item = iter->peek(iter);
1068 g_return_val_if_fail(item != NULL, FALSE);
1070 return fnmatch(pattern, item->leafname, 0) == 0;
1073 static void changed(GtkEditable *mini, FilerWindow *filer_window)
1075 switch (filer_window->mini_type)
1077 case MINI_PATH:
1078 path_changed(filer_window);
1079 return;
1080 case MINI_SELECT_IF:
1081 set_find_string_colour(GTK_WIDGET(mini),
1082 gtk_entry_get_text(
1083 GTK_ENTRY(filer_window->minibuffer)));
1084 return;
1085 case MINI_SELECT_BY_NAME:
1086 view_select_if(filer_window->view,
1087 select_if_glob,
1088 (gpointer) gtk_entry_get_text(
1089 GTK_ENTRY(filer_window->minibuffer)));
1090 return;
1091 default:
1092 break;
1096 /* Returns a string (which must NOT be freed), or NULL if the buffer
1097 * is blank (whitespace only).
1099 static const gchar *mini_contents(FilerWindow *filer_window)
1101 const gchar *entry, *c;
1103 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
1105 for (c = entry; *c; c++)
1106 if (!g_ascii_isspace(*c))
1107 return entry;
1109 return NULL;
1112 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
1113 * behaviour.
1115 static gboolean grab_focus(GtkWidget *minibuffer)
1117 GtkWidgetClass *class;
1119 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET));
1121 class->grab_focus(minibuffer);
1123 g_signal_stop_emission(minibuffer,
1124 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer)), 0);
1127 return 1;