Use Name entry of .desktop files for pinboard/panel icons.
[rox-filer/dt.git] / ROX-Filer / src / minibuffer.c
blob9beb9e4c3402dadb452974644d81e343d90e3126
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 *tmp;
180 tmp = g_strconcat(" ", item->leafname, NULL);
181 gtk_entry_set_text(mini, tmp);
182 g_free(tmp);
184 else
185 gtk_entry_set_text(mini, "");
186 filer_window->mini_cursor_base = -1; /* History */
187 break;
189 default:
190 g_warning("Bad minibuffer type\n");
191 return;
194 filer_window->mini_type = mini_type;
196 gtk_editable_set_position(GTK_EDITABLE(mini), pos);
198 gtk_widget_show_all(filer_window->minibuffer_area);
200 gtk_widget_grab_focus(filer_window->minibuffer);
203 void minibuffer_hide(FilerWindow *filer_window)
205 filer_window->mini_type = MINI_NONE;
207 gtk_widget_hide(filer_window->minibuffer_area);
209 gtk_widget_child_focus(filer_window->window, GTK_DIR_TAB_FORWARD);
211 if (filer_window->temp_show_hidden)
213 DirItem *item;
214 ViewIter iter;
216 view_get_cursor(filer_window->view, &iter);
217 item = iter.peek(&iter);
219 if (item == NULL || item->leafname[0] != '.')
220 display_update_hidden(filer_window);
221 filer_window->temp_show_hidden = FALSE;
225 /* Insert this leafname at the cursor (replacing the selection, if any).
226 * Must be in SHELL mode.
228 void minibuffer_add(FilerWindow *filer_window, const gchar *leafname)
230 guchar *esc;
231 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
232 GtkEntry *entry = GTK_ENTRY(edit);
233 int pos;
235 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
237 esc = shell_escape(leafname);
239 gtk_editable_delete_selection(edit);
240 pos = gtk_editable_get_position(edit);
242 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
243 gtk_editable_insert_text(edit, " ", 1, &pos);
245 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
246 gtk_editable_set_position(edit, pos);
248 g_free(esc);
252 /****************************************************************
253 * INTERNAL FUNCTIONS *
254 ****************************************************************/
256 static void show_help(FilerWindow *filer_window)
258 switch (filer_window->mini_type)
260 case MINI_PATH:
261 info_message(
262 _("Enter the name of a file and I'll display "
263 "it for you. Press Tab to fill in the longest "
264 "match. Escape to close the minibuffer."));
265 break;
266 case MINI_SHELL:
267 info_message(
268 _("Enter a shell command to execute. Click "
269 "on a file to add it to the buffer."));
270 break;
271 case MINI_SELECT_BY_NAME:
272 info_message(
273 _("Enter a file name pattern to select all matching files:\n\n"
274 "? means any character\n"
275 "* means zero or more characters\n"
276 "[aA] means 'a' or 'A'\n"
277 "[a-z] means any character from a to z (lowercase)\n"
278 "*.png means any name ending in '.png'"));
279 break;
280 case MINI_SELECT_IF:
281 show_condition_help(NULL);
282 break;
283 case MINI_FILTER:
284 info_message(
285 _("Enter a pattern to match for files to "
286 "be shown. An empty filter turns the "
287 "filter off."));
288 break;
289 default:
290 g_warning("Unknown minibuffer type!");
291 break;
296 /* PATH ENTRY */
298 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
300 const gchar *path, *pattern;
301 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
302 ViewIter iter;
303 DirItem *item;
305 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
306 pattern = g_basename(path);
308 view_get_cursor(filer_window->view, &iter);
310 item = iter.peek(&iter);
311 if (item == NULL || !matches(&iter, pattern))
313 gdk_beep();
314 return;
317 if ((event->state & GDK_SHIFT_MASK) != 0)
318 flags |= OPEN_SHIFT;
320 filer_openitem(filer_window, &iter, flags);
323 /* Use the cursor item to fill in the minibuffer.
324 * If there are multiple matches then fill in as much as possible and
325 * (possibly) beep.
327 static void complete(FilerWindow *filer_window)
329 GtkEntry *entry;
330 DirItem *item, *other;
331 int shortest_stem = -1;
332 int current_stem;
333 const gchar *text, *leaf;
334 ViewIter cursor, iter;
336 view_get_cursor(filer_window->view, &cursor);
337 item = cursor.peek(&cursor);
339 if (!item)
341 gdk_beep();
342 return;
345 entry = GTK_ENTRY(filer_window->minibuffer);
347 text = gtk_entry_get_text(entry);
348 leaf = strrchr(text, '/');
349 if (!leaf)
351 gdk_beep();
352 return;
355 leaf++;
356 if (!matches(&cursor, leaf))
358 gdk_beep();
359 return;
362 current_stem = strlen(leaf);
364 /* Find the longest other match of this name. If it's longer than
365 * the currently entered text then complete only up to that length.
367 view_get_iter(filer_window->view, &iter, 0);
368 while ((other = iter.next(&iter)))
370 int stem = 0;
372 if (iter.i == cursor.i) /* XXX */
373 continue;
375 while (other->leafname[stem] && item->leafname[stem])
377 gchar a, b;
378 /* Like the matches() function below, the comparison of
379 * leafs must be case-insensitive.
381 a = g_ascii_tolower(item->leafname[stem]);
382 b = g_ascii_tolower(other->leafname[stem]);
383 if (a != b)
384 break;
385 stem++;
388 /* stem is the index of the first difference */
389 if (stem >= current_stem &&
390 (shortest_stem == -1 || stem < shortest_stem))
391 shortest_stem = stem;
394 if (current_stem == shortest_stem)
396 /* We didn't add anything... */
397 if (o_filer_beep_fail.int_value)
398 gdk_beep();
400 else if (current_stem < shortest_stem)
402 gint tmp_pos;
404 /* Have to replace the leafname text in the minibuffer rather
405 * than just append to it. Here's an example:
406 * Suppose we have two dirs, /tmp and /TMP.
407 * The user enters /t in the minibuffer and hits Tab.
408 * With the g_ascii_tolower() code above, this would result
409 * in /tMP being bisplayed in the minibuffer which isn't
410 * intuitive. Therefore all text after the / must be replaced.
412 tmp_pos = leaf - text; /* index of start of leaf */
413 gtk_editable_delete_text(GTK_EDITABLE(entry),
414 tmp_pos, entry->text_length);
415 gtk_editable_insert_text(GTK_EDITABLE(entry),
416 item->leafname, shortest_stem,
417 &tmp_pos);
419 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
421 if (o_filer_beep_multi.int_value)
422 gdk_beep();
424 else
426 const guchar *new;
428 new = make_path(filer_window->sym_path, item->leafname);
430 if (item->base_type == TYPE_DIRECTORY &&
431 (item->flags & ITEM_FLAG_APPDIR) == 0)
432 new = make_path(new, "");
434 gtk_entry_set_text(entry, new);
435 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
439 static void path_changed(FilerWindow *filer_window)
441 GtkWidget *mini = filer_window->minibuffer;
442 const char *rawnew, *leaf;
443 char *path;
444 char *new = NULL;
445 gboolean error = FALSE;
447 rawnew = gtk_entry_get_text(GTK_ENTRY(mini));
448 if (!*rawnew)
450 /* Entry may be blank because we're in the middle of changing
451 * to something else...
453 entry_set_error(mini, FALSE);
454 return;
457 switch (rawnew[0])
459 case '/':
460 new=g_strdup(rawnew);
461 break;
463 case '~':
464 if (!rawnew[1] || rawnew[1]=='/')
466 new=g_strconcat(g_get_home_dir(), "/",
467 rawnew[1]? rawnew+2: "", "/",
468 NULL);
470 else
472 const char *sl;
473 gchar *username;
474 struct passwd *passwd;
477 /* Need to lookup user name */
478 for(sl=rawnew+2; *sl && *sl!='/'; sl++)
480 username=g_strndup(rawnew+1, sl-rawnew-1);
481 passwd=getpwnam(username);
482 g_free(username);
484 if(passwd)
486 new=g_strconcat(passwd->pw_dir, "/",
487 sl+1, "/",
488 NULL);
490 else
491 new=g_strdup(rawnew);
493 break;
495 default:
496 new=g_strdup(rawnew);
497 break;
501 leaf = g_basename(new);
502 if (leaf == new)
503 path = g_strdup("/");
504 else
505 path = g_path_get_dirname(new);
507 if (strcmp(path, filer_window->sym_path) != 0)
509 /* The new path is in a different directory */
510 struct stat info;
512 if (stat_with_timeout(path, &info) == 0 &&
513 S_ISDIR(info.st_mode))
515 filer_change_to(filer_window, path, leaf);
517 else
518 error = TRUE;
520 else
522 if (*leaf == '.' && !filer_window->temp_show_hidden)
524 filer_window->temp_show_hidden = TRUE;
525 display_update_hidden(filer_window);
528 if (find_exact_match(filer_window, leaf) == FALSE &&
529 find_next_match(filer_window, leaf, 0) == FALSE)
530 error = TRUE;
533 g_free(new);
534 g_free(path);
536 entry_set_error(mini, error);
539 /* Look for an exact match, and move the cursor to it if found.
540 * TRUE on success.
542 static gboolean find_exact_match(FilerWindow *filer_window,
543 const gchar *pattern)
545 DirItem *item;
546 ViewIter iter;
547 ViewIface *view = filer_window->view;
549 view_get_iter(view, &iter, 0);
551 while ((item = iter.next(&iter)))
553 if (strcmp(item->leafname, pattern) == 0)
555 view_cursor_to_iter(view, &iter);
556 return TRUE;
560 return FALSE;
563 /* Find the next item in the view that matches 'pattern'. Start from
564 * cursor_base and loop at either end. dir is 1 for a forward search,
565 * -1 for backwards. 0 means forwards, but may stay the same.
567 * Does not automatically update cursor_base.
569 * Returns TRUE if a match is found.
571 static gboolean find_next_match(FilerWindow *filer_window,
572 const char *pattern,
573 int dir)
575 ViewIface *view = filer_window->view;
576 ViewIter iter;
578 if (view_count_items(view) < 1)
579 return FALSE;
581 view_get_iter(view, &iter,
582 VIEW_ITER_FROM_BASE |
583 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
585 if (dir != 0)
586 iter.next(&iter); /* Don't look at the base itself */
588 while (iter.next(&iter))
590 if (matches(&iter, pattern))
592 view_cursor_to_iter(view, &iter);
593 return TRUE;
597 /* No matches (except possibly base itself) */
598 view_get_iter(view, &iter,
599 VIEW_ITER_FROM_BASE | VIEW_ITER_ONE_ONLY |
600 (dir >= 0 ? 0 : VIEW_ITER_BACKWARDS));
602 view_cursor_to_iter(view, &iter);
604 return FALSE;
607 static gboolean matches(ViewIter *iter, const char *pattern)
609 DirItem *item;
611 item = iter->peek(iter);
613 return strncasecmp(item->leafname, pattern, strlen(pattern)) == 0;
616 /* Find next match and set base for future matches. */
617 static void search_in_dir(FilerWindow *filer_window, int dir)
619 const char *path, *pattern;
620 ViewIter iter;
622 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
623 pattern = g_basename(path);
625 view_get_cursor(filer_window->view, &iter);
626 view_set_base(filer_window->view, &iter);
627 find_next_match(filer_window, pattern, dir);
628 view_get_cursor(filer_window->view, &iter);
629 view_set_base(filer_window->view, &iter);
632 /* SHELL COMMANDS */
634 static void add_to_history(const gchar *line)
636 guchar *last;
638 last = shell_history ? (guchar *) shell_history->data : NULL;
640 if (last && strcmp(last, line) == 0)
641 return; /* Duplicating last entry */
643 shell_history = g_list_prepend(shell_history, g_strdup(line));
646 static void shell_done(FilerWindow *filer_window)
648 if (filer_exists(filer_window))
649 filer_update_dir(filer_window, TRUE);
652 /* Given a list of matches, return the longest stem. g_free() the result.
653 * Special chars are escaped. If there is only a single (non-dir) match
654 * then a trailing space is added.
656 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
658 gchar *first = matches->gl_pathv[0];
659 int i;
660 int longest, path_len;
661 guchar *path, *tmp;
663 longest = strlen(first);
665 for (i = 1; i < matches->gl_pathc; i++)
667 int j;
668 guchar *m = matches->gl_pathv[i];
670 for (j = 0; j < longest; j++)
671 if (m[j] != first[j])
672 longest = j;
675 path_len = strlen(filer_window->sym_path);
676 if (strncmp(filer_window->sym_path, first, path_len) == 0 &&
677 first[path_len] == '/' && first[path_len + 1])
679 path = g_strndup(first + path_len + 1, longest - path_len - 1);
681 else
682 path = g_strndup(first, longest);
684 tmp = shell_escape(path);
685 g_free(path);
687 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
689 path = g_strdup_printf("%s ", tmp);
690 g_free(tmp);
691 return path;
694 return tmp;
697 static void shell_tab(FilerWindow *filer_window)
699 const gchar *entry;
700 int i;
701 int pos;
702 GString *leaf;
703 glob_t matches;
704 int leaf_start;
706 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
707 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
708 leaf = g_string_new(NULL);
710 for (i = 0; i < pos; i++)
712 guchar c = entry[i];
714 if (leaf->len == 0)
715 leaf_start = i;
717 if (c == ' ')
719 g_string_truncate(leaf, 0);
720 continue;
722 else if (c == '\\' && i + 1 < pos)
723 c = entry[++i];
724 else if (c == '"' || c == '\'')
726 for (++i; i < pos; i++)
728 guchar cc = entry[i];
730 if (cc == '\\' && i + 1 < pos)
731 cc = entry[++i];
732 else if (cc == c)
733 break;
734 g_string_append_c(leaf, cc);
736 continue;
739 g_string_append_c(leaf, c);
742 if (leaf->len == 0)
743 leaf_start = pos;
745 if (leaf->str[0] != '/' && leaf->str[0] != '~')
747 g_string_prepend_c(leaf, '/');
748 g_string_prepend(leaf, filer_window->sym_path);
751 g_string_append_c(leaf, '*');
753 if (glob(leaf->str,
754 #ifdef GLOB_TILDE
755 GLOB_TILDE |
756 #endif
757 GLOB_MARK, NULL, &matches) == 0)
759 if (matches.gl_pathc > 0)
761 guchar *best;
762 GtkEditable *edit =
763 GTK_EDITABLE(filer_window->minibuffer);
765 best = best_match(filer_window, &matches);
767 gtk_editable_delete_text(edit, leaf_start, pos);
768 gtk_editable_insert_text(edit, best, strlen(best),
769 &leaf_start);
770 gtk_editable_set_position(edit, leaf_start);
772 g_free(best);
774 if (matches.gl_pathc != 1)
775 gdk_beep();
777 globfree(&matches);
780 g_string_free(leaf, TRUE);
783 static void run_child(gpointer unused)
785 /* Ensure output is noticed - send stdout to stderr */
786 dup2(STDERR_FILENO, STDOUT_FILENO);
787 close_on_exec(STDOUT_FILENO, FALSE);
790 /* Either execute the command or make it the default run action */
791 static void shell_return_pressed(FilerWindow *filer_window)
793 GPtrArray *argv;
794 const gchar *entry;
795 GError *error = NULL;
796 pid_t child;
797 DirItem *item;
798 ViewIter iter;
800 entry = mini_contents(filer_window);
802 if (!entry)
803 goto out;
805 add_to_history(entry);
807 argv = g_ptr_array_new();
808 g_ptr_array_add(argv, "sh");
809 g_ptr_array_add(argv, "-c");
810 g_ptr_array_add(argv, (gchar *) entry);
811 g_ptr_array_add(argv, "sh");
813 view_get_iter(filer_window->view, &iter, 0);
814 while ((item = iter.next(&iter)))
816 if (view_get_selected(filer_window->view, &iter))
817 g_ptr_array_add(argv, item->leafname);
820 g_ptr_array_add(argv, NULL);
822 if (!g_spawn_async_with_pipes(filer_window->sym_path,
823 (gchar **) argv->pdata, NULL,
824 G_SPAWN_DO_NOT_REAP_CHILD |
825 G_SPAWN_SEARCH_PATH,
826 run_child, NULL, /* Child setup fn */
827 &child, /* Child PID */
828 NULL, NULL, NULL, /* Standard pipes */
829 &error))
831 delayed_error("%s", error ? error->message : "(null)");
832 g_error_free(error);
834 else
835 on_child_death(child, (CallbackFn) shell_done, filer_window);
837 g_ptr_array_free(argv, TRUE);
839 out:
840 minibuffer_hide(filer_window);
843 /* Move through the shell history */
844 static void shell_recall(FilerWindow *filer_window, int dir)
846 guchar *command;
847 int pos = filer_window->mini_cursor_base;
849 pos += dir;
850 if (pos >= 0)
852 command = g_list_nth_data(shell_history, pos);
853 if (!command)
854 return;
856 else
857 command = "";
859 if (pos < -1)
860 pos = -1;
861 filer_window->mini_cursor_base = pos;
863 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
866 /* SELECT IF */
868 typedef struct {
869 FindInfo info;
870 FilerWindow *filer_window;
871 FindCondition *cond;
872 } SelectData;
874 static gboolean select_if_test(ViewIter *iter, gpointer user_data)
876 DirItem *item;
877 SelectData *data = user_data;
879 item = iter->peek(iter);
880 g_return_val_if_fail(item != NULL, FALSE);
882 data->info.leaf = item->leafname;
883 data->info.fullpath = make_path(data->filer_window->sym_path,
884 data->info.leaf);
886 return mc_lstat(data->info.fullpath, &data->info.stats) == 0 &&
887 find_test_condition(data->cond, &data->info);
890 static void select_return_pressed(FilerWindow *filer_window, guint etime)
892 const gchar *entry;
893 SelectData data;
895 entry = mini_contents(filer_window);
897 if (!entry)
898 goto out;
900 add_to_history(entry);
902 data.cond = find_compile(entry);
903 if (!data.cond)
905 delayed_error(_("Invalid Find condition"));
906 return;
909 data.info.now = time(NULL);
910 data.info.prune = FALSE; /* (don't care) */
911 data.filer_window = filer_window;
913 view_select_if(filer_window->view, select_if_test, &data);
915 find_condition_free(data.cond);
916 out:
917 minibuffer_hide(filer_window);
920 static void filter_return_pressed(FilerWindow *filer_window, guint etime)
922 const gchar *entry;
924 entry = mini_contents(filer_window);
926 if (entry && *entry && strcmp(entry, "*")!=0) {
927 display_set_filter(filer_window, FILER_SHOW_GLOB,
928 entry);
929 } else {
930 display_set_filter(filer_window, FILER_SHOW_ALL, NULL);
932 minibuffer_hide(filer_window);
936 /* EVENT HANDLERS */
938 static gint key_press_event(GtkWidget *widget,
939 GdkEventKey *event,
940 FilerWindow *filer_window)
942 if (event->keyval == GDK_Escape)
944 if (filer_window->mini_type == MINI_SHELL)
946 const gchar *line;
948 line = mini_contents(filer_window);
949 if (line)
950 add_to_history(line);
953 minibuffer_hide(filer_window);
954 return TRUE;
957 switch (filer_window->mini_type)
959 case MINI_PATH:
960 switch (event->keyval)
962 case GDK_Up:
963 search_in_dir(filer_window, -1);
964 break;
965 case GDK_Down:
966 search_in_dir(filer_window, 1);
967 break;
968 case GDK_Return:
969 case GDK_KP_Enter:
970 path_return_pressed(filer_window,
971 event);
972 break;
973 case GDK_Tab:
974 complete(filer_window);
975 break;
976 default:
977 return FALSE;
979 break;
981 case MINI_SHELL:
982 switch (event->keyval)
984 case GDK_Up:
985 shell_recall(filer_window, 1);
986 break;
987 case GDK_Down:
988 shell_recall(filer_window, -1);
989 break;
990 case GDK_Tab:
991 shell_tab(filer_window);
992 break;
993 case GDK_Return:
994 case GDK_KP_Enter:
995 shell_return_pressed(filer_window);
996 break;
997 default:
998 return FALSE;
1000 break;
1001 case MINI_SELECT_IF:
1002 switch (event->keyval)
1004 case GDK_Up:
1005 shell_recall(filer_window, 1);
1006 break;
1007 case GDK_Down:
1008 shell_recall(filer_window, -1);
1009 break;
1010 case GDK_Tab:
1011 break;
1012 case GDK_Return:
1013 case GDK_KP_Enter:
1014 select_return_pressed(filer_window,
1015 event->time);
1016 break;
1017 default:
1018 return FALSE;
1020 break;
1021 case MINI_SELECT_BY_NAME:
1022 switch (event->keyval)
1024 case GDK_Up:
1025 filer_next_selected(filer_window, -1);
1026 break;
1027 case GDK_Down:
1028 filer_next_selected(filer_window, 1);
1029 break;
1030 case GDK_Tab:
1031 break;
1032 case GDK_Return:
1033 case GDK_KP_Enter:
1034 minibuffer_hide(filer_window);
1035 break;
1036 default:
1037 return FALSE;
1039 break;
1041 case MINI_FILTER:
1042 switch (event->keyval)
1044 case GDK_Return:
1045 case GDK_KP_Enter:
1046 filter_return_pressed(filer_window,
1047 event->time);
1048 break;
1049 default:
1050 return FALSE;
1052 break;
1053 default:
1054 break;
1057 return TRUE;
1060 static gboolean select_if_glob(ViewIter *iter, gpointer data)
1062 DirItem *item;
1063 const char *pattern = (char *) data;
1065 item = iter->peek(iter);
1066 g_return_val_if_fail(item != NULL, FALSE);
1068 return fnmatch(pattern, item->leafname, 0) == 0;
1071 static void changed(GtkEditable *mini, FilerWindow *filer_window)
1073 switch (filer_window->mini_type)
1075 case MINI_PATH:
1076 path_changed(filer_window);
1077 return;
1078 case MINI_SELECT_IF:
1079 set_find_string_colour(GTK_WIDGET(mini),
1080 gtk_entry_get_text(
1081 GTK_ENTRY(filer_window->minibuffer)));
1082 return;
1083 case MINI_SELECT_BY_NAME:
1084 view_select_if(filer_window->view,
1085 select_if_glob,
1086 (gpointer) gtk_entry_get_text(
1087 GTK_ENTRY(filer_window->minibuffer)));
1088 return;
1089 default:
1090 break;
1094 /* Returns a string (which must NOT be freed), or NULL if the buffer
1095 * is blank (whitespace only).
1097 static const gchar *mini_contents(FilerWindow *filer_window)
1099 const gchar *entry, *c;
1101 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
1103 for (c = entry; *c; c++)
1104 if (!g_ascii_isspace(*c))
1105 return entry;
1107 return NULL;
1110 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
1111 * behaviour.
1113 static gboolean grab_focus(GtkWidget *minibuffer)
1115 GtkWidgetClass *class;
1117 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET));
1119 class->grab_focus(minibuffer);
1121 g_signal_stop_emission(minibuffer,
1122 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer)), 0);
1125 return 1;