4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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>
37 #include "collection.h"
39 #include "gui_support.h"
41 #include "minibuffer.h"
48 static GList
*shell_history
= NULL
;
50 /* Static prototypes */
51 static gint
key_press_event(GtkWidget
*widget
,
53 FilerWindow
*filer_window
);
54 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
);
55 static gboolean
find_next_match(FilerWindow
*filer_window
,
58 static gboolean
find_exact_match(FilerWindow
*filer_window
, char *pattern
);
59 static gboolean
matches(Collection
*collection
, int item
, char *pattern
);
60 static void search_in_dir(FilerWindow
*filer_window
, int dir
);
61 static guchar
*mini_contents(FilerWindow
*filer_window
);
62 static void show_help(FilerWindow
*filer_window
);
65 /****************************************************************
66 * EXTERNAL INTERFACE *
67 ****************************************************************/
70 /* Creates the minibuffer widgets, setting the appropriate fields
73 void create_minibuffer(FilerWindow
*filer_window
)
75 GtkWidget
*hbox
, *label
, *mini
;
77 hbox
= gtk_hbox_new(FALSE
, 0);
79 gtk_box_pack_start(GTK_BOX(hbox
),
80 new_help_button((HelpFunc
) show_help
, filer_window
),
83 label
= gtk_label_new("");
84 gtk_box_pack_start(GTK_BOX(hbox
), label
, FALSE
, TRUE
, 2);
86 mini
= gtk_entry_new();
87 gtk_box_pack_start(GTK_BOX(hbox
), mini
, TRUE
, TRUE
, 0);
88 gtk_widget_set_style(mini
, fixed_style
);
89 gtk_signal_connect(GTK_OBJECT(mini
), "key_press_event",
90 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
91 gtk_signal_connect(GTK_OBJECT(mini
), "changed",
92 GTK_SIGNAL_FUNC(changed
), filer_window
);
94 filer_window
->minibuffer
= mini
;
95 filer_window
->minibuffer_label
= label
;
96 filer_window
->minibuffer_area
= hbox
;
99 void minibuffer_show(FilerWindow
*filer_window
, MiniType mini_type
)
101 Collection
*collection
;
104 g_return_if_fail(filer_window
!= NULL
);
105 g_return_if_fail(filer_window
->minibuffer
!= NULL
);
107 mini
= GTK_ENTRY(filer_window
->minibuffer
);
109 filer_window
->mini_type
= MINI_NONE
;
110 gtk_label_set_text(GTK_LABEL(filer_window
->minibuffer_label
),
111 mini_type
== MINI_PATH
? _("Goto:") :
112 mini_type
== MINI_SHELL
? _("Shell:") :
113 mini_type
== MINI_RUN_ACTION
? _("Run Action:") :
114 mini_type
== MINI_SELECT_IF
? _("Select If:") :
117 collection
= filer_window
->collection
;
118 collection_move_cursor(collection
, 0, 0); /* Turn the cursor on */
122 filer_window
->mini_cursor_base
=
123 MAX(collection
->cursor_item
, 0);
124 gtk_entry_set_text(mini
,
125 make_path(filer_window
->path
, "")->str
);
126 if (filer_window
->temp_show_hidden
)
128 display_set_hidden(filer_window
, FALSE
);
129 filer_window
->temp_show_hidden
= FALSE
;
133 case MINI_RUN_ACTION
:
135 filer_window
->mini_cursor_base
= -1; /* History */
136 gtk_entry_set_text(mini
, "");
139 g_warning("Bad minibuffer type\n");
143 filer_window
->mini_type
= mini_type
;
145 gtk_entry_set_position(mini
, -1);
147 gtk_widget_show_all(filer_window
->minibuffer_area
);
149 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
150 filer_window
->minibuffer
);
153 void minibuffer_hide(FilerWindow
*filer_window
)
155 filer_window
->mini_type
= MINI_NONE
;
157 gtk_widget_hide(filer_window
->minibuffer_area
);
158 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
159 GTK_WIDGET(filer_window
->collection
));
161 if (filer_window
->temp_show_hidden
)
163 display_set_hidden(filer_window
, FALSE
);
164 filer_window
->temp_show_hidden
= FALSE
;
168 /* Insert this leafname at the cursor (replacing the selection, if any).
169 * Must be in SHELL or RUN_ACTION mode.
171 void minibuffer_add(FilerWindow
*filer_window
, guchar
*leafname
)
174 GtkEditable
*edit
= GTK_EDITABLE(filer_window
->minibuffer
);
175 GtkEntry
*entry
= GTK_ENTRY(edit
);
178 g_return_if_fail(filer_window
->mini_type
== MINI_SHELL
||
179 filer_window
->mini_type
== MINI_RUN_ACTION
);
181 esc
= shell_escape(leafname
);
183 gtk_editable_delete_selection(edit
);
184 pos
= gtk_editable_get_position(edit
);
186 if (pos
> 0 && gtk_entry_get_text(entry
)[pos
- 1] != ' ')
187 gtk_editable_insert_text(edit
, " ", 1, &pos
);
189 gtk_editable_insert_text(edit
, esc
, strlen(esc
), &pos
);
190 gtk_editable_set_position(edit
, pos
);
196 /****************************************************************
197 * INTERNAL FUNCTIONS *
198 ****************************************************************/
200 static void show_help(FilerWindow
*filer_window
)
204 gtk_widget_grab_focus(filer_window
->minibuffer
);
206 switch (filer_window
->mini_type
)
209 message
= _("Enter the name of a file and I'll display "
210 "it for you. Press Tab to fill in the longest "
211 "match. Escape to close the minibuffer.");
214 message
= _("Enter a shell command to execute. Click "
215 "on a file to add it to the buffer.");
217 case MINI_RUN_ACTION
:
219 _("To set the run action for a file, either:\n"
220 "- Drag a file to an application directory (eg drag an image to the "
221 "Gimp), or\n" "- Enter a shell command which contains a \"$1\""
222 "where the name of the file should go (eg ` gimp \"$1\" ')");
225 show_condition_help(NULL
);
232 delayed_error(PROJECT
, message
);
238 static void path_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
240 Collection
*collection
= filer_window
->collection
;
241 int item
= collection
->cursor_item
;
242 char *path
, *pattern
;
243 int flags
= OPEN_FROM_MINI
| OPEN_SAME_WINDOW
;
245 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
246 pattern
= strrchr(path
, '/');
252 if (item
== -1 || item
>= collection
->number_of_items
||
253 !matches(collection
, item
, pattern
))
259 if ((event
->state
& GDK_SHIFT_MASK
) != 0)
262 filer_openitem(filer_window
, item
, flags
);
265 /* Use the cursor item to fill in the minibuffer.
266 * If there are multiple matches the fill in as much as possible and beep.
268 static void complete(FilerWindow
*filer_window
)
271 Collection
*collection
= filer_window
->collection
;
272 int cursor
= collection
->cursor_item
;
274 int shortest_stem
= -1;
279 if (cursor
< 0 || cursor
>= collection
->number_of_items
)
285 entry
= GTK_ENTRY(filer_window
->minibuffer
);
287 item
= (DirItem
*) collection
->items
[cursor
].data
;
289 text
= gtk_entry_get_text(entry
);
290 leaf
= strrchr(text
, '/');
298 if (!matches(collection
, cursor
, leaf
))
304 current_stem
= strlen(leaf
);
306 /* Find the longest other match of this name. It it's longer than
307 * the currently entered text then complete only up to that length.
309 for (other
= 0; other
< collection
->number_of_items
; other
++)
311 DirItem
*other_item
= (DirItem
*) collection
->items
[other
].data
;
317 while (other_item
->leafname
[stem
] && item
->leafname
[stem
])
319 if (other_item
->leafname
[stem
] != item
->leafname
[stem
])
324 /* stem is the index of the first difference */
325 if (stem
>= current_stem
&&
326 (shortest_stem
== -1 || stem
< shortest_stem
))
327 shortest_stem
= stem
;
330 if (current_stem
== shortest_stem
)
332 else if (current_stem
< shortest_stem
)
336 extra
= g_strndup(item
->leafname
+ current_stem
,
337 shortest_stem
- current_stem
);
338 gtk_entry_append_text(entry
, extra
);
346 new = make_path(filer_window
->path
, item
->leafname
);
348 if (item
->base_type
== TYPE_DIRECTORY
&&
349 (item
->flags
& ITEM_FLAG_APPDIR
) == 0)
350 g_string_append_c(new, '/');
352 gtk_entry_set_text(entry
, new->str
);
356 static void path_changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
361 new = gtk_entry_get_text(GTK_ENTRY(mini
));
365 new = g_strdup_printf("/%s", new);
367 slash
= strrchr(new, '/');
375 real
= pathdup(path
);
377 if (strcmp(real
, filer_window
->path
) != 0)
380 if (mc_stat(real
, &info
) == 0 && S_ISDIR(info
.st_mode
))
381 filer_change_to(filer_window
, real
, slash
+ 1);
389 if (!filer_window
->show_hidden
)
391 filer_window
->temp_show_hidden
= TRUE
;
392 display_set_hidden(filer_window
, TRUE
);
395 else if (filer_window
->temp_show_hidden
)
397 display_set_hidden(filer_window
, FALSE
);
398 filer_window
->temp_show_hidden
= FALSE
;
401 if (find_exact_match(filer_window
, slash
+ 1) == FALSE
&&
402 find_next_match(filer_window
, slash
+ 1, 0) == FALSE
)
410 /* Look for an exact match, and move the cursor to it if found.
413 static gboolean
find_exact_match(FilerWindow
*filer_window
, char *pattern
)
415 Collection
*collection
= filer_window
->collection
;
418 for (i
= 0; i
< collection
->number_of_items
; i
++)
420 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
422 if (strcmp(item
->leafname
, pattern
) == 0)
424 collection_set_cursor_item(collection
, i
);
432 /* Find the next item in the collection that matches 'pattern'. Start from
433 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
434 * -1 for backwards. 0 means forwards, but may stay the same.
436 * Does not automatically update mini_cursor_base.
438 * Returns TRUE if a match is found.
440 static gboolean
find_next_match(FilerWindow
*filer_window
,
444 Collection
*collection
= filer_window
->collection
;
445 int base
= filer_window
->mini_cursor_base
;
447 gboolean retval
= TRUE
;
449 if (collection
->number_of_items
< 1)
452 if (base
< 0 || base
>= collection
->number_of_items
)
453 filer_window
->mini_cursor_base
= base
= 0;
457 /* Step to the next item */
460 if (item
>= collection
->number_of_items
)
463 item
= collection
->number_of_items
- 1;
467 else if (item
== base
)
470 break; /* No (other) matches at all */
472 } while (!matches(collection
, item
, pattern
));
474 collection_set_cursor_item(collection
, item
);
479 static gboolean
matches(Collection
*collection
, int item_number
, char *pattern
)
481 DirItem
*item
= (DirItem
*) collection
->items
[item_number
].data
;
483 return strncmp(item
->leafname
, pattern
, strlen(pattern
)) == 0;
486 /* Find next match and set base for future matches. */
487 static void search_in_dir(FilerWindow
*filer_window
, int dir
)
489 char *path
, *pattern
;
491 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
492 pattern
= strrchr(path
, '/');
498 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
499 find_next_match(filer_window
, pattern
, dir
);
500 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
505 static void add_to_history(guchar
*line
)
509 last
= shell_history
? (guchar
*) shell_history
->data
: NULL
;
511 if (last
&& strcmp(last
, line
) == 0)
512 return; /* Duplicating last entry */
514 shell_history
= g_list_prepend(shell_history
, g_strdup(line
));
517 static void shell_done(FilerWindow
*filer_window
)
519 if (filer_exists(filer_window
))
520 filer_update_dir(filer_window
, TRUE
);
523 /* Given a list of matches, return the longest stem. g_free() the result.
524 * Special chars are escaped. If there is only a single (non-dir) match
525 * then a trailing space is added.
527 static guchar
*best_match(FilerWindow
*filer_window
, glob_t
*matches
)
529 gchar
*first
= matches
->gl_pathv
[0];
531 int longest
, path_len
;
534 longest
= strlen(first
);
536 for (i
= 1; i
< matches
->gl_pathc
; i
++)
539 guchar
*m
= matches
->gl_pathv
[i
];
541 for (j
= 0; j
< longest
; j
++)
542 if (m
[j
] != first
[j
])
546 path_len
= strlen(filer_window
->path
);
547 if (strncmp(filer_window
->path
, first
, path_len
) == 0 &&
548 first
[path_len
] == '/' && first
[path_len
+ 1])
550 path
= g_strndup(first
+ path_len
+ 1, longest
- path_len
- 1);
553 path
= g_strndup(first
, longest
);
555 tmp
= shell_escape(path
);
558 if (matches
->gl_pathc
== 1 && tmp
[strlen(tmp
) - 1] != '/')
560 path
= g_strdup_printf("%s ", tmp
);
568 static void shell_tab(FilerWindow
*filer_window
)
578 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
579 pos
= gtk_editable_get_position(GTK_EDITABLE(filer_window
->minibuffer
));
580 leaf
= g_string_new(NULL
);
583 for (i
= 0; i
< pos
; i
++)
592 g_string_truncate(leaf
, 0);
595 else if (c
== '\\' && i
+ 1 < pos
)
597 else if (c
== '"' || c
== '\'')
601 for (++i
; i
< pos
; i
++)
605 if (cc
== '\\' && i
+ 1 < pos
)
607 else if (entry
[i
] == c
)
609 g_string_append_c(leaf
, entry
[i
]);
614 g_string_append_c(leaf
, c
);
620 if (leaf
->str
[0] != '/' && leaf
->str
[0] != '~')
622 g_string_prepend_c(leaf
, '/');
623 g_string_prepend(leaf
, filer_window
->path
);
626 g_string_append_c(leaf
, '*');
632 GLOB_MARK
, NULL
, &matches
) == 0)
634 if (matches
.gl_pathc
> 0)
638 GTK_EDITABLE(filer_window
->minibuffer
);
640 best
= best_match(filer_window
, &matches
);
642 gtk_editable_delete_text(edit
, leaf_start
, pos
);
643 gtk_editable_insert_text(edit
, best
, strlen(best
),
645 gtk_editable_set_position(edit
, leaf_start
);
649 if (matches
.gl_pathc
!= 1)
655 g_string_free(leaf
, TRUE
);
658 /* This is called from shell_return_pressed() so that the command is already
659 * in the history. Returns TRUE if the buffer should be closed.
661 gboolean
set_run_action(FilerWindow
*filer_window
, guchar
*command
)
663 Collection
*collection
= filer_window
->collection
;
664 int n
= collection
->cursor_item
;
670 if (!strchr(command
, '$'))
672 show_help(filer_window
);
676 if (n
< 0 || n
>= collection
->number_of_items
)
678 delayed_error(PROJECT
,
679 _("You must have the cursor on the item to use for '$1'."));
683 item
= (DirItem
*) collection
->items
[n
].data
;
684 path
= type_ask_which_action(item
->mime_type
->media_type
,
685 item
->mime_type
->subtype
);
690 tmp
= g_strdup_printf("#! /bin/sh\nexec %s\n", command
);
693 file
= fopen(path
, "wb");
694 if (fwrite(tmp
, 1, len
, file
) < len
)
696 if (fclose(file
) && error
== 0)
698 if (chmod(path
, 0777))
702 report_error(PROJECT
, g_strerror(errno
));
709 /* Either execute the command or make it the default run action */
710 static void shell_return_pressed(FilerWindow
*filer_window
)
715 Collection
*collection
= filer_window
->collection
;
718 entry
= mini_contents(filer_window
);
723 add_to_history(entry
);
725 if (filer_window
->mini_type
== MINI_RUN_ACTION
)
727 if (set_run_action(filer_window
, entry
))
733 argv
= g_ptr_array_new();
734 g_ptr_array_add(argv
, "sh");
735 g_ptr_array_add(argv
, "-c");
736 g_ptr_array_add(argv
, entry
);
737 g_ptr_array_add(argv
, "sh");
739 for (i
= 0; i
< collection
->number_of_items
; i
++)
741 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
742 if (collection
->items
[i
].selected
)
743 g_ptr_array_add(argv
, item
->leafname
);
746 g_ptr_array_add(argv
, NULL
);
753 delayed_error(PROJECT
, _("Failed to create "
757 dup2(to_error_log
, STDOUT_FILENO
);
758 close_on_exec(STDOUT_FILENO
, FALSE
);
759 dup2(to_error_log
, STDERR_FILENO
);
760 close_on_exec(STDERR_FILENO
, FALSE
);
761 if (chdir(filer_window
->path
))
762 g_printerr("chdir(%s) failed: %s\n",
765 execvp((char *) argv
->pdata
[0],
766 (char **) argv
->pdata
);
767 g_printerr("execvp(%s, ...) failed: %s\n",
768 (char *) argv
->pdata
[0],
772 on_child_death(child
,
773 (CallbackFn
) shell_done
, filer_window
);
777 g_ptr_array_free(argv
, TRUE
);
780 minibuffer_hide(filer_window
);
783 /* Move through the shell history */
784 static void shell_recall(FilerWindow
*filer_window
, int dir
)
787 int pos
= filer_window
->mini_cursor_base
;
792 command
= g_list_nth_data(shell_history
, pos
);
801 filer_window
->mini_cursor_base
= pos
;
803 gtk_entry_set_text(GTK_ENTRY(filer_window
->minibuffer
), command
);
808 static void select_return_pressed(FilerWindow
*filer_window
)
813 Collection
*collection
= filer_window
->collection
;
816 entry
= mini_contents(filer_window
);
821 add_to_history(entry
);
823 cond
= find_compile(entry
);
826 delayed_error(PROJECT
, "Invalid Find condition");
830 info
.now
= time(NULL
);
831 info
.prune
= FALSE
; /* (don't care) */
832 n
= collection
->number_of_items
;
834 /* If an item at the start is selected then we could lose the
835 * primary selection after checking that item and then need to
836 * gain it again at the end. Therefore, if anything is selected
837 * then select the last item until the end of the search.
839 if (collection
->number_selected
)
840 collection_select_item(collection
, n
- 1);
842 for (i
= 0; i
< n
; i
++)
844 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
846 info
.leaf
= item
->leafname
;
847 info
.fullpath
= make_path(filer_window
->path
, info
.leaf
)->str
;
849 if (lstat(info
.fullpath
, &info
.stats
) == 0 &&
850 find_test_condition(cond
, &info
))
851 collection_select_item(collection
, i
);
853 collection_unselect_item(collection
, i
);
856 find_condition_free(cond
);
859 minibuffer_hide(filer_window
);
865 static gint
key_press_event(GtkWidget
*widget
,
867 FilerWindow
*filer_window
)
869 if (event
->keyval
== GDK_Escape
)
871 if (filer_window
->mini_type
== MINI_SHELL
)
875 line
= mini_contents(filer_window
);
877 add_to_history(line
);
880 minibuffer_hide(filer_window
);
884 switch (filer_window
->mini_type
)
887 switch (event
->keyval
)
890 search_in_dir(filer_window
, -1);
893 search_in_dir(filer_window
, 1);
896 path_return_pressed(filer_window
,
900 complete(filer_window
);
908 case MINI_RUN_ACTION
:
909 switch (event
->keyval
)
912 shell_recall(filer_window
, 1);
915 shell_recall(filer_window
, -1);
918 shell_tab(filer_window
);
921 shell_return_pressed(filer_window
);
928 switch (event
->keyval
)
931 shell_recall(filer_window
, 1);
934 shell_recall(filer_window
, -1);
939 select_return_pressed(filer_window
);
949 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget
), "key_press_event");
953 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
955 switch (filer_window
->mini_type
)
958 path_changed(mini
, filer_window
);
965 /* Returns a string (which must NOT be freed), or NULL if the buffer
966 * is blank (whitespace only).
968 static guchar
*mini_contents(FilerWindow
*filer_window
)
972 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
974 for (c
= entry
; *c
; c
++)