4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
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 */
32 #include <gdk/gdkkeysyms.h>
34 #include "collection.h"
35 #include "gui_support.h"
37 #include "minibuffer.h"
41 static GList
*shell_history
= NULL
;
43 /* Static prototypes */
44 static gint
key_press_event(GtkWidget
*widget
,
46 FilerWindow
*filer_window
);
47 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
);
48 static void find_next_match(FilerWindow
*filer_window
, char *pattern
, int dir
);
49 static gboolean
matches(Collection
*collection
, int item
, char *pattern
);
50 static void search_in_dir(FilerWindow
*filer_window
, int dir
);
51 static guchar
*mini_contents(FilerWindow
*filer_window
);
54 /****************************************************************
55 * EXTERNAL INTERFACE *
56 ****************************************************************/
59 GtkWidget
*create_minibuffer(FilerWindow
*filer_window
)
63 mini
= gtk_entry_new();
64 gtk_widget_set_style(mini
, fixed_style
);
65 gtk_signal_connect(GTK_OBJECT(mini
), "key_press_event",
66 GTK_SIGNAL_FUNC(key_press_event
), filer_window
);
67 gtk_signal_connect(GTK_OBJECT(mini
), "changed",
68 GTK_SIGNAL_FUNC(changed
), filer_window
);
73 void minibuffer_show(FilerWindow
*filer_window
, MiniType mini_type
)
75 Collection
*collection
;
78 g_return_if_fail(filer_window
!= NULL
);
79 g_return_if_fail(filer_window
->minibuffer
!= NULL
);
81 mini
= GTK_ENTRY(filer_window
->minibuffer
);
83 filer_window
->mini_type
= MINI_NONE
;
88 collection
= filer_window
->collection
;
89 filer_window
->mini_cursor_base
=
90 MAX(collection
->cursor_item
, 0);
91 gtk_entry_set_text(mini
,
92 make_path(filer_window
->path
, "")->str
);
95 filer_window
->mini_cursor_base
= -1; /* History */
96 gtk_entry_set_text(mini
, "");
99 g_warning("Bad minibuffer type\n");
103 filer_window
->mini_type
= mini_type
;
105 gtk_entry_set_position(mini
, -1);
107 gtk_widget_show(filer_window
->minibuffer
);
109 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
110 filer_window
->minibuffer
);
113 void minibuffer_hide(FilerWindow
*filer_window
)
115 filer_window
->mini_type
= MINI_NONE
;
117 gtk_widget_hide(filer_window
->minibuffer
);
118 gtk_window_set_focus(GTK_WINDOW(filer_window
->window
),
119 GTK_WIDGET(filer_window
->collection
));
122 /* Insert this leafname at the cursor (replacing the selection, if any).
123 * Must be in SHELL mode.
125 void minibuffer_add(FilerWindow
*filer_window
, guchar
*leafname
)
128 GtkEditable
*edit
= GTK_EDITABLE(filer_window
->minibuffer
);
129 GtkEntry
*entry
= GTK_ENTRY(edit
);
132 g_return_if_fail(filer_window
->mini_type
== MINI_SHELL
);
134 esc
= shell_escape(leafname
);
136 gtk_editable_delete_selection(edit
);
137 pos
= gtk_editable_get_position(edit
);
139 if (pos
> 0 && gtk_entry_get_text(entry
)[pos
- 1] != ' ')
140 gtk_editable_insert_text(edit
, " ", 1, &pos
);
142 gtk_editable_insert_text(edit
, esc
, strlen(esc
), &pos
);
143 gtk_editable_set_position(edit
, pos
);
149 /****************************************************************
150 * INTERNAL FUNCTIONS *
151 ****************************************************************/
155 static void path_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
157 Collection
*collection
= filer_window
->collection
;
158 int item
= collection
->cursor_item
;
159 char *path
, *pattern
;
160 int flags
= OPEN_FROM_MINI
| OPEN_SAME_WINDOW
;
162 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
163 pattern
= strrchr(path
, '/');
169 if (item
== -1 || !matches(collection
, item
, pattern
))
175 if ((event
->state
& GDK_SHIFT_MASK
) != 0)
178 filer_openitem(filer_window
, item
, flags
);
181 /* Use the cursor item to fill in the minibuffer.
182 * If there are multiple matches the fill in as much as possible and beep.
184 static void complete(FilerWindow
*filer_window
)
187 Collection
*collection
= filer_window
->collection
;
188 int cursor
= collection
->cursor_item
;
190 int shortest_stem
= -1;
195 if (cursor
< 0 || cursor
>= collection
->number_of_items
)
201 entry
= GTK_ENTRY(filer_window
->minibuffer
);
203 item
= (DirItem
*) collection
->items
[cursor
].data
;
205 text
= gtk_entry_get_text(entry
);
206 leaf
= strrchr(text
, '/');
214 if (!matches(collection
, cursor
, leaf
))
220 current_stem
= strlen(leaf
);
222 /* Find the longest other match of this name. It it's longer than
223 * the currently entered text then complete only up to that length.
225 for (other
= 0; other
< collection
->number_of_items
; other
++)
227 DirItem
*other_item
= (DirItem
*) collection
->items
[other
].data
;
233 while (other_item
->leafname
[stem
] && item
->leafname
[stem
])
235 if (other_item
->leafname
[stem
] != item
->leafname
[stem
])
240 /* stem is the index of the first difference */
241 if (stem
>= current_stem
&&
242 (shortest_stem
== -1 || stem
< shortest_stem
))
243 shortest_stem
= stem
;
246 if (current_stem
== shortest_stem
)
248 else if (current_stem
< shortest_stem
)
252 extra
= g_strndup(item
->leafname
+ current_stem
,
253 shortest_stem
- current_stem
);
254 gtk_entry_append_text(entry
, extra
);
262 new = make_path(filer_window
->path
, item
->leafname
);
264 if (item
->base_type
== TYPE_DIRECTORY
&&
265 (item
->flags
& ITEM_FLAG_APPDIR
) == 0)
266 g_string_append_c(new, '/');
268 gtk_entry_set_text(entry
, new->str
);
272 static void path_changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
277 new = gtk_entry_get_text(GTK_ENTRY(mini
));
281 new = g_strdup_printf("/%s", new);
283 slash
= strrchr(new, '/');
291 real
= pathdup(path
);
293 if (strcmp(real
, filer_window
->path
) != 0)
296 if (mc_stat(real
, &info
) == 0 && S_ISDIR(info
.st_mode
))
297 filer_change_to(filer_window
, real
, slash
+ 1);
303 Collection
*collection
= filer_window
->collection
;
306 find_next_match(filer_window
, slash
+ 1, 0);
307 item
= collection
->cursor_item
;
308 if (item
!= -1 && !matches(collection
, item
, slash
+ 1))
316 /* Find the next item in the collection that matches 'pattern'. Start from
317 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
318 * -1 for backwards. 0 means forwards, but may stay the same.
320 * Does not automatically update mini_cursor_base.
322 static void find_next_match(FilerWindow
*filer_window
, char *pattern
, int dir
)
324 Collection
*collection
= filer_window
->collection
;
325 int base
= filer_window
->mini_cursor_base
;
328 if (collection
->number_of_items
< 1)
331 if (base
< 0 || base
>= collection
->number_of_items
)
332 filer_window
->mini_cursor_base
= base
= 0;
336 /* Step to the next item */
339 if (item
>= collection
->number_of_items
)
342 item
= collection
->number_of_items
- 1;
346 else if (item
== base
)
347 break; /* No (other) matches at all */
350 } while (!matches(collection
, item
, pattern
));
352 collection_set_cursor_item(collection
, item
);
355 static gboolean
matches(Collection
*collection
, int item_number
, char *pattern
)
357 DirItem
*item
= (DirItem
*) collection
->items
[item_number
].data
;
359 return strncmp(item
->leafname
, pattern
, strlen(pattern
)) == 0;
362 /* Find next match and set base for future matches. */
363 static void search_in_dir(FilerWindow
*filer_window
, int dir
)
365 char *path
, *pattern
;
367 path
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
368 pattern
= strrchr(path
, '/');
374 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
375 find_next_match(filer_window
, pattern
, dir
);
376 filer_window
->mini_cursor_base
= filer_window
->collection
->cursor_item
;
381 static void add_to_history(guchar
*line
)
385 last
= shell_history
? (guchar
*) shell_history
->data
: NULL
;
387 if (last
&& strcmp(last
, line
) == 0)
388 return; /* Duplicating last entry */
390 shell_history
= g_list_prepend(shell_history
, g_strdup(line
));
393 static void shell_done(FilerWindow
*filer_window
)
395 if (filer_exists(filer_window
))
396 filer_update_dir(filer_window
, TRUE
);
399 /* Given a list of matches, return the longest stem. g_free() the result.
400 * Special chars are escaped. If there is only a single (non-dir) match
401 * then a trailing space is added.
403 static guchar
*best_match(FilerWindow
*filer_window
, glob_t
*matches
)
405 gchar
*first
= matches
->gl_pathv
[0];
407 int longest
, path_len
;
410 longest
= strlen(first
);
412 for (i
= 1; i
< matches
->gl_pathc
; i
++)
415 guchar
*m
= matches
->gl_pathv
[i
];
417 for (j
= 0; j
< longest
; j
++)
418 if (m
[j
] != first
[j
])
422 path_len
= strlen(filer_window
->path
);
423 if (strncmp(filer_window
->path
, first
, path_len
) == 0 &&
424 first
[path_len
] == '/' && first
[path_len
+ 1])
426 path
= g_strndup(first
+ path_len
+ 1, longest
- path_len
- 1);
429 path
= g_strndup(first
, longest
);
431 tmp
= shell_escape(path
);
434 if (matches
->gl_pathc
== 1 && tmp
[strlen(tmp
) - 1] != '/')
436 path
= g_strdup_printf("%s ", tmp
);
444 static void shell_tab(FilerWindow
*filer_window
)
454 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
455 pos
= gtk_editable_get_position(GTK_EDITABLE(filer_window
->minibuffer
));
456 leaf
= g_string_new(NULL
);
459 for (i
= 0; i
< pos
; i
++)
468 g_string_truncate(leaf
, 0);
471 else if (c
== '\\' && i
+ 1 < pos
)
473 else if (c
== '"' || c
== '\'')
477 for (++i
; i
< pos
; i
++)
481 if (cc
== '\\' && i
+ 1 < pos
)
483 else if (entry
[i
] == c
)
485 g_string_append_c(leaf
, entry
[i
]);
490 g_string_append_c(leaf
, c
);
496 if (leaf
->str
[0] != '/')
498 g_string_prepend_c(leaf
, '/');
499 g_string_prepend(leaf
, filer_window
->path
);
502 g_string_append_c(leaf
, '*');
508 GLOB_MARK
, NULL
, &matches
) == 0)
510 if (matches
.gl_pathc
> 0)
514 GTK_EDITABLE(filer_window
->minibuffer
);
516 best
= best_match(filer_window
, &matches
);
518 gtk_editable_delete_text(edit
, leaf_start
, pos
);
519 gtk_editable_insert_text(edit
, best
, strlen(best
),
521 gtk_editable_set_position(edit
, leaf_start
);
525 if (matches
.gl_pathc
!= 1)
531 g_string_free(leaf
, TRUE
);
534 static void shell_return_pressed(FilerWindow
*filer_window
, GdkEventKey
*event
)
539 Collection
*collection
= filer_window
->collection
;
542 entry
= mini_contents(filer_window
);
547 add_to_history(entry
);
549 argv
= g_ptr_array_new();
550 g_ptr_array_add(argv
, "sh");
551 g_ptr_array_add(argv
, "-c");
552 g_ptr_array_add(argv
, entry
);
553 g_ptr_array_add(argv
, "sh");
555 for (i
= 0; i
< collection
->number_of_items
; i
++)
557 DirItem
*item
= (DirItem
*) collection
->items
[i
].data
;
558 if (collection
->items
[i
].selected
)
559 g_ptr_array_add(argv
, item
->leafname
);
562 g_ptr_array_add(argv
, NULL
);
569 delayed_error("ROX-Filer", "Failed to create "
573 dup2(to_error_log
, STDOUT_FILENO
);
574 close_on_exec(STDOUT_FILENO
, FALSE
);
575 dup2(to_error_log
, STDERR_FILENO
);
576 close_on_exec(STDERR_FILENO
, FALSE
);
577 if (chdir(filer_window
->path
))
578 g_printerr("chdir(%s) failed: %s\n",
581 execvp((char *) argv
->pdata
[0],
582 (char **) argv
->pdata
);
583 g_printerr("execvp(%s, ...) failed: %s\n",
584 (char *) argv
->pdata
[0],
588 on_child_death(child
,
589 (CallbackFn
) shell_done
, filer_window
);
593 g_ptr_array_free(argv
, TRUE
);
596 minibuffer_hide(filer_window
);
599 /* Move through the shell history */
600 static void shell_recall(FilerWindow
*filer_window
, int dir
)
603 int pos
= filer_window
->mini_cursor_base
;
608 command
= g_list_nth_data(shell_history
, pos
);
617 filer_window
->mini_cursor_base
= pos
;
619 gtk_entry_set_text(GTK_ENTRY(filer_window
->minibuffer
), command
);
624 static gint
key_press_event(GtkWidget
*widget
,
626 FilerWindow
*filer_window
)
628 if (event
->keyval
== GDK_Escape
)
630 if (filer_window
->mini_type
== MINI_SHELL
)
634 line
= mini_contents(filer_window
);
636 add_to_history(line
);
639 minibuffer_hide(filer_window
);
643 switch (filer_window
->mini_type
)
646 switch (event
->keyval
)
649 search_in_dir(filer_window
, -1);
652 search_in_dir(filer_window
, 1);
655 path_return_pressed(filer_window
,
659 complete(filer_window
);
667 switch (event
->keyval
)
670 shell_recall(filer_window
, 1);
673 shell_recall(filer_window
, -1);
676 shell_tab(filer_window
);
679 shell_return_pressed(filer_window
,
689 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget
), "key_press_event");
693 static void changed(GtkEditable
*mini
, FilerWindow
*filer_window
)
695 switch (filer_window
->mini_type
)
698 path_changed(mini
, filer_window
);
707 /* Returns a string (which must NOT be freed), or NULL if the buffer
708 * is blank (whitespace only).
710 static guchar
*mini_contents(FilerWindow
*filer_window
)
714 entry
= gtk_entry_get_text(GTK_ENTRY(filer_window
->minibuffer
));
716 for (c
= entry
; *c
; c
++)