r240: Tidied up the pixmap code - now uses ImLib functions if possible.
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blob4ccf70f553daebdd82243513b59282323ac42a44
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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 */
24 #include "config.h"
26 #include <string.h>
27 #include <errno.h>
28 #include <ctype.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
33 #include "collection.h"
34 #include "gui_support.h"
35 #include "support.h"
36 #include "minibuffer.h"
37 #include "filer.h"
38 #include "main.h"
40 static GList *shell_history = NULL;
42 /* Static prototypes */
43 static gint key_press_event(GtkWidget *widget,
44 GdkEventKey *event,
45 FilerWindow *filer_window);
46 static void changed(GtkEditable *mini, FilerWindow *filer_window);
47 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
48 static gboolean matches(Collection *collection, int item, char *pattern);
49 static void search_in_dir(FilerWindow *filer_window, int dir);
52 /****************************************************************
53 * EXTERNAL INTERFACE *
54 ****************************************************************/
57 GtkWidget *create_minibuffer(FilerWindow *filer_window)
59 GtkWidget *mini;
61 mini = gtk_entry_new();
62 gtk_widget_set_style(mini, fixed_style);
63 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
64 GTK_SIGNAL_FUNC(key_press_event), filer_window);
65 gtk_signal_connect(GTK_OBJECT(mini), "changed",
66 GTK_SIGNAL_FUNC(changed), filer_window);
68 return mini;
71 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
73 Collection *collection;
74 GtkEntry *mini;
76 g_return_if_fail(filer_window != NULL);
77 g_return_if_fail(filer_window->minibuffer != NULL);
79 mini = GTK_ENTRY(filer_window->minibuffer);
81 filer_window->mini_type = MINI_NONE;
83 switch (mini_type)
85 case MINI_PATH:
86 collection = filer_window->collection;
87 filer_window->mini_cursor_base =
88 MAX(collection->cursor_item, 0);
89 gtk_entry_set_text(mini,
90 make_path(filer_window->path, "")->str);
91 break;
92 case MINI_SHELL:
93 filer_window->mini_cursor_base = -1; /* History */
94 gtk_entry_set_text(mini, "");
95 break;
96 default:
97 g_warning("Bad minibuffer type\n");
98 return;
101 filer_window->mini_type = mini_type;
103 gtk_entry_set_position(mini, -1);
105 gtk_widget_show(filer_window->minibuffer);
107 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
108 filer_window->minibuffer);
111 void minibuffer_hide(FilerWindow *filer_window)
113 filer_window->mini_type = MINI_NONE;
115 gtk_widget_hide(filer_window->minibuffer);
116 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
117 GTK_WIDGET(filer_window->collection));
120 /* Insert this leafname at the cursor (replacing the selection, if any).
121 * Must be in SHELL mode.
123 void minibuffer_add(FilerWindow *filer_window, guchar *leafname)
125 guchar *esc;
126 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
127 int pos;
129 g_return_if_fail(filer_window->mini_type == MINI_SHELL);
131 if (strchr(leafname, ' '))
132 esc = g_strdup_printf(" \"%s\"", leafname);
133 else
134 esc = g_strdup_printf(" %s", leafname);
136 gtk_editable_delete_selection(edit);
137 pos = gtk_editable_get_position(edit);
138 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
139 gtk_editable_set_position(edit, pos);
141 g_free(esc);
145 /****************************************************************
146 * INTERNAL FUNCTIONS *
147 ****************************************************************/
149 /* PATH ENTRY */
151 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
153 Collection *collection = filer_window->collection;
154 int item = collection->cursor_item;
155 char *path, *pattern;
156 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
158 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
159 pattern = strrchr(path, '/');
160 if (pattern)
161 pattern++;
162 else
163 pattern = path;
165 if (item == -1 || !matches(collection, item, pattern))
167 gdk_beep();
168 return;
171 if ((event->state & GDK_SHIFT_MASK) != 0)
172 flags |= OPEN_SHIFT;
174 filer_openitem(filer_window, item, flags);
177 /* Use the cursor item to fill in the minibuffer.
178 * If there are multiple matches the fill in as much as possible and beep.
180 static void complete(FilerWindow *filer_window)
182 GtkEntry *entry;
183 Collection *collection = filer_window->collection;
184 int cursor = collection->cursor_item;
185 DirItem *item;
186 int shortest_stem = -1;
187 int current_stem;
188 int other;
189 guchar *text, *leaf;
191 if (cursor < 0 || cursor >= collection->number_of_items)
193 gdk_beep();
194 return;
197 entry = GTK_ENTRY(filer_window->minibuffer);
199 item = (DirItem *) collection->items[cursor].data;
201 text = gtk_entry_get_text(entry);
202 leaf = strrchr(text, '/');
203 if (!leaf)
205 gdk_beep();
206 return;
209 leaf++;
210 if (!matches(collection, cursor, leaf))
212 gdk_beep();
213 return;
216 current_stem = strlen(leaf);
218 /* Find the longest other match of this name. It it's longer than
219 * the currently entered text then complete only up to that length.
221 for (other = 0; other < collection->number_of_items; other++)
223 DirItem *other_item = (DirItem *) collection->items[other].data;
224 int stem = 0;
226 if (other == cursor)
227 continue;
229 while (other_item->leafname[stem] && item->leafname[stem])
231 if (other_item->leafname[stem] != item->leafname[stem])
232 break;
233 stem++;
236 /* stem is the index of the first difference */
237 if (stem >= current_stem &&
238 (shortest_stem == -1 || stem < shortest_stem))
239 shortest_stem = stem;
242 if (current_stem == shortest_stem)
243 gdk_beep();
244 else if (current_stem < shortest_stem)
246 guchar *extra;
248 extra = g_strndup(item->leafname + current_stem,
249 shortest_stem - current_stem);
250 gtk_entry_append_text(entry, extra);
251 g_free(extra);
252 gdk_beep();
254 else
256 GString *new;
258 new = make_path(filer_window->path, item->leafname);
260 if (item->base_type == TYPE_DIRECTORY &&
261 (item->flags & ITEM_FLAG_APPDIR) == 0)
262 g_string_append_c(new, '/');
264 gtk_entry_set_text(entry, new->str);
268 static void path_changed(GtkEditable *mini, FilerWindow *filer_window)
270 char *new, *slash;
271 char *path, *real;
273 new = gtk_entry_get_text(GTK_ENTRY(mini));
274 if (*new == '/')
275 new = g_strdup(new);
276 else
277 new = g_strdup_printf("/%s", new);
279 slash = strrchr(new, '/');
280 *slash = '\0';
282 if (*new == '\0')
283 path = "/";
284 else
285 path = new;
287 real = pathdup(path);
289 if (strcmp(real, filer_window->path) != 0)
291 struct stat info;
292 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
293 filer_change_to(filer_window, real, slash + 1);
294 else
295 gdk_beep();
297 else
299 Collection *collection = filer_window->collection;
300 int item;
302 find_next_match(filer_window, slash + 1, 0);
303 item = collection->cursor_item;
304 if (item != -1 && !matches(collection, item, slash + 1))
305 gdk_beep();
308 g_free(real);
309 g_free(new);
312 /* Find the next item in the collection that matches 'pattern'. Start from
313 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
314 * -1 for backwards. 0 means forwards, but may stay the same.
316 * Does not automatically update mini_cursor_base.
318 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
320 Collection *collection = filer_window->collection;
321 int base = filer_window->mini_cursor_base;
322 int item = base;
324 if (collection->number_of_items < 1)
325 return;
327 if (base < 0 || base>= collection->number_of_items)
328 filer_window->mini_cursor_base = base = 0;
332 /* Step to the next item */
333 item += dir;
335 if (item >= collection->number_of_items)
336 item = 0;
337 else if (item < 0)
338 item = collection->number_of_items - 1;
340 if (dir == 0)
341 dir = 1;
342 else if (item == base)
343 break; /* No (other) matches at all */
346 } while (!matches(collection, item, pattern));
348 collection_set_cursor_item(collection, item);
351 static gboolean matches(Collection *collection, int item_number, char *pattern)
353 DirItem *item = (DirItem *) collection->items[item_number].data;
355 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
358 /* Find next match and set base for future matches. */
359 static void search_in_dir(FilerWindow *filer_window, int dir)
361 char *path, *pattern;
363 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
364 pattern = strrchr(path, '/');
365 if (pattern)
366 pattern++;
367 else
368 pattern = path;
370 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
371 find_next_match(filer_window, pattern, dir);
372 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
375 /* SHELL COMMANDS */
377 static void add_to_history(FilerWindow *filer_window)
379 guchar *line, *last, *c;
381 line = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
383 for (c = line; *c && isspace(*c); c++)
386 if (!*c)
387 return;
389 last = shell_history ? (guchar *) shell_history->data : NULL;
391 if (last && strcmp(last, line) == 0)
392 return; /* Duplicating last entry */
394 shell_history = g_list_prepend(shell_history, g_strdup(line));
397 static void shell_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
399 GPtrArray *argv;
400 int i;
401 guchar *entry;
402 Collection *collection = filer_window->collection;
404 add_to_history(filer_window);
406 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
408 argv = g_ptr_array_new();
409 g_ptr_array_add(argv, "sh");
410 g_ptr_array_add(argv, "-c");
411 g_ptr_array_add(argv, entry);
412 g_ptr_array_add(argv, "sh");
414 for (i = 0; i < collection->number_of_items; i++)
416 DirItem *item = (DirItem *) collection->items[i].data;
417 if (collection->items[i].selected)
418 g_ptr_array_add(argv, item->leafname);
421 g_ptr_array_add(argv, NULL);
423 switch (fork())
425 case -1:
426 delayed_error("ROX-Filer", "Failed to create "
427 "child process");
428 return;
429 case 0: /* Child */
430 dup2(to_error_log, STDOUT_FILENO);
431 close_on_exec(STDOUT_FILENO, FALSE);
432 dup2(to_error_log, STDERR_FILENO);
433 close_on_exec(STDERR_FILENO, FALSE);
434 if (chdir(filer_window->path))
435 g_printerr("chdir(%s) failed: %s\n",
436 filer_window->path,
437 g_strerror(errno));
438 execvp((char *) argv->pdata[0],
439 (char **) argv->pdata);
440 g_printerr("execvp(%s, ...) failed: %s\n",
441 (char *) argv->pdata[0],
442 g_strerror(errno));
443 _exit(0);
446 g_ptr_array_free(argv, TRUE);
448 minibuffer_hide(filer_window);
451 /* Move through the shell history */
452 static void shell_recall(FilerWindow *filer_window, int dir)
454 guchar *command;
455 int pos = filer_window->mini_cursor_base;
457 pos += dir;
458 if (pos >= 0)
460 command = g_list_nth_data(shell_history, pos);
461 if (!command)
462 return;
464 else
465 command = "";
467 if (pos < -1)
468 pos = -1;
469 filer_window->mini_cursor_base = pos;
471 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
474 /* EVENT HANDLERS */
476 static gint key_press_event(GtkWidget *widget,
477 GdkEventKey *event,
478 FilerWindow *filer_window)
480 if (event->keyval == GDK_Escape)
482 if (filer_window->mini_type == MINI_SHELL)
483 add_to_history(filer_window);
484 minibuffer_hide(filer_window);
485 return TRUE;
488 switch (filer_window->mini_type)
490 case MINI_PATH:
491 switch (event->keyval)
493 case GDK_Up:
494 search_in_dir(filer_window, -1);
495 break;
496 case GDK_Down:
497 search_in_dir(filer_window, 1);
498 break;
499 case GDK_Return:
500 path_return_pressed(filer_window,
501 event);
502 break;
503 case GDK_Tab:
504 complete(filer_window);
505 break;
506 default:
507 return FALSE;
509 break;
511 case MINI_SHELL:
512 switch (event->keyval)
514 case GDK_Up:
515 shell_recall(filer_window, 1);
516 break;
517 case GDK_Down:
518 shell_recall(filer_window, -1);
519 break;
520 case GDK_Tab:
521 break;
522 case GDK_Return:
523 shell_return_pressed(filer_window,
524 event);
525 default:
526 return FALSE;
528 break;
529 default:
530 break;
533 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
534 return TRUE;
537 static void changed(GtkEditable *mini, FilerWindow *filer_window)
539 switch (filer_window->mini_type)
541 case MINI_PATH:
542 path_changed(mini, filer_window);
543 return;
544 case MINI_SHELL:
545 break;
546 default:
547 break;