r280: All objects now have MIME types. This allows you to specify the action for
[rox-filer/ma.git] / ROX-Filer / src / minibuffer.c
blob7ff5ec074c5c550df71c58aad7285248d0f1ae01
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>
29 #include <glob.h>
30 #include <stdio.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
35 #include "collection.h"
36 #include "gui_support.h"
37 #include "support.h"
38 #include "minibuffer.h"
39 #include "filer.h"
40 #include "main.h"
42 static GList *shell_history = NULL;
44 /* Static prototypes */
45 static gint key_press_event(GtkWidget *widget,
46 GdkEventKey *event,
47 FilerWindow *filer_window);
48 static void changed(GtkEditable *mini, FilerWindow *filer_window);
49 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
50 static gboolean matches(Collection *collection, int item, char *pattern);
51 static void search_in_dir(FilerWindow *filer_window, int dir);
52 static guchar *mini_contents(FilerWindow *filer_window);
55 /****************************************************************
56 * EXTERNAL INTERFACE *
57 ****************************************************************/
60 /* Creates the minibuffer widgets, setting the appropriate fields
61 * in filer_window.
63 void create_minibuffer(FilerWindow *filer_window)
65 GtkWidget *hbox, *label, *mini;
67 hbox = gtk_hbox_new(FALSE, 0);
68 label = gtk_label_new(NULL);
69 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
71 mini = gtk_entry_new();
72 gtk_box_pack_start(GTK_BOX(hbox), mini, TRUE, TRUE, 0);
73 gtk_widget_set_style(mini, fixed_style);
74 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
75 GTK_SIGNAL_FUNC(key_press_event), filer_window);
76 gtk_signal_connect(GTK_OBJECT(mini), "changed",
77 GTK_SIGNAL_FUNC(changed), filer_window);
79 filer_window->minibuffer = mini;
80 filer_window->minibuffer_label = label;
81 filer_window->minibuffer_area = hbox;
84 void minibuffer_show(FilerWindow *filer_window, MiniType mini_type)
86 Collection *collection;
87 GtkEntry *mini;
89 g_return_if_fail(filer_window != NULL);
90 g_return_if_fail(filer_window->minibuffer != NULL);
92 mini = GTK_ENTRY(filer_window->minibuffer);
94 filer_window->mini_type = MINI_NONE;
95 gtk_label_set_text(GTK_LABEL(filer_window->minibuffer_label),
96 mini_type == MINI_PATH ? "Goto:" :
97 mini_type == MINI_SHELL ? "Shell:" :
98 mini_type == MINI_RUN_ACTION ? "Run Action:" :
99 "?");
101 collection = filer_window->collection;
102 collection_move_cursor(collection, 0, 0); /* Turn the cursor on */
103 switch (mini_type)
105 case MINI_PATH:
106 filer_window->mini_cursor_base =
107 MAX(collection->cursor_item, 0);
108 gtk_entry_set_text(mini,
109 make_path(filer_window->path, "")->str);
110 break;
111 case MINI_SHELL:
112 case MINI_RUN_ACTION:
113 filer_window->mini_cursor_base = -1; /* History */
114 gtk_entry_set_text(mini, "");
115 break;
116 default:
117 g_warning("Bad minibuffer type\n");
118 return;
121 filer_window->mini_type = mini_type;
123 gtk_entry_set_position(mini, -1);
125 gtk_widget_show_all(filer_window->minibuffer_area);
127 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
128 filer_window->minibuffer);
131 void minibuffer_hide(FilerWindow *filer_window)
133 filer_window->mini_type = MINI_NONE;
135 gtk_widget_hide(filer_window->minibuffer_area);
136 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
137 GTK_WIDGET(filer_window->collection));
140 /* Insert this leafname at the cursor (replacing the selection, if any).
141 * Must be in SHELL or RUN_ACTION mode.
143 void minibuffer_add(FilerWindow *filer_window, guchar *leafname)
145 guchar *esc;
146 GtkEditable *edit = GTK_EDITABLE(filer_window->minibuffer);
147 GtkEntry *entry = GTK_ENTRY(edit);
148 int pos;
150 g_return_if_fail(filer_window->mini_type == MINI_SHELL ||
151 filer_window->mini_type == MINI_RUN_ACTION);
153 esc = shell_escape(leafname);
155 gtk_editable_delete_selection(edit);
156 pos = gtk_editable_get_position(edit);
158 if (pos > 0 && gtk_entry_get_text(entry)[pos - 1] != ' ')
159 gtk_editable_insert_text(edit, " ", 1, &pos);
161 gtk_editable_insert_text(edit, esc, strlen(esc), &pos);
162 gtk_editable_set_position(edit, pos);
164 g_free(esc);
168 /****************************************************************
169 * INTERNAL FUNCTIONS *
170 ****************************************************************/
172 /* PATH ENTRY */
174 static void path_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
176 Collection *collection = filer_window->collection;
177 int item = collection->cursor_item;
178 char *path, *pattern;
179 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
181 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
182 pattern = strrchr(path, '/');
183 if (pattern)
184 pattern++;
185 else
186 pattern = path;
188 if (item == -1 || item >= collection->number_of_items ||
189 !matches(collection, item, pattern))
191 gdk_beep();
192 return;
195 if ((event->state & GDK_SHIFT_MASK) != 0)
196 flags |= OPEN_SHIFT;
198 filer_openitem(filer_window, item, flags);
201 /* Use the cursor item to fill in the minibuffer.
202 * If there are multiple matches the fill in as much as possible and beep.
204 static void complete(FilerWindow *filer_window)
206 GtkEntry *entry;
207 Collection *collection = filer_window->collection;
208 int cursor = collection->cursor_item;
209 DirItem *item;
210 int shortest_stem = -1;
211 int current_stem;
212 int other;
213 guchar *text, *leaf;
215 if (cursor < 0 || cursor >= collection->number_of_items)
217 gdk_beep();
218 return;
221 entry = GTK_ENTRY(filer_window->minibuffer);
223 item = (DirItem *) collection->items[cursor].data;
225 text = gtk_entry_get_text(entry);
226 leaf = strrchr(text, '/');
227 if (!leaf)
229 gdk_beep();
230 return;
233 leaf++;
234 if (!matches(collection, cursor, leaf))
236 gdk_beep();
237 return;
240 current_stem = strlen(leaf);
242 /* Find the longest other match of this name. It it's longer than
243 * the currently entered text then complete only up to that length.
245 for (other = 0; other < collection->number_of_items; other++)
247 DirItem *other_item = (DirItem *) collection->items[other].data;
248 int stem = 0;
250 if (other == cursor)
251 continue;
253 while (other_item->leafname[stem] && item->leafname[stem])
255 if (other_item->leafname[stem] != item->leafname[stem])
256 break;
257 stem++;
260 /* stem is the index of the first difference */
261 if (stem >= current_stem &&
262 (shortest_stem == -1 || stem < shortest_stem))
263 shortest_stem = stem;
266 if (current_stem == shortest_stem)
267 gdk_beep();
268 else if (current_stem < shortest_stem)
270 guchar *extra;
272 extra = g_strndup(item->leafname + current_stem,
273 shortest_stem - current_stem);
274 gtk_entry_append_text(entry, extra);
275 g_free(extra);
276 gdk_beep();
278 else
280 GString *new;
282 new = make_path(filer_window->path, item->leafname);
284 if (item->base_type == TYPE_DIRECTORY &&
285 (item->flags & ITEM_FLAG_APPDIR) == 0)
286 g_string_append_c(new, '/');
288 gtk_entry_set_text(entry, new->str);
292 static void path_changed(GtkEditable *mini, FilerWindow *filer_window)
294 char *new, *slash;
295 char *path, *real;
297 new = gtk_entry_get_text(GTK_ENTRY(mini));
298 if (*new == '/')
299 new = g_strdup(new);
300 else
301 new = g_strdup_printf("/%s", new);
303 slash = strrchr(new, '/');
304 *slash = '\0';
306 if (*new == '\0')
307 path = "/";
308 else
309 path = new;
311 real = pathdup(path);
313 if (strcmp(real, filer_window->path) != 0)
315 struct stat info;
316 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
317 filer_change_to(filer_window, real, slash + 1);
318 else
319 gdk_beep();
321 else
323 Collection *collection = filer_window->collection;
324 int item;
326 find_next_match(filer_window, slash + 1, 0);
327 item = collection->cursor_item;
328 if (item != -1 && !matches(collection, item, slash + 1))
329 gdk_beep();
332 g_free(real);
333 g_free(new);
336 /* Find the next item in the collection that matches 'pattern'. Start from
337 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
338 * -1 for backwards. 0 means forwards, but may stay the same.
340 * Does not automatically update mini_cursor_base.
342 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
344 Collection *collection = filer_window->collection;
345 int base = filer_window->mini_cursor_base;
346 int item = base;
348 if (collection->number_of_items < 1)
349 return;
351 if (base < 0 || base>= collection->number_of_items)
352 filer_window->mini_cursor_base = base = 0;
356 /* Step to the next item */
357 item += dir;
359 if (item >= collection->number_of_items)
360 item = 0;
361 else if (item < 0)
362 item = collection->number_of_items - 1;
364 if (dir == 0)
365 dir = 1;
366 else if (item == base)
367 break; /* No (other) matches at all */
370 } while (!matches(collection, item, pattern));
372 collection_set_cursor_item(collection, item);
375 static gboolean matches(Collection *collection, int item_number, char *pattern)
377 DirItem *item = (DirItem *) collection->items[item_number].data;
379 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
382 /* Find next match and set base for future matches. */
383 static void search_in_dir(FilerWindow *filer_window, int dir)
385 char *path, *pattern;
387 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
388 pattern = strrchr(path, '/');
389 if (pattern)
390 pattern++;
391 else
392 pattern = path;
394 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
395 find_next_match(filer_window, pattern, dir);
396 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
399 /* SHELL COMMANDS */
401 static void add_to_history(guchar *line)
403 guchar *last;
405 last = shell_history ? (guchar *) shell_history->data : NULL;
407 if (last && strcmp(last, line) == 0)
408 return; /* Duplicating last entry */
410 shell_history = g_list_prepend(shell_history, g_strdup(line));
413 static void shell_done(FilerWindow *filer_window)
415 if (filer_exists(filer_window))
416 filer_update_dir(filer_window, TRUE);
419 /* Given a list of matches, return the longest stem. g_free() the result.
420 * Special chars are escaped. If there is only a single (non-dir) match
421 * then a trailing space is added.
423 static guchar *best_match(FilerWindow *filer_window, glob_t *matches)
425 gchar *first = matches->gl_pathv[0];
426 int i;
427 int longest, path_len;
428 guchar *path, *tmp;
430 longest = strlen(first);
432 for (i = 1; i < matches->gl_pathc; i++)
434 int j;
435 guchar *m = matches->gl_pathv[i];
437 for (j = 0; j < longest; j++)
438 if (m[j] != first[j])
439 longest = j;
442 path_len = strlen(filer_window->path);
443 if (strncmp(filer_window->path, first, path_len) == 0 &&
444 first[path_len] == '/' && first[path_len + 1])
446 path = g_strndup(first + path_len + 1, longest - path_len - 1);
448 else
449 path = g_strndup(first, longest);
451 tmp = shell_escape(path);
452 g_free(path);
454 if (matches->gl_pathc == 1 && tmp[strlen(tmp) - 1] != '/')
456 path = g_strdup_printf("%s ", tmp);
457 g_free(tmp);
458 return path;
461 return tmp;
464 static void shell_tab(FilerWindow *filer_window)
466 int i;
467 guchar *entry;
468 guchar quote;
469 int pos;
470 GString *leaf;
471 glob_t matches;
472 int leaf_start;
474 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
475 pos = gtk_editable_get_position(GTK_EDITABLE(filer_window->minibuffer));
476 leaf = g_string_new(NULL);
478 quote = '\0';
479 for (i = 0; i < pos; i++)
481 guchar c = entry[i];
483 if (leaf->len == 0)
484 leaf_start = i;
486 if (c == ' ')
488 g_string_truncate(leaf, 0);
489 continue;
491 else if (c == '\\' && i + 1 < pos)
492 c = entry[++i];
493 else if (c == '"' || c == '\'')
495 guchar cc;
497 for (++i; i < pos; i++)
499 cc = entry[i];
501 if (cc == '\\' && i + 1 < pos)
502 cc = entry[++i];
503 else if (entry[i] == c)
504 break;
505 g_string_append_c(leaf, entry[i]);
507 continue;
510 g_string_append_c(leaf, c);
513 if (leaf->len == 0)
514 leaf_start = pos;
516 if (leaf->str[0] != '/')
518 g_string_prepend_c(leaf, '/');
519 g_string_prepend(leaf, filer_window->path);
522 g_string_append_c(leaf, '*');
524 if (glob(leaf->str,
525 #ifdef GLOB_TILDE
526 GLOB_TILDE |
527 #endif
528 GLOB_MARK, NULL, &matches) == 0)
530 if (matches.gl_pathc > 0)
532 guchar *best;
533 GtkEditable *edit =
534 GTK_EDITABLE(filer_window->minibuffer);
536 best = best_match(filer_window, &matches);
538 gtk_editable_delete_text(edit, leaf_start, pos);
539 gtk_editable_insert_text(edit, best, strlen(best),
540 &leaf_start);
541 gtk_editable_set_position(edit, leaf_start);
543 g_free(best);
545 if (matches.gl_pathc != 1)
546 gdk_beep();
548 globfree(&matches);
551 g_string_free(leaf, TRUE);
554 /* This is called from shell_return_pressed() so that the command is already
555 * in the history. Returns TRUE if the buffer should be closed.
557 gboolean set_run_action(FilerWindow *filer_window, guchar *command)
559 Collection *collection = filer_window->collection;
560 int n = collection->cursor_item;
561 DirItem *item;
562 guchar *path, *tmp;
563 FILE *file;
564 int error = 0, len;
566 if (!strchr(command, '$'))
568 delayed_error(PROJECT,
569 _("To set the run action for a file, either:\n"
570 "- Drag a file to an application directory (eg drag an image to the "
571 "Gimp), or\n" "- Enter a shell command which contains a \"$1\""
572 "where the name of the file should go (eg ` gimp \"$1\" ')"));
573 return FALSE;
576 if (n < 0 || n >= collection->number_of_items)
578 delayed_error(PROJECT,
579 _("You must have the cursor on the item to use for '$1'."));
580 return FALSE;
583 item = (DirItem *) collection->items[n].data;
584 path = type_ask_which_action(item->mime_type->media_type,
585 item->mime_type->subtype);
587 if (!path)
588 return TRUE;
590 tmp = g_strdup_printf("#! /bin/sh\nexec %s\n", command);
591 len = strlen(tmp);
593 file = fopen(path, "wb");
594 if (fwrite(tmp, 1, len, file) < len)
595 error = errno;
596 if (fclose(file) && error == 0)
597 error = errno;
598 if (chmod(path, 0777))
599 error = errno;
601 if (error)
602 report_error(PROJECT, g_strerror(errno));
604 g_free(tmp);
606 return TRUE;
609 /* Either execute the command or make it the default run action */
610 static void shell_return_pressed(FilerWindow *filer_window, GdkEventKey *event)
612 GPtrArray *argv;
613 int i;
614 guchar *entry;
615 Collection *collection = filer_window->collection;
616 int child;
618 entry = mini_contents(filer_window);
620 if (!entry)
621 goto out;
623 add_to_history(entry);
625 if (filer_window->mini_type == MINI_RUN_ACTION)
627 if (set_run_action(filer_window, entry))
628 goto out;
629 else
630 return;
633 argv = g_ptr_array_new();
634 g_ptr_array_add(argv, "sh");
635 g_ptr_array_add(argv, "-c");
636 g_ptr_array_add(argv, entry);
637 g_ptr_array_add(argv, "sh");
639 for (i = 0; i < collection->number_of_items; i++)
641 DirItem *item = (DirItem *) collection->items[i].data;
642 if (collection->items[i].selected)
643 g_ptr_array_add(argv, item->leafname);
646 g_ptr_array_add(argv, NULL);
648 child = fork();
650 switch (child)
652 case -1:
653 delayed_error(PROJECT, _("Failed to create "
654 "child process"));
655 break;
656 case 0: /* Child */
657 dup2(to_error_log, STDOUT_FILENO);
658 close_on_exec(STDOUT_FILENO, FALSE);
659 dup2(to_error_log, STDERR_FILENO);
660 close_on_exec(STDERR_FILENO, FALSE);
661 if (chdir(filer_window->path))
662 g_printerr("chdir(%s) failed: %s\n",
663 filer_window->path,
664 g_strerror(errno));
665 execvp((char *) argv->pdata[0],
666 (char **) argv->pdata);
667 g_printerr("execvp(%s, ...) failed: %s\n",
668 (char *) argv->pdata[0],
669 g_strerror(errno));
670 _exit(0);
671 default:
672 on_child_death(child,
673 (CallbackFn) shell_done, filer_window);
674 break;
677 g_ptr_array_free(argv, TRUE);
679 out:
680 minibuffer_hide(filer_window);
683 /* Move through the shell history */
684 static void shell_recall(FilerWindow *filer_window, int dir)
686 guchar *command;
687 int pos = filer_window->mini_cursor_base;
689 pos += dir;
690 if (pos >= 0)
692 command = g_list_nth_data(shell_history, pos);
693 if (!command)
694 return;
696 else
697 command = "";
699 if (pos < -1)
700 pos = -1;
701 filer_window->mini_cursor_base = pos;
703 gtk_entry_set_text(GTK_ENTRY(filer_window->minibuffer), command);
706 /* EVENT HANDLERS */
708 static gint key_press_event(GtkWidget *widget,
709 GdkEventKey *event,
710 FilerWindow *filer_window)
712 if (event->keyval == GDK_Escape)
714 if (filer_window->mini_type == MINI_SHELL)
716 guchar *line;
718 line = mini_contents(filer_window);
719 if (line)
720 add_to_history(line);
723 minibuffer_hide(filer_window);
724 return TRUE;
727 switch (filer_window->mini_type)
729 case MINI_PATH:
730 switch (event->keyval)
732 case GDK_Up:
733 search_in_dir(filer_window, -1);
734 break;
735 case GDK_Down:
736 search_in_dir(filer_window, 1);
737 break;
738 case GDK_Return:
739 path_return_pressed(filer_window,
740 event);
741 break;
742 case GDK_Tab:
743 complete(filer_window);
744 break;
745 default:
746 return FALSE;
748 break;
750 case MINI_SHELL:
751 case MINI_RUN_ACTION:
752 switch (event->keyval)
754 case GDK_Up:
755 shell_recall(filer_window, 1);
756 break;
757 case GDK_Down:
758 shell_recall(filer_window, -1);
759 break;
760 case GDK_Tab:
761 shell_tab(filer_window);
762 break;
763 case GDK_Return:
764 shell_return_pressed(filer_window,
765 event);
766 default:
767 return FALSE;
769 break;
770 default:
771 break;
774 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
775 return TRUE;
778 static void changed(GtkEditable *mini, FilerWindow *filer_window)
780 switch (filer_window->mini_type)
782 case MINI_PATH:
783 path_changed(mini, filer_window);
784 return;
785 default:
786 break;
790 /* Returns a string (which must NOT be freed), or NULL if the buffer
791 * is blank (whitespace only).
793 static guchar *mini_contents(FilerWindow *filer_window)
795 guchar *entry, *c;
797 entry = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
799 for (c = entry; *c; c++)
800 if (!isspace(*c))
801 return entry;
803 return NULL;