##### Note: GQview CVS on sourceforge is not always up to date, please use #####
[geeqie.git] / src / ui_tabcomp.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #ifdef HAVE_CONFIG_H
13 #  include "config.h"
14 #endif
15 #include "intl.h"
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <dirent.h>
23
24 #include <gdk/gdk.h>
25 #include <gtk/gtk.h>
26 #include <gdk-pixbuf/gdk-pixbuf.h>
27
28 #include "ui_tabcomp.h"
29
30 #include "ui_bookmark.h"
31 #include "ui_fileops.h"
32 #include "ui_spinner.h"
33 #include "ui_utildlg.h"
34
35 #include <gdk/gdkkeysyms.h> /* for key values */
36
37
38 /* define this to enable a pop-up menu that shows possible matches
39  * #define TAB_COMPLETION_ENABLE_POPUP_MENU
40  */
41 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1
42 #define TAB_COMP_POPUP_MAX 500
43
44 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
45 #include "ui_menu.h"
46 #endif
47
48
49 /* ----------------------------------------------------------------
50    Tab completion routines, can be connected to any gtkentry widget
51    using the tab_completion_add_to_entry() function.
52    Use remove_trailing_slash() to strip the trailing '/'.
53    ----------------------------------------------------------------*/
54
55 typedef struct _TabCompData TabCompData;
56 struct _TabCompData
57 {
58         GtkWidget *entry;
59         gchar *dir_path;
60         GList *file_list;
61         void (*enter_func)(const gchar *, gpointer);
62         void (*tab_func)(const gchar *, gpointer);
63         gpointer enter_data;
64         gpointer tab_data;
65
66         GtkWidget *combo;
67         gint has_history;
68         gchar *history_key;
69         gint history_levels;
70
71         FileDialog *fd;
72         gchar *fd_title;
73         gint fd_folders_only;
74         GtkWidget *fd_button;
75 };
76
77
78 static void tab_completion_select_show(TabCompData *td);
79
80 static void tab_completion_free_list(TabCompData *td)
81 {
82         GList *list;
83
84         g_free(td->dir_path);
85         td->dir_path = NULL;
86
87         list = td->file_list;
88
89         while(list)
90                 {
91                 g_free(list->data);
92                 list = list->next;
93                 }
94
95         g_list_free(td->file_list);
96         td->file_list = NULL;
97 }
98
99 static void tab_completion_read_dir(TabCompData *td, const gchar *path)
100 {
101         DIR *dp;
102         struct dirent *dir;
103         GList *list = NULL;
104         gchar *pathl;
105
106         tab_completion_free_list(td);
107
108         pathl = path_from_utf8(path);
109         dp = opendir(pathl);
110         g_free(pathl);
111         if (!dp)
112                 {
113                 /* dir not found */
114                 return;
115                 }
116         while ((dir = readdir(dp)) != NULL)
117                 {
118                 /* skips removed files */
119                 if (dir->d_ino > 0)
120                         {
121                         gchar *name = dir->d_name;
122                         if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
123                                 {
124                                 list = g_list_prepend(list, path_to_utf8(name));
125                                 }
126                         }
127                 }
128         closedir(dp);
129
130         td->dir_path = g_strdup(path);
131         td->file_list = list;
132 }
133
134 static void tab_completion_destroy(GtkWidget *widget, gpointer data)
135 {
136         TabCompData *td = data;
137
138         tab_completion_free_list(td);
139         g_free(td->history_key);
140
141         if (td->fd) file_dialog_close(td->fd);
142         g_free(td->fd_title);
143
144         g_free(td);
145 }
146
147 static gint tab_completion_emit_enter_signal(TabCompData *td)
148 {
149         gchar *text;
150         if (!td->enter_func) return FALSE;
151
152         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
153
154         if (text[0] == '~')
155                 {
156                 gchar *t = text;
157                 text = g_strconcat(homedir(), t + 1, NULL);
158                 g_free(t);
159                 }
160
161         td->enter_func(text, td->enter_data);
162         g_free(text);
163
164         return TRUE;
165 }
166
167 static void tab_completion_emit_tab_signal(TabCompData *td)
168 {
169         gchar *text;
170         if (!td->tab_func) return;
171
172         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
173
174         if (text[0] == '~')
175                 {
176                 gchar *t = text;
177                 text = g_strconcat(homedir(), t + 1, NULL);
178                 g_free(t);
179                 }
180
181         td->tab_func(text, td->tab_data);
182         g_free(text);
183 }
184
185 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
186
187 static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
188 {
189         TabCompData *td = data;
190
191         if (event->keyval == GDK_Tab ||
192             event->keyval == GDK_BackSpace ||
193             (event->keyval >= 0x20 && event->keyval <= 0xFF) )
194                 {
195                 if (event->keyval >= 0x20 && event->keyval <= 0xFF)
196                         {
197                         gchar buf[2];
198                         gint p = -1;
199
200                         buf[0] = event->keyval;
201                         buf[1] = '\0';
202                         gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p);
203                         gtk_editable_set_position(GTK_EDITABLE(td->entry), -1);
204                         }
205
206                 /*close the menu */
207                 gtk_menu_popdown(GTK_MENU(widget));
208                 /* doing this does not emit the "selection done" signal, unref it ourselves */
209                 gtk_widget_unref(widget);
210                         
211                 return TRUE;
212                 }
213
214         return FALSE;
215 }
216
217 static void tab_completion_popup_cb(GtkWidget *widget, gpointer data)
218 {
219         gchar *name = data;
220         TabCompData *td;
221         gchar *buf;
222         gchar *ptr;
223
224         td = g_object_get_data(G_OBJECT(widget), "tab_completion_data");
225         if (!td) return;
226
227         ptr = td->dir_path + strlen(td->dir_path) - 1;
228         buf = g_strconcat(td->dir_path, (ptr[0] == '/') ? "" : "/", name, NULL);
229         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
230         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
231         g_free(buf);
232
233         tab_completion_emit_tab_signal(td);
234 }
235
236 static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
237 {
238         TabCompData *td = data;
239         gint height;
240         PangoLayout *layout;
241         PangoRectangle strong_pos, weak_pos;
242         gint length;
243         gint xoffset, yoffset;
244         GtkRequisition req;
245         GdkScreen *screen;
246         gint monitor_num;
247         GdkRectangle monitor;
248
249         gdk_window_get_origin(td->entry->window, x, y);
250
251         screen = gtk_widget_get_screen(GTK_WIDGET(menu));
252         monitor_num = gdk_screen_get_monitor_at_window(screen, td->entry->window);
253         gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
254
255         gtk_widget_size_request(GTK_WIDGET(menu), &req);
256
257         length = strlen(gtk_entry_get_text(GTK_ENTRY(td->entry)));
258         gtk_entry_get_layout_offsets(GTK_ENTRY(td->entry), &xoffset, &yoffset);
259
260         layout = gtk_entry_get_layout(GTK_ENTRY(td->entry));
261         pango_layout_get_cursor_pos(layout, length, &strong_pos, &weak_pos);
262
263         *x += strong_pos.x / PANGO_SCALE + xoffset;
264
265         height = MIN(td->entry->requisition.height, td->entry->allocation.height);
266
267         if (req.height > monitor.y + monitor.height - *y - height &&
268             *y - monitor.y >  monitor.y + monitor.height - *y)
269                 {
270                 height = MIN(*y - monitor.y, req.height);
271                 gtk_widget_set_size_request(GTK_WIDGET(menu), -1, height);
272                 *y -= height;
273                 }
274         else
275                 {
276                 *y += height;
277                 }
278 }
279
280 static void tab_completion_popup_list(TabCompData *td, GList *list)
281 {
282         GtkWidget *menu;
283         GList *work;
284         GdkEvent *event;
285         guint32 etime;
286         gint ebutton;
287         gint count = 0;
288
289         if (!list) return;
290
291 #if 0
292         /*
293          * well, the menu would be too long anyway...
294          * (listing /dev causes gtk+ window allocation errors, -> too big a window)
295          * this is why menu popups are disabled, this really should be a popup scrollable listview.
296          */
297         if (g_list_length(list) > 200) return;
298 #endif
299
300         menu = popup_menu_short_lived();
301
302         work = list;
303         while (work && count < TAB_COMP_POPUP_MAX)
304                 {
305                 gchar *name = work->data;
306                 GtkWidget *item;
307
308                 item = menu_item_add_simple(menu, name, G_CALLBACK(tab_completion_popup_cb), name);
309                 g_object_set_data(G_OBJECT(item), "tab_completion_data", td);
310
311                 work = work->next;
312                 count++;
313                 }
314
315         g_signal_connect(G_OBJECT(menu), "key_press_event",
316                          G_CALLBACK(tab_completion_popup_key_press), td);
317
318         /* peek at the current event to get the time, etc. */
319         event = gtk_get_current_event();
320
321         if (event && event->type == GDK_BUTTON_RELEASE)
322                 {
323                 ebutton = event->button.button;
324                 }
325         else
326                 {
327                 ebutton = 0;
328                 }
329
330         if (event)
331                 {
332                 etime = gdk_event_get_time(event);
333                 gdk_event_free(event);
334                 }
335         else
336                 {
337                 etime = 0;
338                 }
339
340         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
341                        tab_completion_popup_pos_cb, td, ebutton, etime);
342 }
343
344 #ifndef CASE_SORT
345 #define CASE_SORT strcmp
346 #endif
347
348 static gint simple_sort(gconstpointer a, gconstpointer b)
349 {
350         return CASE_SORT((gchar *)a, (gchar *)b);
351 }
352
353 #endif
354
355 static gint tab_completion_do(TabCompData *td)
356 {
357         const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry));
358         const gchar *entry_file;
359         gchar *entry_dir;
360         gchar *ptr;
361         gint home_exp = FALSE;
362
363         /* home dir expansion */
364         if (entry_text[0] == '~')
365                 {
366                 entry_dir = g_strconcat(homedir(), entry_text + 1, NULL);
367                 home_exp = TRUE;
368                 }
369         else
370                 {
371                 entry_dir = g_strdup(entry_text);
372                 }
373
374         entry_file = filename_from_path(entry_text);
375
376         if (isfile(entry_dir))
377                 {
378                 if (home_exp)
379                         {
380                         gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
381                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
382                         }
383                 g_free(entry_dir);
384                 return home_exp;
385                 }
386         if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
387                 {
388                 ptr = entry_dir + strlen(entry_dir) - 1;
389                 if (ptr[0] == '/')
390                         {
391                         if (home_exp)
392                                 {
393                                 gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
394                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
395                                 }
396
397                         tab_completion_read_dir(td, entry_dir);
398                         td->file_list = g_list_sort(td->file_list, simple_sort);
399                         if (td->file_list && !td->file_list->next)
400                                 {
401                                 gchar *buf;
402                                 const gchar *file;
403
404                                 file = td->file_list->data;
405                                 buf = g_strconcat(entry_dir, file, NULL);
406                                 if (isdir(buf))
407                                         {
408                                         g_free(buf);
409                                         buf = g_strconcat(entry_dir, file, "/", NULL);
410                                         }
411                                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
412                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
413                                 g_free(buf);
414                                 }
415
416 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
417
418                         else
419                                 {
420                                 tab_completion_popup_list(td, td->file_list);
421                                 }
422 #endif
423
424                         g_free(entry_dir);
425                         return home_exp;
426                         }
427                 else
428                         {
429                         gchar *buf = g_strconcat(entry_dir, "/", NULL);
430                         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
431                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
432                         g_free(buf);
433                         g_free(entry_dir);
434                         return TRUE;
435                         }
436                 }
437
438         ptr = (gchar *)filename_from_path(entry_dir);
439         if (ptr > entry_dir) ptr--;
440         ptr[0] = '\0';
441
442         if (strlen(entry_dir) == 0)
443                 {
444                 g_free(entry_dir);
445                 entry_dir = g_strdup("/");
446                 }
447
448         if (isdir(entry_dir))
449                 {
450                 GList *list;
451                 GList *poss = NULL;
452                 gint l = strlen(entry_file);
453
454                 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
455                         {
456                         tab_completion_read_dir(td, entry_dir);
457                         }
458
459                 if (strcmp(entry_dir, "/") == 0) entry_dir[0] = '\0';
460
461                 list = td->file_list;
462                 while(list)
463                         {
464                         gchar *file = list->data;
465                         if (strncmp(entry_file, file, l) == 0)
466                                 {
467                                 poss = g_list_prepend(poss, file);
468                                 }
469                         list = list->next;
470                         }
471
472                 if (poss)
473                         {
474                         if (!poss->next)
475                                 {
476                                 gchar *file = poss->data;
477                                 gchar *buf;
478
479                                 buf = g_strconcat(entry_dir, "/", file, NULL);
480
481                                 if (isdir(buf))
482                                         {
483                                         g_free(buf);
484                                         buf = g_strconcat(entry_dir, "/", file, "/", NULL);
485                                         }
486                                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
487                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
488                                 g_free(buf);
489                                 g_list_free(poss);
490                                 g_free(entry_dir);
491                                 return TRUE;
492                                 }
493                         else
494                                 {
495                                 gint c = strlen(entry_file);
496                                 gint done = FALSE;
497                                 gchar *test_file = poss->data;
498
499                                 while (!done)
500                                         {
501                                         list = poss;
502                                         if (!list) done = TRUE;
503                                         while(list && !done)
504                                                 {
505                                                 gchar *file = list->data;
506                                                 if (strlen(file) < c || strncmp(test_file, file, c) != 0)
507                                                         {
508                                                         done = TRUE;
509                                                         }
510                                                 list = list->next;
511                                                 }
512                                         c++;
513                                         }
514                                 c -= 2;
515                                 if (c > 0)
516                                         {
517                                         gchar *file;
518                                         gchar *buf;
519                                         file = g_strdup(test_file);
520                                         file[c] = '\0';
521                                         buf = g_strconcat(entry_dir, "/", file, NULL);
522                                         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
523                                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
524
525 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
526
527                                         poss = g_list_sort(poss, simple_sort);
528                                         tab_completion_popup_list(td, poss);
529
530 #endif
531
532                                         g_free(file);
533                                         g_free(buf);
534                                         g_list_free(poss);
535                                         g_free(entry_dir);
536                                         return TRUE;
537                                         }
538                                 }
539                         g_list_free(poss);
540                         }
541                 }
542
543         g_free(entry_dir);
544
545         return FALSE;
546 }
547
548 static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
549 {
550         TabCompData *td = data;
551         gint stop_signal = FALSE;
552
553         switch (event->keyval)
554                 {
555                 case GDK_Tab:
556                         if (!(event->state & GDK_CONTROL_MASK))
557                                 {
558                                 if (tab_completion_do(td))
559                                         {
560                                         tab_completion_emit_tab_signal(td);
561                                         }
562                                 stop_signal = TRUE;
563                                 }
564                         break;
565                 case GDK_Return: case GDK_KP_Enter:
566                         if (td->fd_button &&
567                             (event->state & GDK_CONTROL_MASK))
568                                 {
569                                 tab_completion_select_show(td);
570                                 stop_signal = TRUE;
571                                 }
572                         else if (tab_completion_emit_enter_signal(td))
573                                 {
574                                 stop_signal = TRUE;
575                                 }
576                         break;
577                 default:
578                         break;
579                 }
580
581         if (stop_signal) g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
582
583         return (stop_signal);
584 }
585
586 static void tab_completion_button_pressed(GtkWidget *widget, gpointer data)
587 {
588         TabCompData *td;
589         GtkWidget *entry = data;
590
591         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
592
593         if (!td) return;
594
595         if (!GTK_WIDGET_HAS_FOCUS(entry))
596                 {
597                 gtk_widget_grab_focus(entry);
598                 }
599
600         if (tab_completion_do(td))
601                 {
602                 tab_completion_emit_tab_signal(td);
603                 }
604 }
605
606 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data)
607 {
608         GtkWidget *parent = data;
609
610         if (allocation->height > parent->allocation.height)
611                 {
612                 GtkAllocation button_allocation;
613
614                 button_allocation = button->allocation;
615                 button_allocation.height = parent->allocation.height;
616                 button_allocation.y = parent->allocation.y +
617                         (parent->allocation.height - parent->allocation.height) / 2;
618                 gtk_widget_size_allocate(button, &button_allocation);
619                 }
620 }
621
622 static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWidget *parent)
623 {
624         GtkWidget *button;
625         GtkWidget *icon;
626         GdkPixbuf *pixbuf;
627
628         button = gtk_button_new();
629         GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
630         g_signal_connect(G_OBJECT(button), "size_allocate",
631                          G_CALLBACK(tab_completion_button_size_allocate), parent);
632         g_signal_connect(G_OBJECT(button), "clicked",
633                          G_CALLBACK(tab_completion_button_pressed), entry);
634
635         pixbuf = gdk_pixbuf_new_from_inline(-1, icon_tabcomp, FALSE, NULL);
636         icon = gtk_image_new_from_pixbuf(pixbuf);
637         gdk_pixbuf_unref(pixbuf);
638         gtk_container_add(GTK_CONTAINER(button), icon);
639         gtk_widget_show(icon);
640
641         return button;
642 }
643
644 /*
645  *----------------------------------------------------------------------------
646  * public interface
647  *----------------------------------------------------------------------------
648  */
649
650 GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
651                                            const gchar *history_key, gint max_levels,
652                                            void (*enter_func)(const gchar *, gpointer), gpointer data)
653 {
654         GtkWidget *box;
655         GtkWidget *combo;
656         GtkWidget *combo_entry;
657         GtkWidget *button;
658         GList *work;
659         TabCompData *td;
660         gint n = 0;
661
662         box = gtk_hbox_new(FALSE, 0);
663
664         combo = gtk_combo_box_entry_new_text();
665         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
666         gtk_widget_show(combo);
667
668         combo_entry = GTK_BIN(combo)->child;
669 #if 0
670         gtk_combo_set_case_sensitive(GTK_COMBO(combo), TRUE);
671         gtk_combo_set_use_arrows(GTK_COMBO(combo), FALSE);
672 #endif
673
674         button = tab_completion_create_complete_button(combo_entry, combo);
675         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
676         gtk_widget_show(button);
677
678         tab_completion_add_to_entry(combo_entry, enter_func, data);
679
680         td = g_object_get_data(G_OBJECT(combo_entry), "tab_completion_data");
681         if (!td) return NULL; /* this should never happen! */
682
683         td->combo = combo;
684         td->has_history = TRUE;
685         td->history_key = g_strdup(history_key);
686         td->history_levels = max_levels;
687
688         work = history_list_get_by_key(td->history_key);
689
690         work = history_list_get_by_key(history_key);
691         while (work)
692                 {
693                 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (gchar *)work->data);
694                 work = work->next;
695                 n++;
696                 }
697
698         if (text)
699                 {
700                 gtk_entry_set_text(GTK_ENTRY(combo_entry), text);
701                 }
702         else if (n > 0)
703                 {
704                 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
705                 }
706
707         if (entry) *entry = combo_entry;
708         return box;
709 }
710
711 const gchar *tab_completion_set_to_last_history(GtkWidget *entry)
712 {
713         TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
714         const gchar *buf;
715
716         if (!td || !td->has_history) return NULL;
717
718         buf = history_list_find_last_path_by_key(td->history_key);
719         if (buf)
720                 {
721                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
722                 }
723
724         return buf;
725 }
726
727 void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
728 {
729         TabCompData *td;
730         GtkTreeModel *store;
731         GList *work;
732
733         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
734
735         if (!path) return;
736
737         if (!td || !td->has_history) return;
738
739         history_list_add_to_key(td->history_key, path, td->history_levels);
740
741         gtk_combo_box_set_active(GTK_COMBO_BOX(td->combo), -1);
742
743         store = gtk_combo_box_get_model(GTK_COMBO_BOX(td->combo));
744         gtk_list_store_clear(GTK_LIST_STORE(store));
745
746         work = history_list_get_by_key(td->history_key);
747         while (work)
748                 {
749                 gtk_combo_box_append_text(GTK_COMBO_BOX(td->combo), (gchar *)work->data);
750                 work = work->next;
751                 }
752 }
753
754 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text,
755                               void (*enter_func)(const gchar *, gpointer), gpointer data)
756 {
757         GtkWidget *hbox;
758         GtkWidget *button;
759         GtkWidget *newentry;
760
761         hbox = gtk_hbox_new(FALSE, 0);
762
763         newentry = gtk_entry_new();
764         if (text) gtk_entry_set_text(GTK_ENTRY(newentry), text);
765         gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0);
766         gtk_widget_show(newentry);
767
768         button = tab_completion_create_complete_button(newentry, newentry);
769         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
770         gtk_widget_show(button);
771
772         tab_completion_add_to_entry(newentry, enter_func, data);
773
774         if (entry) *entry = newentry;
775         return hbox;
776 }
777
778 void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gchar *, gpointer), gpointer data)
779 {
780         TabCompData *td;
781         if (!entry)
782                 {
783                 printf("Tab completion error: entry != NULL\n");
784                 return;
785                 }
786
787         td = g_new0(TabCompData, 1);
788         td->entry = entry;
789         td->dir_path = NULL;
790         td->file_list = NULL;
791         td->enter_func = enter_func;
792         td->enter_data = data;
793         td->tab_func = NULL;
794         td->tab_data = NULL;
795
796         td->has_history = FALSE;
797         td->history_key = NULL;
798         td->history_levels = 0;
799
800         g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td);
801
802         g_signal_connect(G_OBJECT(entry), "key_press_event",
803                          G_CALLBACK(tab_completion_key_pressed), td);
804         g_signal_connect(G_OBJECT(entry), "destroy",
805                          G_CALLBACK(tab_completion_destroy), td);
806 }
807
808 void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar *, gpointer), gpointer data)
809 {
810         TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
811
812         if (!td) return;
813
814         td->tab_func = tab_func;
815         td->tab_data = data;
816 }
817
818 gchar *remove_trailing_slash(const gchar *path)
819 {
820         gchar *ret;
821         gint l;
822         if (!path) return NULL;
823
824         ret = g_strdup(path);
825         l = strlen(ret);
826         if (l > 1 && ret[l - 1] == '/') ret[l - 1] = '\0';
827
828         return ret;
829 }
830
831 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data)
832 {
833         TabCompData *td = data;
834
835         td->fd = NULL;
836         file_dialog_close(fd);
837 }
838
839 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data)
840 {
841         TabCompData *td = data;
842
843         gtk_entry_set_text(GTK_ENTRY(td->entry), gtk_entry_get_text(GTK_ENTRY(fd->entry)));
844
845         tab_completion_select_cancel_cb(fd, data);
846
847         tab_completion_emit_enter_signal(td);
848 }
849
850 static void tab_completion_select_show(TabCompData *td)
851 {
852         const gchar *title;
853         const gchar *path;
854
855         if (td->fd)
856                 {
857                 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog));
858                 return;
859                 }
860
861         title = (td->fd_title) ? td->fd_title : _("Select path");
862         td->fd = file_dialog_new(title, PACKAGE, "select_path", td->entry,
863                                  tab_completion_select_cancel_cb, td);
864         file_dialog_add_button(td->fd, GTK_STOCK_OK, NULL,
865                                  tab_completion_select_ok_cb, TRUE);
866
867         generic_dialog_add_message(GENERIC_DIALOG(td->fd), NULL, title, NULL);
868
869         path = gtk_entry_get_text(GTK_ENTRY(td->entry));
870         if (strlen(path) == 0) path = NULL;
871         if (td->fd_folders_only)
872                 {
873                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, NULL, NULL);
874                 }
875         else
876                 {
877                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, "*", _("All files"));
878                 }
879
880         gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog);
881 }
882
883 static void tab_completion_select_pressed(GtkWidget *widget, gpointer data)
884 {
885         TabCompData *td = data;
886
887         tab_completion_select_show(td);
888 }
889
890 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint folders_only)
891 {
892         TabCompData *td;
893         GtkWidget *parent;
894         GtkWidget *hbox;
895
896         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
897
898         if (!td) return;
899
900         g_free(td->fd_title);
901         td->fd_title = g_strdup(title);
902         td->fd_folders_only = folders_only;
903
904         if (td->fd_button) return;
905
906         parent = (td->combo) ? td->combo : td->entry;
907
908         hbox = gtk_widget_get_parent(parent);
909         if (!GTK_IS_BOX(hbox)) return;
910
911         td->fd_button = gtk_button_new_with_label("...");
912         g_signal_connect(G_OBJECT(td->fd_button), "size_allocate",
913                          G_CALLBACK(tab_completion_button_size_allocate), parent);
914         g_signal_connect(G_OBJECT(td->fd_button), "clicked",
915                          G_CALLBACK(tab_completion_select_pressed), td);
916
917         gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0);
918
919         gtk_widget_show(td->fd_button);
920 }
921