r214: Added support for mc's Virtual File System.
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blobe93b7f6bc20fc0b6b2aacc6f0ebc8327e2aa45ef
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>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
31 #include "collection.h"
32 #include "support.h"
33 #include "minibuffer.h"
34 #include "filer.h"
36 /* Static prototypes */
37 static gint key_press_event(GtkWidget *widget,
38 GdkEventKey *event,
39 FilerWindow *filer_window);
40 static void changed(GtkEditable *mini, FilerWindow *filer_window);
41 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
42 static gboolean matches(Collection *collection, int item, char *pattern);
43 static void search_in_dir(FilerWindow *filer_window, int dir);
46 /****************************************************************
47 * EXTERNAL INTERFACE *
48 ****************************************************************/
51 GtkWidget *create_minibuffer(FilerWindow *filer_window)
53 GtkWidget *mini;
55 mini = gtk_entry_new();
56 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
57 GTK_SIGNAL_FUNC(key_press_event), filer_window);
58 gtk_signal_connect(GTK_OBJECT(mini), "changed",
59 GTK_SIGNAL_FUNC(changed), filer_window);
61 return mini;
64 void minibuffer_show(FilerWindow *filer_window)
66 Collection *collection;
67 GtkEntry *mini;
69 g_return_if_fail(filer_window != NULL);
70 g_return_if_fail(filer_window->minibuffer != NULL);
72 collection = filer_window->collection;
73 filer_window->mini_cursor_base = MAX(collection->cursor_item, 0);
75 mini = GTK_ENTRY(filer_window->minibuffer);
77 gtk_entry_set_text(mini, make_path(filer_window->path, "")->str);
78 gtk_entry_set_position(mini, -1);
80 gtk_widget_show(filer_window->minibuffer);
82 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
83 filer_window->minibuffer);
86 void minibuffer_hide(FilerWindow *filer_window)
88 gtk_widget_hide(filer_window->minibuffer);
89 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
90 GTK_WIDGET(filer_window->collection));
94 /****************************************************************
95 * INTERNAL FUNCTIONS *
96 ****************************************************************/
98 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
100 Collection *collection = filer_window->collection;
101 int item = collection->cursor_item;
102 char *path, *pattern;
103 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
105 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
106 pattern = strrchr(path, '/');
107 if (pattern)
108 pattern++;
109 else
110 pattern = path;
112 if (item == -1 || !matches(collection, item, pattern))
114 gdk_beep();
115 return;
118 if ((event->state & GDK_SHIFT_MASK) != 0)
119 flags |= OPEN_SHIFT;
121 filer_openitem(filer_window, item, flags);
124 /* Use the cursor item to fill in the minibuffer.
125 * If there are multiple matches the fill in as much as possible and beep.
127 static void complete(FilerWindow *filer_window)
129 GtkEntry *entry;
130 Collection *collection = filer_window->collection;
131 int cursor = collection->cursor_item;
132 DirItem *item;
133 int shortest_stem = -1;
134 int current_stem;
135 int other;
136 guchar *text, *leaf;
138 if (cursor < 0 || cursor >= collection->number_of_items)
140 gdk_beep();
141 return;
144 entry = GTK_ENTRY(filer_window->minibuffer);
146 item = (DirItem *) collection->items[cursor].data;
148 text = gtk_entry_get_text(entry);
149 leaf = strrchr(text, '/');
150 if (!leaf)
152 gdk_beep();
153 return;
156 leaf++;
157 if (!matches(collection, cursor, leaf))
159 gdk_beep();
160 return;
163 current_stem = strlen(leaf);
165 /* Find the longest other match of this name. It it's longer than
166 * the currently entered text then complete only up to that length.
168 for (other = 0; other < collection->number_of_items; other++)
170 DirItem *other_item = (DirItem *) collection->items[other].data;
171 int stem = 0;
173 if (other == cursor)
174 continue;
176 while (other_item->leafname[stem] && item->leafname[stem])
178 if (other_item->leafname[stem] != item->leafname[stem])
179 break;
180 stem++;
183 /* stem is the index of the first difference */
184 if (stem >= current_stem &&
185 (shortest_stem == -1 || stem < shortest_stem))
186 shortest_stem = stem;
189 if (current_stem == shortest_stem)
190 gdk_beep();
191 else if (current_stem < shortest_stem)
193 guchar *extra;
195 extra = g_strndup(item->leafname + current_stem,
196 shortest_stem - current_stem);
197 gtk_entry_append_text(entry, extra);
198 g_free(extra);
199 gdk_beep();
201 else
203 GString *new;
205 new = make_path(filer_window->path, item->leafname);
207 if (item->base_type == TYPE_DIRECTORY &&
208 (item->flags & ITEM_FLAG_APPDIR) == 0)
209 g_string_append_c(new, '/');
211 gtk_entry_set_text(entry, new->str);
215 static gint key_press_event(GtkWidget *widget,
216 GdkEventKey *event,
217 FilerWindow *filer_window)
219 switch (event->keyval)
221 case GDK_Escape:
222 minibuffer_hide(filer_window);
223 break;
224 case GDK_Up:
225 search_in_dir(filer_window, -1);
226 break;
227 case GDK_Down:
228 search_in_dir(filer_window, 1);
229 break;
230 case GDK_Return:
231 return_pressed(filer_window, event);
232 break;
233 case GDK_Tab:
234 complete(filer_window);
235 break;
236 default:
237 return FALSE;
240 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
241 return TRUE;
244 static void changed(GtkEditable *mini, FilerWindow *filer_window)
246 char *new, *slash;
247 char *path, *real;
249 new = gtk_entry_get_text(GTK_ENTRY(mini));
250 if (*new == '/')
251 new = g_strdup(new);
252 else
253 new = g_strdup_printf("/%s", new);
255 slash = strrchr(new, '/');
256 *slash = '\0';
258 if (*new == '\0')
259 path = "/";
260 else
261 path = new;
263 real = pathdup(path);
265 if (strcmp(real, filer_window->path) != 0)
267 struct stat info;
268 if (mc_stat(real, &info) == 0 && S_ISDIR(info.st_mode))
269 filer_change_to(filer_window, real, slash + 1);
270 else
271 gdk_beep();
273 else
275 Collection *collection = filer_window->collection;
276 int item;
278 find_next_match(filer_window, slash + 1, 0);
279 item = collection->cursor_item;
280 if (item != -1 && !matches(collection, item, slash + 1))
281 gdk_beep();
284 g_free(real);
285 g_free(new);
288 /* Find the next item in the collection that matches 'pattern'. Start from
289 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
290 * -1 for backwards. 0 means forwards, but may stay the same.
292 * Does not automatically update mini_cursor_base.
294 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
296 Collection *collection = filer_window->collection;
297 int base = filer_window->mini_cursor_base;
298 int item = base;
300 if (collection->number_of_items < 1)
301 return;
303 if (base < 0 || base>= collection->number_of_items)
304 filer_window->mini_cursor_base = base = 0;
308 /* Step to the next item */
309 item += dir;
311 if (item >= collection->number_of_items)
312 item = 0;
313 else if (item < 0)
314 item = collection->number_of_items - 1;
316 if (dir == 0)
317 dir = 1;
318 else if (item == base)
319 break; /* No (other) matches at all */
322 } while (!matches(collection, item, pattern));
324 collection_set_cursor_item(collection, item);
327 static gboolean matches(Collection *collection, int item_number, char *pattern)
329 DirItem *item = (DirItem *) collection->items[item_number].data;
331 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
334 /* Find next match and set base for future matches. */
335 static void search_in_dir(FilerWindow *filer_window, int dir)
337 char *path, *pattern;
339 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
340 pattern = strrchr(path, '/');
341 if (pattern)
342 pattern++;
343 else
344 pattern = path;
346 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
347 find_next_match(filer_window, pattern, dir);
348 filer_window->mini_cursor_base = filer_window->collection->cursor_item;