Move miscellaneous functions to their own files (new misc.[ch]).
[geeqie.git] / src / ui_tabcomp.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #  include "config.h"
15 #endif
16 #include "intl.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <dirent.h>
24
25 #include <gdk/gdk.h>
26 #include <gtk/gtk.h>
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28
29 #include "main.h"
30 #include "ui_tabcomp.h"
31
32 #include "history_list.h"
33 #include "misc.h"       /* expand_tilde() */
34 #include "ui_fileops.h"
35 #include "ui_spinner.h"
36 #include "ui_utildlg.h"
37
38 #include <gdk/gdkkeysyms.h> /* for key values */
39
40
41 /* define this to enable a pop-up menu that shows possible matches
42  * #define TAB_COMPLETION_ENABLE_POPUP_MENU
43  */
44 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1
45 #define TAB_COMP_POPUP_MAX 500
46
47 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
48 #include "ui_menu.h"
49 #endif
50
51
52 /* ----------------------------------------------------------------
53    Tab completion routines, can be connected to any gtkentry widget
54    using the tab_completion_add_to_entry() function.
55    Use remove_trailing_slash() to strip the trailing G_DIR_SEPARATOR.
56    ----------------------------------------------------------------*/
57
58 typedef struct _TabCompData TabCompData;
59 struct _TabCompData
60 {
61         GtkWidget *entry;
62         gchar *dir_path;
63         GList *file_list;
64         void (*enter_func)(const gchar *, gpointer);
65         void (*tab_func)(const gchar *, gpointer);
66         gpointer enter_data;
67         gpointer tab_data;
68
69         GtkWidget *combo;
70         gint has_history;
71         gchar *history_key;
72         gint history_levels;
73
74         FileDialog *fd;
75         gchar *fd_title;
76         gint fd_folders_only;
77         GtkWidget *fd_button;
78 };
79
80
81 static void tab_completion_select_show(TabCompData *td);
82
83 static void tab_completion_free_list(TabCompData *td)
84 {
85         GList *list;
86
87         g_free(td->dir_path);
88         td->dir_path = NULL;
89
90         list = td->file_list;
91
92         while (list)
93                 {
94                 g_free(list->data);
95                 list = list->next;
96                 }
97
98         g_list_free(td->file_list);
99         td->file_list = NULL;
100 }
101
102 static void tab_completion_read_dir(TabCompData *td, const gchar *path)
103 {
104         DIR *dp;
105         struct dirent *dir;
106         GList *list = NULL;
107         gchar *pathl;
108
109         tab_completion_free_list(td);
110
111         pathl = path_from_utf8(path);
112         dp = opendir(pathl);
113         g_free(pathl);
114         if (!dp)
115                 {
116                 /* dir not found */
117                 return;
118                 }
119         while ((dir = readdir(dp)) != NULL)
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         closedir(dp);
128
129         td->dir_path = g_strdup(path);
130         td->file_list = list;
131 }
132
133 static void tab_completion_destroy(GtkWidget *widget, gpointer data)
134 {
135         TabCompData *td = data;
136
137         tab_completion_free_list(td);
138         g_free(td->history_key);
139
140         if (td->fd) file_dialog_close(td->fd);
141         g_free(td->fd_title);
142
143         g_free(td);
144 }
145
146 static gchar *tab_completion_get_text(TabCompData *td)
147 {
148         gchar *text;
149
150         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
151
152         if (text[0] == '~')
153                 {
154                 gchar *t = text;
155                 text = expand_tilde(text);
156                 g_free(t);
157                 }
158
159         return text;
160 }
161
162 static gint tab_completion_emit_enter_signal(TabCompData *td)
163 {
164         gchar *text;
165         if (!td->enter_func) return FALSE;
166
167         text = tab_completion_get_text(td);
168         td->enter_func(text, td->enter_data);
169         g_free(text);
170
171         return TRUE;
172 }
173
174 static void tab_completion_emit_tab_signal(TabCompData *td)
175 {
176         gchar *text;
177         if (!td->tab_func) return;
178
179         text = tab_completion_get_text(td);
180         td->tab_func(text, td->tab_data);
181         g_free(text);
182 }
183
184 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
185
186 static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
187 {
188         TabCompData *td = data;
189
190         if (event->keyval == GDK_Tab ||
191             event->keyval == GDK_BackSpace ||
192             (event->keyval >= 0x20 && event->keyval <= 0xFF) )
193                 {
194                 if (event->keyval >= 0x20 && event->keyval <= 0xFF)
195                         {
196                         gchar buf[2];
197                         gint p = -1;
198
199                         buf[0] = event->keyval;
200                         buf[1] = '\0';
201                         gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p);
202                         gtk_editable_set_position(GTK_EDITABLE(td->entry), -1);
203                         }
204
205                 /*close the menu */
206                 gtk_menu_popdown(GTK_MENU(widget));
207                 /* doing this does not emit the "selection done" signal, unref it ourselves */
208                 gtk_widget_unref(widget);
209
210                 return TRUE;
211                 }
212
213         return FALSE;
214 }
215
216 static void tab_completion_popup_cb(GtkWidget *widget, gpointer data)
217 {
218         gchar *name = data;
219         TabCompData *td;
220         gchar *buf;
221
222         td = g_object_get_data(G_OBJECT(widget), "tab_completion_data");
223         if (!td) return;
224
225         buf = g_build_filename(td->dir_path, name, NULL);
226         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
227         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
228         g_free(buf);
229
230         tab_completion_emit_tab_signal(td);
231 }
232
233 static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
234 {
235         TabCompData *td = data;
236         gint height;
237         PangoLayout *layout;
238         PangoRectangle strong_pos, weak_pos;
239         gint length;
240         gint xoffset, yoffset;
241         GtkRequisition req;
242         GdkScreen *screen;
243         gint monitor_num;
244         GdkRectangle monitor;
245
246         gdk_window_get_origin(td->entry->window, x, y);
247
248         screen = gtk_widget_get_screen(GTK_WIDGET(menu));
249         monitor_num = gdk_screen_get_monitor_at_window(screen, td->entry->window);
250         gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
251
252         gtk_widget_size_request(GTK_WIDGET(menu), &req);
253
254         length = strlen(gtk_entry_get_text(GTK_ENTRY(td->entry)));
255         gtk_entry_get_layout_offsets(GTK_ENTRY(td->entry), &xoffset, &yoffset);
256
257         layout = gtk_entry_get_layout(GTK_ENTRY(td->entry));
258         pango_layout_get_cursor_pos(layout, length, &strong_pos, &weak_pos);
259
260         *x += strong_pos.x / PANGO_SCALE + xoffset;
261
262         height = MIN(td->entry->requisition.height, td->entry->allocation.height);
263
264         if (req.height > monitor.y + monitor.height - *y - height &&
265             *y - monitor.y >  monitor.y + monitor.height - *y)
266                 {
267                 height = MIN(*y - monitor.y, req.height);
268                 gtk_widget_set_size_request(GTK_WIDGET(menu), -1, height);
269                 *y -= height;
270                 }
271         else
272                 {
273                 *y += height;
274                 }
275 }
276
277 static void tab_completion_popup_list(TabCompData *td, GList *list)
278 {
279         GtkWidget *menu;
280         GList *work;
281         GdkEvent *event;
282         guint32 etime;
283         gint ebutton;
284         gint count = 0;
285
286         if (!list) return;
287
288 #if 0
289         /*
290          * well, the menu would be too long anyway...
291          * (listing /dev causes gtk+ window allocation errors, -> too big a window)
292          * this is why menu popups are disabled, this really should be a popup scrollable listview.
293          */
294         if (g_list_length(list) > 200) return;
295 #endif
296
297         menu = popup_menu_short_lived();
298
299         work = list;
300         while (work && count < TAB_COMP_POPUP_MAX)
301                 {
302                 gchar *name = work->data;
303                 GtkWidget *item;
304
305                 item = menu_item_add_simple(menu, name, G_CALLBACK(tab_completion_popup_cb), name);
306                 g_object_set_data(G_OBJECT(item), "tab_completion_data", td);
307
308                 work = work->next;
309                 count++;
310                 }
311
312         g_signal_connect(G_OBJECT(menu), "key_press_event",
313                          G_CALLBACK(tab_completion_popup_key_press), td);
314
315         /* peek at the current event to get the time, etc. */
316         event = gtk_get_current_event();
317
318         if (event && event->type == GDK_BUTTON_RELEASE)
319                 {
320                 ebutton = event->button.button;
321                 }
322         else
323                 {
324                 ebutton = 0;
325                 }
326
327         if (event)
328                 {
329                 etime = gdk_event_get_time(event);
330                 gdk_event_free(event);
331                 }
332         else
333                 {
334                 etime = 0;
335                 }
336
337         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
338                        tab_completion_popup_pos_cb, td, ebutton, etime);
339 }
340
341 #ifndef CASE_SORT
342 #define CASE_SORT strcmp
343 #endif
344
345 static gint simple_sort(gconstpointer a, gconstpointer b)
346 {
347         return CASE_SORT((gchar *)a, (gchar *)b);
348 }
349
350 #endif
351
352 static gint tab_completion_do(TabCompData *td)
353 {
354         const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry));
355         const gchar *entry_file;
356         gchar *entry_dir;
357         gchar *ptr;
358         gint home_exp = FALSE;
359
360         /* home dir expansion */
361         if (entry_text[0] == '~')
362                 {
363                 entry_dir = expand_tilde(entry_text);
364                 home_exp = TRUE;
365                 }
366         else
367                 {
368                 entry_dir = g_strdup(entry_text);
369                 }
370
371         entry_file = filename_from_path(entry_text);
372
373         if (isfile(entry_dir))
374                 {
375                 if (home_exp)
376                         {
377                         gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
378                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
379                         }
380                 g_free(entry_dir);
381                 return home_exp;
382                 }
383
384         if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
385                 {
386                 ptr = entry_dir + strlen(entry_dir) - 1;
387                 if (ptr[0] == G_DIR_SEPARATOR)
388                         {
389                         if (home_exp)
390                                 {
391                                 gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
392                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
393                                 }
394
395                         tab_completion_read_dir(td, entry_dir);
396                         td->file_list = g_list_sort(td->file_list, simple_sort);
397                         if (td->file_list && !td->file_list->next)
398                                 {
399                                 gchar *buf;
400                                 const gchar *file;
401
402                                 file = td->file_list->data;
403                                 buf = g_build_filename(entry_dir, file, NULL);
404                                 if (isdir(buf))
405                                         {
406                                         gchar *tmp = g_strconcat(buf, G_DIR_SEPARATOR_S, NULL);
407                                         g_free(buf);
408                                         buf = tmp;
409                                         }
410                                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
411                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
412                                 g_free(buf);
413                                 }
414
415 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
416
417                         else
418                                 {
419                                 tab_completion_popup_list(td, td->file_list);
420                                 }
421 #endif
422
423                         g_free(entry_dir);
424                         return home_exp;
425                         }
426                 else
427                         {
428                         gchar *buf = g_strconcat(entry_dir, G_DIR_SEPARATOR_S, NULL);
429                         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
430                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
431                         g_free(buf);
432                         g_free(entry_dir);
433                         return TRUE;
434                         }
435                 }
436
437         ptr = (gchar *)filename_from_path(entry_dir);
438         if (ptr > entry_dir) ptr--;
439         ptr[0] = '\0';
440
441         if (strlen(entry_dir) == 0)
442                 {
443                 g_free(entry_dir);
444                 entry_dir = g_strdup(G_DIR_SEPARATOR_S); /* FIXME: win32 */
445                 }
446
447         if (isdir(entry_dir))
448                 {
449                 GList *list;
450                 GList *poss = NULL;
451                 gint l = strlen(entry_file);
452
453                 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
454                         {
455                         tab_completion_read_dir(td, entry_dir);
456                         }
457
458                 if (strcmp(entry_dir, G_DIR_SEPARATOR_S) == 0) entry_dir[0] = '\0'; /* FIXME: win32 */
459
460                 list = td->file_list;
461                 while (list)
462                         {
463                         gchar *file = list->data;
464                         if (strncmp(entry_file, file, l) == 0)
465                                 {
466                                 poss = g_list_prepend(poss, file);
467                                 }
468                         list = list->next;
469                         }
470
471                 if (poss)
472                         {
473                         if (!poss->next)
474                                 {
475                                 gchar *file = poss->data;
476                                 gchar *buf;
477
478                                 buf = g_build_filename(entry_dir, file, NULL);
479
480                                 if (isdir(buf))
481                                         {
482                                         gchar *tmp = g_strconcat(buf, G_DIR_SEPARATOR_S, NULL);
483                                         g_free(buf);
484                                         buf = tmp;
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                                 gsize 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_build_filename(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                 log_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         gint l;
821
822         if (!path) return NULL;
823
824         l = strlen(path);
825         while (l > 1 && path[l - 1] == G_DIR_SEPARATOR) l--;
826
827         return g_strndup(path, l);
828 }
829
830 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data)
831 {
832         TabCompData *td = data;
833
834         td->fd = NULL;
835         file_dialog_close(fd);
836 }
837
838 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data)
839 {
840         TabCompData *td = data;
841
842         gtk_entry_set_text(GTK_ENTRY(td->entry), gtk_entry_get_text(GTK_ENTRY(fd->entry)));
843
844         tab_completion_select_cancel_cb(fd, data);
845
846         tab_completion_emit_enter_signal(td);
847 }
848
849 static void tab_completion_select_show(TabCompData *td)
850 {
851         const gchar *title;
852         const gchar *path;
853
854         if (td->fd)
855                 {
856                 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog));
857                 return;
858                 }
859
860         title = (td->fd_title) ? td->fd_title : _("Select path");
861         td->fd = file_dialog_new(title, GQ_WMCLASS, "select_path", td->entry,
862                                  tab_completion_select_cancel_cb, td);
863         file_dialog_add_button(td->fd, GTK_STOCK_OK, NULL,
864                                  tab_completion_select_ok_cb, TRUE);
865
866         generic_dialog_add_message(GENERIC_DIALOG(td->fd), NULL, title, NULL);
867
868         path = gtk_entry_get_text(GTK_ENTRY(td->entry));
869         if (strlen(path) == 0) path = NULL;
870         if (td->fd_folders_only)
871                 {
872                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, NULL, NULL);
873                 }
874         else
875                 {
876                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, "*", _("All files"));
877                 }
878
879         gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog);
880 }
881
882 static void tab_completion_select_pressed(GtkWidget *widget, gpointer data)
883 {
884         TabCompData *td = data;
885
886         tab_completion_select_show(td);
887 }
888
889 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint folders_only)
890 {
891         TabCompData *td;
892         GtkWidget *parent;
893         GtkWidget *hbox;
894
895         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
896
897         if (!td) return;
898
899         g_free(td->fd_title);
900         td->fd_title = g_strdup(title);
901         td->fd_folders_only = folders_only;
902
903         if (td->fd_button) return;
904
905         parent = (td->combo) ? td->combo : td->entry;
906
907         hbox = gtk_widget_get_parent(parent);
908         if (!GTK_IS_BOX(hbox)) return;
909
910         td->fd_button = gtk_button_new_with_label("...");
911         g_signal_connect(G_OBJECT(td->fd_button), "size_allocate",
912                          G_CALLBACK(tab_completion_button_size_allocate), parent);
913         g_signal_connect(G_OBJECT(td->fd_button), "clicked",
914                          G_CALLBACK(tab_completion_select_pressed), td);
915
916         gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0);
917
918         gtk_widget_show(td->fd_button);
919 }