4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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)
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
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 */
33 #include <gdk/gdkkeysyms.h>
38 #include "collection.h"
40 #include "gui_support.h"
42 #include "minibuffer.h"
50 static GList
*shell_history
= NULL
;
52 /* Static prototypes */
53 static gint
key_press_event(GtkWidget
*widget
,
55 FilerWindow
*filer_window
);
56 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
);
57 static gboolean
find_next_match(FilerWindow
*filer_window
,
60 static gboolean
find_exact_match(FilerWindow
*filer_window
,
61 const gchar
*pattern
);
62 static gboolean
matches(Collection
*collection
, int item
, const char *pattern
);
63 static void search_in_dir(FilerWindow
*filer_window
, int dir
);
64 static const gchar
*mini_contents(FilerWindow
*filer_window
);
65 static void show_help(FilerWindow
*filer_window
);
66 static gboolean
grab_focus(GtkWidget
*minibuffer
);
68 /****************************************************************
69 * EXTERNAL INTERFACE *
70 ****************************************************************/
72 static Option o_filer_beep_fail
, o_filer_beep_multi
;
74 void minibuffer_init(void)
76 option_add_int(&o_filer_beep_fail
, "filer_beep_fail", 1);
77 option_add_int(&o_filer_beep_multi
, "filer_beep_multi", 1);
80 /* Creates the minibuffer widgets, setting the appropriate fields
83 void create_minibuffer(FilerWindow
*filer_window
)
85 GtkWidget
*hbox
, *label
, *mini
;
87 hbox
= gtk_hbox_new(FALSE
, 0);
89 gtk_box_pack_start(GTK_BOX(hbox
),
90 new_help_button((HelpFunc
) show_help
, filer_window
),
93 label
= gtk_label_new("");
94 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, TRUE
, 2);
96 mini
= gtk_entry_new();
97 gtk_box_pack_start(GTK_BOX(hbox
), mini
, TRUE
, TRUE
, 0);
98 gtk_widget_set_name(mini
, "fixed-style");
99 g_signal_connect(mini
, "key_press_event",
100 G_CALLBACK(key_press_event
), filer_window
);
101 g_signal_connect(mini
, "changed", G_CALLBACK(changed
), filer_window
);
103 /* Grabbing focus musn't select the text... */
104 g_signal_connect_swapped(mini
, "grab-focus",
105 G_CALLBACK(grab_focus
), mini
);
107 filer_window
->minibuffer
= mini
;
108 filer_window
->minibuffer_label
= label
;
109 filer_window
->minibuffer_area
= hbox
;
112 void minibuffer_show(FilerWindow
*filer_window
, MiniType mini_type
)
114 Collection
*collection
;
118 g_return_if_fail(filer_window
!= NULL
);
119 g_return_if_fail(filer_window
->minibuffer
!= NULL
);
121 mini
= GTK_ENTRY(filer_window
->minibuffer
);
123 filer_window
->mini_type
= MINI_NONE
;
124 gtk_label_set_text(GTK_LABEL(filer_window
->minibuffer_label
),
125 mini_type
== MINI_PATH
? _("Goto:") :
126 mini_type
== MINI_SHELL
? _("Shell:") :
127 mini_type
== MINI_SELECT_IF
? _("Select If:") :
130 collection
= filer_window
->collection
;
134 collection_move_cursor(collection
, 0, 0);
135 filer_window
->mini_cursor_base
=
136 MAX(collection
->cursor_item
, 0);
137 gtk_entry_set_text(mini
,
138 make_path(filer_window
->path
, "")->str
);
139 if (filer_window
->temp_show_hidden
)
141 display_set_hidden(filer_window
, FALSE
);
142 filer_window
->temp_show_hidden
= FALSE
;
146 gtk_entry_set_text(mini
, "");
147 filer_window
->mini_cursor_base
= -1; /* History */
151 i
= collection
->cursor_item
;
152 if (collection
->number_selected
)
153 gtk_entry_set_text(mini
, " \"$@\"");
154 else if (i
> -1 && i
< collection
->number_of_items
)
156 DirItem
*item
= (DirItem
*)
157 collection
->items
[i
].data
;
160 tmp
= g_strconcat(" ", item
->leafname
, NULL
);
161 gtk_entry_set_text(mini
, tmp
);
165 gtk_entry_set_text(mini
, "");
166 filer_window
->mini_cursor_base
= -1; /* History */
169 g_warning("Bad minibuffer type\n");
173 filer_window
->mini_type
= mini_type
;
175 gtk_editable_set_position(GTK_EDITABLE(mini
), pos
);
177 gtk_widget_show_all(filer_window
->minibuffer_area
);
179 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
180 filer_window
->minibuffer
);
183 void minibuffer_hide(FilerWindow
*filer_window
)
185 filer_window
->mini_type
= MINI_NONE
;
187 gtk_widget_hide(filer_window
->minibuffer_area
);
188 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
189 GTK_WIDGET(filer_window
->collection
));
191 if (filer_window
->temp_show_hidden
)
193 Collection
*collection
= filer_window
->collection
;
194 int i
= collection
->cursor_item
;
195 DirItem
*item
= NULL
;
197 if (i
>= 0 && i
< collection
->number_of_items
)
198 item
= (DirItem
*) collection
->items
[i
].data
;
200 if (item
== NULL
|| item
->leafname
[0] != '.')
201 display_set_hidden(filer_window
, FALSE
);
202 filer_window
->temp_show_hidden
= FALSE
;
206 /* Insert this leafname at the cursor (replacing the selection, if any).
207 * Must be in SHELL mode.
209 void minibuffer_add(FilerWindow
*filer_window
, const gchar
*leafname
)
212 GtkEditable
*edit
= GTK_EDITABLE(filer_window
->minibuffer
);
213 GtkEntry
*entry
= GTK_ENTRY(edit
);
216 g_return_if_fail(filer_window
->mini_type
== MINI_SHELL
);
218 esc
= shell_escape(leafname
);
220 gtk_editable_delete_selection(edit
);
221 pos
= gtk_editable_get_position(edit
);
223 if (pos
> 0 && gtk_entry_get_text(entry
)[pos
- 1] != ' ')
224 gtk_editable_insert_text(edit
, " ", 1, &pos
);
226 gtk_editable_insert_text(edit
, esc
, strlen(esc
), &pos
);
227 gtk_editable_set_position(edit
, pos
);
233 /****************************************************************
234 * INTERNAL FUNCTIONS *
235 ****************************************************************/
237 static void show_help(FilerWindow
*filer_window
)
239 const gchar
*message
;
241 gtk_widget_grab_focus(filer_window
->minibuffer
);
243 switch (filer_window
->mini_type
)
246 message
= _("Enter the name of a file and I'll display "
247 "it for you. Press Tab to fill in the longest "
248 "match. Escape to close the minibuffer.");
251 message
= _("Enter a shell command to execute. Click "
252 "on a file to add it to the buffer.");
255 show_condition_help(NULL
);
262 delayed_error("%s", message
);
268 static void path_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
270 Collection
*collection
= filer_window
->collection
;
271 int item
= collection
->cursor_item
;
272 const gchar
*path
, *pattern
;
273 int flags
= OPEN_FROM_MINI
| OPEN_SAME_WINDOW
;
275 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
276 pattern
= strrchr(path
, '/');
282 if (item
== -1 || item
>= collection
->number_of_items
||
283 !matches(collection
, item
, pattern
))
289 if ((event
->state
& GDK_SHIFT_MASK
) != 0)
292 filer_openitem(filer_window
, item
, flags
);
295 /* Use the cursor item to fill in the minibuffer.
296 * If there are multiple matches then fill in as much as possible and
299 static void complete(FilerWindow
*filer_window
)
302 Collection
*collection
= filer_window
->collection
;
303 int cursor
= collection
->cursor_item
;
305 int shortest_stem
= -1;
308 const gchar
*text
, *leaf
;
310 if (cursor
< 0 || cursor
>= collection
->number_of_items
)
316 entry
= GTK_ENTRY(filer_window
->minibuffer
);
318 item
= (DirItem
*) collection
->items
[cursor
].data
;
320 text
= gtk_entry_get_text(entry
);
321 leaf
= strrchr(text
, '/');
329 if (!matches(collection
, cursor
, leaf
))
335 current_stem
= strlen(leaf
);
337 /* Find the longest other match of this name. It it's longer than
338 * the currently entered text then complete only up to that length.
340 for (other
= 0; other
< collection
->number_of_items
; other
++)
342 DirItem
*other_item
= (DirItem
*) collection
->items
[other
].data
;
348 while (other_item
->leafname
[stem
] && item
->leafname
[stem
])
350 if (other_item
->leafname
[stem
] != item
->leafname
[stem
])
355 /* stem is the index of the first difference */
356 if (stem
>= current_stem
&&
357 (shortest_stem
== -1 || stem
< shortest_stem
))
358 shortest_stem
= stem
;
361 if (current_stem
== shortest_stem
)
363 /* We didn't add anything... */
364 if (o_filer_beep_fail
.int_value
)
367 else if (current_stem
< shortest_stem
)
372 extra
= g_strndup(item
->leafname
+ current_stem
,
373 shortest_stem
- current_stem
);
375 tmp_pos
= entry
->text_length
;
376 gtk_editable_insert_text(GTK_EDITABLE(entry
), extra
, -1,
380 gtk_editable_set_position(GTK_EDITABLE(entry
), -1);
382 if (o_filer_beep_multi
.int_value
)
389 new = make_path(filer_window
->path
, item
->leafname
);
391 if (item
->base_type
== TYPE_DIRECTORY
&&
392 (item
->flags
& ITEM_FLAG_APPDIR
) == 0)
393 g_string_append_c(new, '/');
395 gtk_entry_set_text(entry
, new->str
);
396 gtk_editable_set_position(GTK_EDITABLE(entry
), -1);
400 /* This is an idle function because Gtk+ 2.0 changes text in a entry
401 * with two signals; one to blank it and one to put the new text in.
403 static gboolean
path_changed(gpointer data
)
405 FilerWindow
*filer_window
= (FilerWindow
*) data
;
406 GtkWidget
*mini
= filer_window
->minibuffer
;
407 const char *new, *leaf
;
410 new = gtk_entry_get_text(GTK_ENTRY(mini
));
412 leaf
= g_basename(new);
414 path
= g_strdup("/");
416 path
= g_dirname(new);
417 real
= pathdup(path
);
420 if (strcmp(real
, filer_window
->path
) != 0)
422 /* The new path is in a different directory */
425 if (mc_stat(real
, &info
) == 0 && S_ISDIR(info
.st_mode
))
426 filer_change_to(filer_window
, real
, leaf
);
434 if (!filer_window
->show_hidden
)
436 filer_window
->temp_show_hidden
= TRUE
;
437 display_set_hidden(filer_window
, TRUE
);
440 else if (filer_window
->temp_show_hidden
)
442 display_set_hidden(filer_window
, FALSE
);
443 filer_window
->temp_show_hidden
= FALSE
;
446 if (find_exact_match(filer_window
, leaf
) == FALSE
&&
447 find_next_match(filer_window
, leaf
, 0) == FALSE
)
456 /* Look for an exact match, and move the cursor to it if found.
459 static gboolean
find_exact_match(FilerWindow
*filer_window
,
460 const gchar
*pattern
)
462 Collection
*collection
= filer_window
->collection
;
465 for (i
= 0; i
< collection
->number_of_items
; i
++)
467 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
469 if (strcmp(item
->leafname
, pattern
) == 0)
471 collection_set_cursor_item(collection
, i
);
479 /* Find the next item in the collection that matches 'pattern'. Start from
480 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
481 * -1 for backwards. 0 means forwards, but may stay the same.
483 * Does not automatically update mini_cursor_base.
485 * Returns TRUE if a match is found.
487 static gboolean
find_next_match(FilerWindow
*filer_window
,
491 Collection
*collection
= filer_window
->collection
;
492 int base
= filer_window
->mini_cursor_base
;
494 gboolean retval
= TRUE
;
496 if (collection
->number_of_items
< 1)
499 if (base
< 0 || base
>= collection
->number_of_items
)
500 filer_window
->mini_cursor_base
= base
= 0;
504 /* Step to the next item */
507 if (item
>= collection
->number_of_items
)
510 item
= collection
->number_of_items
- 1;
514 else if (item
== base
)
517 break; /* No (other) matches at all */
519 } while (!matches(collection
, item
, pattern
));
521 collection_set_cursor_item(collection
, item
);
526 static gboolean
matches(Collection
*collection
,
527 int item_number
, const char *pattern
)
529 DirItem
*item
= (DirItem
*) collection
->items
[item_number
].data
;
531 return strncmp(item
->leafname
, pattern
, strlen(pattern
)) == 0;
534 /* Find next match and set base for future matches. */
535 static void search_in_dir(FilerWindow
*filer_window
, int dir
)
537 const char *path
, *pattern
;
539 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
540 pattern
= strrchr(path
, '/');
546 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
547 find_next_match(filer_window
, pattern
, dir
);
548 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
553 static void add_to_history(const gchar
*line
)
557 last
= shell_history
? (guchar
*) shell_history
->data
: NULL
;
559 if (last
&& strcmp(last
, line
) == 0)
560 return; /* Duplicating last entry */
562 shell_history
= g_list_prepend(shell_history
, g_strdup(line
));
565 static void shell_done(FilerWindow
*filer_window
)
567 if (filer_exists(filer_window
))
568 filer_update_dir(filer_window
, TRUE
);
571 /* Given a list of matches, return the longest stem. g_free() the result.
572 * Special chars are escaped. If there is only a single (non-dir) match
573 * then a trailing space is added.
575 static guchar
*best_match(FilerWindow
*filer_window
, glob_t
*matches
)
577 gchar
*first
= matches
->gl_pathv
[0];
579 int longest
, path_len
;
582 longest
= strlen(first
);
584 for (i
= 1; i
< matches
->gl_pathc
; i
++)
587 guchar
*m
= matches
->gl_pathv
[i
];
589 for (j
= 0; j
< longest
; j
++)
590 if (m
[j
] != first
[j
])
594 path_len
= strlen(filer_window
->path
);
595 if (strncmp(filer_window
->path
, first
, path_len
) == 0 &&
596 first
[path_len
] == '/' && first
[path_len
+ 1])
598 path
= g_strndup(first
+ path_len
+ 1, longest
- path_len
- 1);
601 path
= g_strndup(first
, longest
);
603 tmp
= shell_escape(path
);
606 if (matches
->gl_pathc
== 1 && tmp
[strlen(tmp
) - 1] != '/')
608 path
= g_strdup_printf("%s ", tmp
);
616 static void shell_tab(FilerWindow
*filer_window
)
626 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
627 pos
= gtk_editable_get_position(GTK_EDITABLE(filer_window
->minibuffer
));
628 leaf
= g_string_new(NULL
);
631 for (i
= 0; i
< pos
; i
++)
640 g_string_truncate(leaf
, 0);
643 else if (c
== '\\' && i
+ 1 < pos
)
645 else if (c
== '"' || c
== '\'')
649 for (++i
; i
< pos
; i
++)
653 if (cc
== '\\' && i
+ 1 < pos
)
655 else if (entry
[i
] == c
)
657 g_string_append_c(leaf
, entry
[i
]);
662 g_string_append_c(leaf
, c
);
668 if (leaf
->str
[0] != '/' && leaf
->str
[0] != '~')
670 g_string_prepend_c(leaf
, '/');
671 g_string_prepend(leaf
, filer_window
->path
);
674 g_string_append_c(leaf
, '*');
680 GLOB_MARK
, NULL
, &matches
) == 0)
682 if (matches
.gl_pathc
> 0)
686 GTK_EDITABLE(filer_window
->minibuffer
);
688 best
= best_match(filer_window
, &matches
);
690 gtk_editable_delete_text(edit
, leaf_start
, pos
);
691 gtk_editable_insert_text(edit
, best
, strlen(best
),
693 gtk_editable_set_position(edit
, leaf_start
);
697 if (matches
.gl_pathc
!= 1)
703 g_string_free(leaf
, TRUE
);
706 /* Either execute the command or make it the default run action */
707 static void shell_return_pressed(FilerWindow
*filer_window
)
712 Collection
*collection
= filer_window
->collection
;
715 entry
= mini_contents(filer_window
);
720 add_to_history(entry
);
722 argv
= g_ptr_array_new();
723 g_ptr_array_add(argv
, "sh");
724 g_ptr_array_add(argv
, "-c");
725 g_ptr_array_add(argv
, (gchar
*) entry
);
726 g_ptr_array_add(argv
, "sh");
728 for (i
= 0; i
< collection
->number_of_items
; i
++)
730 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
731 if (collection
->items
[i
].selected
)
732 g_ptr_array_add(argv
, item
->leafname
);
735 g_ptr_array_add(argv
, NULL
);
744 delayed_error(_("Failed to create child process"));
747 /* Ensure output is noticed - send stdout to stderr */
748 dup2(STDERR_FILENO
, STDOUT_FILENO
);
749 close_on_exec(STDOUT_FILENO
, FALSE
);
750 if (chdir(filer_window
->path
))
751 g_printerr("chdir(%s) failed: %s\n",
754 execvp((char *) argv
->pdata
[0],
755 (char **) argv
->pdata
);
756 g_printerr("execvp(%s, ...) failed: %s\n",
757 (char *) argv
->pdata
[0],
761 on_child_death(child
,
762 (CallbackFn
) shell_done
, filer_window
);
766 g_ptr_array_free(argv
, TRUE
);
769 minibuffer_hide(filer_window
);
772 /* Move through the shell history */
773 static void shell_recall(FilerWindow
*filer_window
, int dir
)
776 int pos
= filer_window
->mini_cursor_base
;
781 command
= g_list_nth_data(shell_history
, pos
);
790 filer_window
->mini_cursor_base
= pos
;
792 gtk_entry_set_text(GTK_ENTRY(filer_window
->minibuffer
), command
);
797 static void select_return_pressed(FilerWindow
*filer_window
, guint etime
)
802 Collection
*collection
= filer_window
->collection
;
805 entry
= mini_contents(filer_window
);
810 add_to_history(entry
);
812 cond
= find_compile(entry
);
815 delayed_error(_("Invalid Find condition"));
819 info
.now
= time(NULL
);
820 info
.prune
= FALSE
; /* (don't care) */
821 n
= collection
->number_of_items
;
823 collection
->block_selection_changed
++;
825 /* If an item at the start is selected then we could lose the
826 * primary selection after checking that item and then need to
827 * gain it again at the end. Therefore, if anything is selected
828 * then select the last item until the end of the search.
830 if (collection
->number_selected
)
831 collection_select_item(collection
, n
- 1);
833 for (i
= 0; i
< n
; i
++)
835 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
837 info
.leaf
= item
->leafname
;
838 info
.fullpath
= make_path(filer_window
->path
, info
.leaf
)->str
;
840 if (lstat(info
.fullpath
, &info
.stats
) == 0 &&
841 find_test_condition(cond
, &info
))
842 collection_select_item(collection
, i
);
844 collection_unselect_item(collection
, i
);
847 find_condition_free(cond
);
849 collection_unblock_selection_changed(collection
, etime
, TRUE
);
851 minibuffer_hide(filer_window
);
857 static gint
key_press_event(GtkWidget
*widget
,
859 FilerWindow
*filer_window
)
861 if (event
->keyval
== GDK_Escape
)
863 if (filer_window
->mini_type
== MINI_SHELL
)
867 line
= mini_contents(filer_window
);
869 add_to_history(line
);
872 minibuffer_hide(filer_window
);
876 switch (filer_window
->mini_type
)
879 switch (event
->keyval
)
882 search_in_dir(filer_window
, -1);
885 search_in_dir(filer_window
, 1);
889 path_return_pressed(filer_window
,
893 complete(filer_window
);
901 switch (event
->keyval
)
904 shell_recall(filer_window
, 1);
907 shell_recall(filer_window
, -1);
910 shell_tab(filer_window
);
914 shell_return_pressed(filer_window
);
921 switch (event
->keyval
)
924 shell_recall(filer_window
, 1);
927 shell_recall(filer_window
, -1);
933 select_return_pressed(filer_window
,
947 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
949 switch (filer_window
->mini_type
)
952 gtk_idle_add(path_changed
, filer_window
);
955 set_find_string_colour(GTK_WIDGET(mini
),
957 GTK_ENTRY(filer_window
->minibuffer
)));
964 /* Returns a string (which must NOT be freed), or NULL if the buffer
965 * is blank (whitespace only).
967 static const gchar
*mini_contents(FilerWindow
*filer_window
)
969 const gchar
*entry
, *c
;
971 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
973 for (c
= entry
; *c
; c
++)
980 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
983 static gboolean
grab_focus(GtkWidget
*minibuffer
)
985 GtkWidgetClass
*class;
987 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET
));
989 class->grab_focus(minibuffer
);
991 g_signal_stop_emission(minibuffer
,
992 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer
)), 0);