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>
39 #include "gui_support.h"
41 #include "minibuffer.h"
48 #include "view_iface.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(ViewIter
*iter
, 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
)
118 g_return_if_fail(filer_window
!= NULL
);
119 g_return_if_fail(filer_window
->minibuffer
!= NULL
);
121 mini
= GTK_ENTRY(filer_window
->minibuffer
);
122 entry_set_error(filer_window
->minibuffer
, FALSE
);
124 filer_window
->mini_type
= MINI_NONE
;
125 gtk_label_set_text(GTK_LABEL(filer_window
->minibuffer_label
),
126 mini_type
== MINI_PATH
? _("Goto:") :
127 mini_type
== MINI_SHELL
? _("Shell:") :
128 mini_type
== MINI_SELECT_IF
? _("Select If:") :
134 view_show_cursor(filer_window
->view
);
135 view_get_cursor(filer_window
->view
, &cursor
);
136 view_set_base(filer_window
->view
, &cursor
);
138 gtk_entry_set_text(mini
,
139 make_path(filer_window
->sym_path
, "")->str
);
140 if (filer_window
->temp_show_hidden
)
142 display_set_hidden(filer_window
, FALSE
);
143 filer_window
->temp_show_hidden
= FALSE
;
147 gtk_entry_set_text(mini
, "");
148 filer_window
->mini_cursor_base
= -1; /* History */
153 view_get_cursor(filer_window
->view
, &cursor
);
154 item
= cursor
.peek(&cursor
);
156 if (view_count_selected(filer_window
->view
) > 0)
157 gtk_entry_set_text(mini
, " \"$@\"");
162 tmp
= g_strconcat(" ", item
->leafname
, NULL
);
163 gtk_entry_set_text(mini
, tmp
);
167 gtk_entry_set_text(mini
, "");
168 filer_window
->mini_cursor_base
= -1; /* History */
172 g_warning("Bad minibuffer type\n");
176 filer_window
->mini_type
= mini_type
;
178 gtk_editable_set_position(GTK_EDITABLE(mini
), pos
);
180 gtk_widget_show_all(filer_window
->minibuffer_area
);
182 gtk_widget_grab_focus(filer_window
->minibuffer
);
185 void minibuffer_hide(FilerWindow
*filer_window
)
187 filer_window
->mini_type
= MINI_NONE
;
189 gtk_widget_hide(filer_window
->minibuffer_area
);
191 gtk_widget_child_focus(filer_window
->window
, GTK_DIR_TAB_FORWARD
);
193 if (filer_window
->temp_show_hidden
)
198 view_get_cursor(filer_window
->view
, &iter
);
199 item
= iter
.peek(&iter
);
201 if (item
== NULL
|| item
->leafname
[0] != '.')
202 display_set_hidden(filer_window
, FALSE
);
203 filer_window
->temp_show_hidden
= FALSE
;
207 /* Insert this leafname at the cursor (replacing the selection, if any).
208 * Must be in SHELL mode.
210 void minibuffer_add(FilerWindow
*filer_window
, const gchar
*leafname
)
213 GtkEditable
*edit
= GTK_EDITABLE(filer_window
->minibuffer
);
214 GtkEntry
*entry
= GTK_ENTRY(edit
);
217 g_return_if_fail(filer_window
->mini_type
== MINI_SHELL
);
219 esc
= shell_escape(leafname
);
221 gtk_editable_delete_selection(edit
);
222 pos
= gtk_editable_get_position(edit
);
224 if (pos
> 0 && gtk_entry_get_text(entry
)[pos
- 1] != ' ')
225 gtk_editable_insert_text(edit
, " ", 1, &pos
);
227 gtk_editable_insert_text(edit
, esc
, strlen(esc
), &pos
);
228 gtk_editable_set_position(edit
, pos
);
234 /****************************************************************
235 * INTERNAL FUNCTIONS *
236 ****************************************************************/
238 static void show_help(FilerWindow
*filer_window
)
240 switch (filer_window
->mini_type
)
244 _("Enter the name of a file and I'll display "
245 "it for you. Press Tab to fill in the longest "
246 "match. Escape to close the minibuffer."));
250 _("Enter a shell command to execute. Click "
251 "on a file to add it to the buffer."));
254 show_condition_help(NULL
);
257 g_warning("Unknown minibuffer type!");
265 static void path_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
267 const gchar
*path
, *pattern
;
268 int flags
= OPEN_FROM_MINI
| OPEN_SAME_WINDOW
;
272 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
273 pattern
= g_basename(path
);
275 view_get_cursor(filer_window
->view
, &iter
);
277 item
= iter
.peek(&iter
);
278 if (item
== NULL
|| !matches(&iter
, pattern
))
284 if ((event
->state
& GDK_SHIFT_MASK
) != 0)
287 filer_openitem(filer_window
, &iter
, flags
);
290 /* Use the cursor item to fill in the minibuffer.
291 * If there are multiple matches then fill in as much as possible and
294 static void complete(FilerWindow
*filer_window
)
297 DirItem
*item
, *other
;
298 int shortest_stem
= -1;
300 const gchar
*text
, *leaf
;
301 ViewIter cursor
, iter
;
303 view_get_cursor(filer_window
->view
, &cursor
);
304 item
= cursor
.peek(&cursor
);
312 entry
= GTK_ENTRY(filer_window
->minibuffer
);
314 text
= gtk_entry_get_text(entry
);
315 leaf
= strrchr(text
, '/');
323 if (!matches(&cursor
, leaf
))
329 current_stem
= strlen(leaf
);
331 /* Find the longest other match of this name. It it's longer than
332 * the currently entered text then complete only up to that length.
334 view_get_iter(filer_window
->view
, &iter
, 0);
335 while ((other
= iter
.next(&iter
)))
339 if (iter
.i
== cursor
.i
) /* XXX */
342 while (other
->leafname
[stem
] && item
->leafname
[stem
])
344 if (other
->leafname
[stem
] != item
->leafname
[stem
])
349 /* stem is the index of the first difference */
350 if (stem
>= current_stem
&&
351 (shortest_stem
== -1 || stem
< shortest_stem
))
352 shortest_stem
= stem
;
355 if (current_stem
== shortest_stem
)
357 /* We didn't add anything... */
358 if (o_filer_beep_fail
.int_value
)
361 else if (current_stem
< shortest_stem
)
366 extra
= g_strndup(item
->leafname
+ current_stem
,
367 shortest_stem
- current_stem
);
369 tmp_pos
= entry
->text_length
;
370 gtk_editable_insert_text(GTK_EDITABLE(entry
), extra
, -1,
374 gtk_editable_set_position(GTK_EDITABLE(entry
), -1);
376 if (o_filer_beep_multi
.int_value
)
383 new = make_path(filer_window
->sym_path
, item
->leafname
);
385 if (item
->base_type
== TYPE_DIRECTORY
&&
386 (item
->flags
& ITEM_FLAG_APPDIR
) == 0)
387 g_string_append_c(new, '/');
389 gtk_entry_set_text(entry
, new->str
);
390 gtk_editable_set_position(GTK_EDITABLE(entry
), -1);
394 static void path_changed(FilerWindow
*filer_window
)
396 GtkWidget
*mini
= filer_window
->minibuffer
;
397 const char *new, *leaf
;
399 gboolean error
= FALSE
;
401 new = gtk_entry_get_text(GTK_ENTRY(mini
));
405 /* Entry may be blank because we're in the middle of changing
406 * to something else...
408 entry_set_error(mini
, FALSE
);
412 leaf
= g_basename(new);
414 path
= g_strdup("/");
416 path
= g_dirname(new);
418 if (strcmp(path
, filer_window
->sym_path
) != 0)
420 /* The new path is in a different directory */
423 if (mc_stat(path
, &info
) == 0 && S_ISDIR(info
.st_mode
))
424 filer_change_to(filer_window
, path
, leaf
);
432 if (!filer_window
->show_hidden
)
434 filer_window
->temp_show_hidden
= TRUE
;
435 display_set_hidden(filer_window
, TRUE
);
438 else if (filer_window
->temp_show_hidden
)
440 display_set_hidden(filer_window
, FALSE
);
441 filer_window
->temp_show_hidden
= FALSE
;
444 if (find_exact_match(filer_window
, leaf
) == FALSE
&&
445 find_next_match(filer_window
, leaf
, 0) == FALSE
)
451 entry_set_error(mini
, error
);
454 /* Look for an exact match, and move the cursor to it if found.
457 static gboolean
find_exact_match(FilerWindow
*filer_window
,
458 const gchar
*pattern
)
462 ViewIface
*view
= filer_window
->view
;
464 view_get_iter(view
, &iter
, 0);
466 while ((item
= iter
.next(&iter
)))
468 if (strcmp(item
->leafname
, pattern
) == 0)
470 view_cursor_to_iter(view
, &iter
);
478 /* Find the next item in the view that matches 'pattern'. Start from
479 * cursor_base and loop at either end. dir is 1 for a forward search,
480 * -1 for backwards. 0 means forwards, but may stay the same.
482 * Does not automatically update cursor_base.
484 * Returns TRUE if a match is found.
486 static gboolean
find_next_match(FilerWindow
*filer_window
,
490 ViewIface
*view
= filer_window
->view
;
493 if (view_count_items(view
) < 1)
496 view_get_iter(view
, &iter
,
497 VIEW_ITER_FROM_BASE
|
498 (dir
>= 0 ? 0 : VIEW_ITER_BACKWARDS
));
501 iter
.next(&iter
); /* Don't look at the base itself */
503 while (iter
.next(&iter
))
505 if (matches(&iter
, pattern
))
507 view_cursor_to_iter(view
, &iter
);
512 /* No matches (except possibly base itself) */
513 view_get_iter(view
, &iter
,
514 VIEW_ITER_FROM_BASE
| VIEW_ITER_ONE_ONLY
|
515 (dir
>= 0 ? 0 : VIEW_ITER_BACKWARDS
));
517 view_cursor_to_iter(view
, &iter
);
522 static gboolean
matches(ViewIter
*iter
, const char *pattern
)
526 item
= iter
->peek(iter
);
528 return strncmp(item
->leafname
, pattern
, strlen(pattern
)) == 0;
531 /* Find next match and set base for future matches. */
532 static void search_in_dir(FilerWindow
*filer_window
, int dir
)
534 const char *path
, *pattern
;
537 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
538 pattern
= g_basename(path
);
540 view_get_cursor(filer_window
->view
, &iter
);
541 view_set_base(filer_window
->view
, &iter
);
542 find_next_match(filer_window
, pattern
, dir
);
543 view_get_cursor(filer_window
->view
, &iter
);
544 view_set_base(filer_window
->view
, &iter
);
549 static void add_to_history(const gchar
*line
)
553 last
= shell_history
? (guchar
*) shell_history
->data
: NULL
;
555 if (last
&& strcmp(last
, line
) == 0)
556 return; /* Duplicating last entry */
558 shell_history
= g_list_prepend(shell_history
, g_strdup(line
));
561 static void shell_done(FilerWindow
*filer_window
)
563 if (filer_exists(filer_window
))
564 filer_update_dir(filer_window
, TRUE
);
567 /* Given a list of matches, return the longest stem. g_free() the result.
568 * Special chars are escaped. If there is only a single (non-dir) match
569 * then a trailing space is added.
571 static guchar
*best_match(FilerWindow
*filer_window
, glob_t
*matches
)
573 gchar
*first
= matches
->gl_pathv
[0];
575 int longest
, path_len
;
578 longest
= strlen(first
);
580 for (i
= 1; i
< matches
->gl_pathc
; i
++)
583 guchar
*m
= matches
->gl_pathv
[i
];
585 for (j
= 0; j
< longest
; j
++)
586 if (m
[j
] != first
[j
])
590 path_len
= strlen(filer_window
->sym_path
);
591 if (strncmp(filer_window
->sym_path
, first
, path_len
) == 0 &&
592 first
[path_len
] == '/' && first
[path_len
+ 1])
594 path
= g_strndup(first
+ path_len
+ 1, longest
- path_len
- 1);
597 path
= g_strndup(first
, longest
);
599 tmp
= shell_escape(path
);
602 if (matches
->gl_pathc
== 1 && tmp
[strlen(tmp
) - 1] != '/')
604 path
= g_strdup_printf("%s ", tmp
);
612 static void shell_tab(FilerWindow
*filer_window
)
621 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
622 pos
= gtk_editable_get_position(GTK_EDITABLE(filer_window
->minibuffer
));
623 leaf
= g_string_new(NULL
);
625 for (i
= 0; i
< pos
; i
++)
634 g_string_truncate(leaf
, 0);
637 else if (c
== '\\' && i
+ 1 < pos
)
639 else if (c
== '"' || c
== '\'')
641 for (++i
; i
< pos
; i
++)
643 guchar cc
= entry
[i
];
645 if (cc
== '\\' && i
+ 1 < pos
)
649 g_string_append_c(leaf
, cc
);
654 g_string_append_c(leaf
, c
);
660 if (leaf
->str
[0] != '/' && leaf
->str
[0] != '~')
662 g_string_prepend_c(leaf
, '/');
663 g_string_prepend(leaf
, filer_window
->sym_path
);
666 g_string_append_c(leaf
, '*');
672 GLOB_MARK
, NULL
, &matches
) == 0)
674 if (matches
.gl_pathc
> 0)
678 GTK_EDITABLE(filer_window
->minibuffer
);
680 best
= best_match(filer_window
, &matches
);
682 gtk_editable_delete_text(edit
, leaf_start
, pos
);
683 gtk_editable_insert_text(edit
, best
, strlen(best
),
685 gtk_editable_set_position(edit
, leaf_start
);
689 if (matches
.gl_pathc
!= 1)
695 g_string_free(leaf
, TRUE
);
698 static void run_child(gpointer unused
)
700 /* Ensure output is noticed - send stdout to stderr */
701 dup2(STDERR_FILENO
, STDOUT_FILENO
);
702 close_on_exec(STDOUT_FILENO
, FALSE
);
705 /* Either execute the command or make it the default run action */
706 static void shell_return_pressed(FilerWindow
*filer_window
)
710 GError
*error
= NULL
;
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 view_get_iter(filer_window
->view
, &iter
, 0);
729 while ((item
= iter
.next(&iter
)))
731 if (view_get_selected(filer_window
->view
, &iter
))
732 g_ptr_array_add(argv
, item
->leafname
);
735 g_ptr_array_add(argv
, NULL
);
737 if (!g_spawn_async_with_pipes(filer_window
->sym_path
,
738 (gchar
**) argv
->pdata
, NULL
,
739 G_SPAWN_DO_NOT_REAP_CHILD
|
741 run_child
, NULL
, /* Child setup fn */
742 &child
, /* Child PID */
743 NULL
, NULL
, NULL
, /* Standard pipes */
746 delayed_error("%s", error
? error
->message
: "(null)");
750 on_child_death(child
, (CallbackFn
) shell_done
, filer_window
);
752 g_ptr_array_free(argv
, TRUE
);
755 minibuffer_hide(filer_window
);
758 /* Move through the shell history */
759 static void shell_recall(FilerWindow
*filer_window
, int dir
)
762 int pos
= filer_window
->mini_cursor_base
;
767 command
= g_list_nth_data(shell_history
, pos
);
776 filer_window
->mini_cursor_base
= pos
;
778 gtk_entry_set_text(GTK_ENTRY(filer_window
->minibuffer
), command
);
785 FilerWindow
*filer_window
;
789 static gboolean
select_if_test(ViewIter
*iter
, gpointer user_data
)
792 SelectData
*data
= user_data
;
794 item
= iter
->peek(iter
);
795 g_return_val_if_fail(item
!= NULL
, FALSE
);
797 data
->info
.leaf
= item
->leafname
;
798 data
->info
.fullpath
= make_path(data
->filer_window
->sym_path
,
799 data
->info
.leaf
)->str
;
801 return lstat(data
->info
.fullpath
, &data
->info
.stats
) == 0 &&
802 find_test_condition(data
->cond
, &data
->info
);
805 static void select_return_pressed(FilerWindow
*filer_window
, guint etime
)
810 entry
= mini_contents(filer_window
);
815 add_to_history(entry
);
817 data
.cond
= find_compile(entry
);
820 delayed_error(_("Invalid Find condition"));
824 data
.info
.now
= time(NULL
);
825 data
.info
.prune
= FALSE
; /* (don't care) */
826 data
.filer_window
= filer_window
;
828 view_select_if(filer_window
->view
, select_if_test
, &data
);
830 find_condition_free(data
.cond
);
832 minibuffer_hide(filer_window
);
838 static gint
key_press_event(GtkWidget
*widget
,
840 FilerWindow
*filer_window
)
842 if (event
->keyval
== GDK_Escape
)
844 if (filer_window
->mini_type
== MINI_SHELL
)
848 line
= mini_contents(filer_window
);
850 add_to_history(line
);
853 minibuffer_hide(filer_window
);
857 switch (filer_window
->mini_type
)
860 switch (event
->keyval
)
863 search_in_dir(filer_window
, -1);
866 search_in_dir(filer_window
, 1);
870 path_return_pressed(filer_window
,
874 complete(filer_window
);
882 switch (event
->keyval
)
885 shell_recall(filer_window
, 1);
888 shell_recall(filer_window
, -1);
891 shell_tab(filer_window
);
895 shell_return_pressed(filer_window
);
902 switch (event
->keyval
)
905 shell_recall(filer_window
, 1);
908 shell_recall(filer_window
, -1);
914 select_return_pressed(filer_window
,
928 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
930 switch (filer_window
->mini_type
)
933 path_changed(filer_window
);
936 set_find_string_colour(GTK_WIDGET(mini
),
938 GTK_ENTRY(filer_window
->minibuffer
)));
945 /* Returns a string (which must NOT be freed), or NULL if the buffer
946 * is blank (whitespace only).
948 static const gchar
*mini_contents(FilerWindow
*filer_window
)
950 const gchar
*entry
, *c
;
952 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
954 for (c
= entry
; *c
; c
++)
961 /* This is a really ugly hack to get around Gtk+-2.0's broken auto-select
964 static gboolean
grab_focus(GtkWidget
*minibuffer
)
966 GtkWidgetClass
*class;
968 class = GTK_WIDGET_CLASS(gtk_type_class(GTK_TYPE_WIDGET
));
970 class->grab_focus(minibuffer
);
972 g_signal_stop_emission(minibuffer
,
973 g_signal_lookup("grab_focus", G_OBJECT_TYPE(minibuffer
)), 0);