r183: Added the TODO file. Info permissions are displayed in symbolic form.
[rox-filer.git] / ROX-Filer / src / minibuffer.c
blob2ab709c17c8df0cbfa5effcd267cb528af1f3dba
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 1999, 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 <string.h>
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
29 #include "collection.h"
30 #include "support.h"
31 #include "minibuffer.h"
32 #include "filer.h"
34 /* Static prototypes */
35 static gint key_press_event(GtkWidget *widget,
36 GdkEventKey *event,
37 FilerWindow *filer_window);
38 static void changed(GtkEditable *mini, FilerWindow *filer_window);
39 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir);
40 static gboolean matches(Collection *collection, int item, char *pattern);
41 static void search_in_dir(FilerWindow *filer_window, int dir);
44 /****************************************************************
45 * EXTERNAL INTERFACE *
46 ****************************************************************/
49 GtkWidget *create_minibuffer(FilerWindow *filer_window)
51 GtkWidget *mini;
53 mini = gtk_entry_new();
54 gtk_signal_connect(GTK_OBJECT(mini), "key_press_event",
55 GTK_SIGNAL_FUNC(key_press_event), filer_window);
56 gtk_signal_connect(GTK_OBJECT(mini), "changed",
57 GTK_SIGNAL_FUNC(changed), filer_window);
59 return mini;
62 void minibuffer_show(FilerWindow *filer_window)
64 Collection *collection;
65 GtkEntry *mini;
67 g_return_if_fail(filer_window != NULL);
68 g_return_if_fail(filer_window->minibuffer != NULL);
70 collection = filer_window->collection;
71 filer_window->mini_cursor_base = MAX(collection->cursor_item, 0);
73 mini = GTK_ENTRY(filer_window->minibuffer);
75 gtk_entry_set_text(mini, make_path(filer_window->path, "")->str);
76 gtk_entry_set_position(mini, -1);
78 gtk_widget_show(filer_window->minibuffer);
80 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
81 filer_window->minibuffer);
84 void minibuffer_hide(FilerWindow *filer_window)
86 gtk_widget_hide(filer_window->minibuffer);
87 gtk_window_set_focus(GTK_WINDOW(filer_window->window),
88 GTK_WIDGET(filer_window->collection));
92 /****************************************************************
93 * INTERNAL FUNCTIONS *
94 ****************************************************************/
96 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
98 Collection *collection = filer_window->collection;
99 int item = collection->cursor_item;
100 char *path, *pattern;
101 int flags = OPEN_FROM_MINI | OPEN_SAME_WINDOW;
103 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
104 pattern = strrchr(path, '/');
105 if (pattern)
106 pattern++;
107 else
108 pattern = path;
110 if (item == -1 || !matches(collection, item, pattern))
112 gdk_beep();
113 return;
116 if ((event->state & GDK_SHIFT_MASK) != 0)
117 flags |= OPEN_SHIFT;
119 filer_openitem(filer_window, item, flags);
122 /* Use the cursor item to fill in the minibuffer.
123 * If there are multiple matches the fill in as much as possible and beep.
125 static void complete(FilerWindow *filer_window)
127 GtkEntry *entry;
128 Collection *collection = filer_window->collection;
129 int cursor = collection->cursor_item;
130 DirItem *item;
131 int shortest_stem = -1;
132 int current_stem;
133 int other;
134 guchar *text, *leaf;
136 if (cursor < 0 || cursor >= collection->number_of_items)
138 gdk_beep();
139 return;
142 entry = GTK_ENTRY(filer_window->minibuffer);
144 item = (DirItem *) collection->items[cursor].data;
146 text = gtk_entry_get_text(entry);
147 leaf = strrchr(text, '/');
148 if (!leaf)
150 gdk_beep();
151 return;
154 leaf++;
155 if (!matches(collection, cursor, leaf))
157 gdk_beep();
158 return;
161 current_stem = strlen(leaf);
163 /* Find the longest other match of this name. It it's longer than
164 * the currently entered text then complete only up to that length.
166 for (other = 0; other < collection->number_of_items; other++)
168 DirItem *other_item = (DirItem *) collection->items[other].data;
169 int stem = 0;
171 if (other == cursor)
172 continue;
174 while (other_item->leafname[stem] && item->leafname[stem])
176 if (other_item->leafname[stem] != item->leafname[stem])
177 break;
178 stem++;
181 /* stem is the index of the first difference */
182 if (stem >= current_stem &&
183 (shortest_stem == -1 || stem < shortest_stem))
184 shortest_stem = stem;
187 if (current_stem == shortest_stem)
188 gdk_beep();
189 else if (current_stem < shortest_stem)
191 guchar *extra;
193 extra = g_strndup(item->leafname + current_stem,
194 shortest_stem - current_stem);
195 gtk_entry_append_text(entry, extra);
196 g_free(extra);
197 gdk_beep();
199 else
201 GString *new;
203 new = make_path(filer_window->path, item->leafname);
205 if (item->base_type == TYPE_DIRECTORY &&
206 (item->flags & ITEM_FLAG_APPDIR) == 0)
207 g_string_append_c(new, '/');
209 gtk_entry_set_text(entry, new->str);
213 static gint key_press_event(GtkWidget *widget,
214 GdkEventKey *event,
215 FilerWindow *filer_window)
217 switch (event->keyval)
219 case GDK_Escape:
220 minibuffer_hide(filer_window);
221 break;
222 case GDK_Up:
223 search_in_dir(filer_window, -1);
224 break;
225 case GDK_Down:
226 search_in_dir(filer_window, 1);
227 break;
228 case GDK_Return:
229 return_pressed(filer_window, event);
230 break;
231 case GDK_Tab:
232 complete(filer_window);
233 break;
234 default:
235 return FALSE;
238 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
239 return TRUE;
242 static void changed(GtkEditable *mini, FilerWindow *filer_window)
244 char *new, *slash;
245 char *path, *real;
247 new = gtk_entry_get_text(GTK_ENTRY(mini));
248 if (*new == '/')
249 new = g_strdup(new);
250 else
251 new = g_strdup_printf("/%s", new);
253 slash = strrchr(new, '/');
254 *slash = '\0';
256 if (*new == '\0')
257 path = "/";
258 else
259 path = new;
261 real = pathdup(path);
263 if (strcmp(real, filer_window->path) != 0)
265 struct stat info;
266 if (stat(real, &info) == 0 && S_ISDIR(info.st_mode))
267 filer_change_to(filer_window, real, slash + 1);
268 else
269 gdk_beep();
271 else
273 Collection *collection = filer_window->collection;
274 int item;
276 find_next_match(filer_window, slash + 1, 0);
277 item = collection->cursor_item;
278 if (item != -1 && !matches(collection, item, slash + 1))
279 gdk_beep();
282 g_free(real);
283 g_free(new);
286 /* Find the next item in the collection that matches 'pattern'. Start from
287 * mini_cursor_base and loop at either end. dir is 1 for a forward search,
288 * -1 for backwards. 0 means forwards, but may stay the same.
290 * Does not automatically update mini_cursor_base.
292 static void find_next_match(FilerWindow *filer_window, char *pattern, int dir)
294 Collection *collection = filer_window->collection;
295 int base = filer_window->mini_cursor_base;
296 int item = base;
298 if (collection->number_of_items < 1)
299 return;
301 if (base < 0 || base>= collection->number_of_items)
302 filer_window->mini_cursor_base = base = 0;
306 /* Step to the next item */
307 item += dir;
309 if (item >= collection->number_of_items)
310 item = 0;
311 else if (item < 0)
312 item = collection->number_of_items - 1;
314 if (dir == 0)
315 dir = 1;
316 else if (item == base)
317 break; /* No (other) matches at all */
320 } while (!matches(collection, item, pattern));
322 collection_set_cursor_item(collection, item);
325 static gboolean matches(Collection *collection, int item_number, char *pattern)
327 DirItem *item = (DirItem *) collection->items[item_number].data;
329 return strncmp(item->leafname, pattern, strlen(pattern)) == 0;
332 /* Find next match and set base for future matches. */
333 static void search_in_dir(FilerWindow *filer_window, int dir)
335 char *path, *pattern;
337 path = gtk_entry_get_text(GTK_ENTRY(filer_window->minibuffer));
338 pattern = strrchr(path, '/');
339 if (pattern)
340 pattern++;
341 else
342 pattern = path;
344 filer_window->mini_cursor_base = filer_window->collection->cursor_item;
345 find_next_match(filer_window, pattern, dir);
346 filer_window->mini_cursor_base = filer_window->collection->cursor_item;