Improve remove_trailing_slash() so it allocates no more than
[geeqie.git] / src / ui_tabcomp.c
1 /*
2  * (SLIK) SimpLIstic sKin functions
3  * (C) 2006 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 "gqview.h"
29 #include "ui_tabcomp.h"
30
31 #include "ui_bookmark.h"
32 #include "ui_fileops.h"
33 #include "ui_spinner.h"
34 #include "ui_utildlg.h"
35
36 #include <gdk/gdkkeysyms.h> /* for key values */
37
38
39 /* define this to enable a pop-up menu that shows possible matches
40  * #define TAB_COMPLETION_ENABLE_POPUP_MENU
41  */
42 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1
43 #define TAB_COMP_POPUP_MAX 500
44
45 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
46 #include "ui_menu.h"
47 #endif
48
49
50 /* ----------------------------------------------------------------
51    Tab completion routines, can be connected to any gtkentry widget
52    using the tab_completion_add_to_entry() function.
53    Use remove_trailing_slash() to strip the trailing '/'.
54    ----------------------------------------------------------------*/
55
56 typedef struct _TabCompData TabCompData;
57 struct _TabCompData
58 {
59         GtkWidget *entry;
60         gchar *dir_path;
61         GList *file_list;
62         void (*enter_func)(const gchar *, gpointer);
63         void (*tab_func)(const gchar *, gpointer);
64         gpointer enter_data;
65         gpointer tab_data;
66
67         GtkWidget *combo;
68         gint has_history;
69         gchar *history_key;
70         gint history_levels;
71
72         FileDialog *fd;
73         gchar *fd_title;
74         gint fd_folders_only;
75         GtkWidget *fd_button;
76 };
77
78
79 static void tab_completion_select_show(TabCompData *td);
80
81 static void tab_completion_free_list(TabCompData *td)
82 {
83         GList *list;
84
85         g_free(td->dir_path);
86         td->dir_path = NULL;
87
88         list = td->file_list;
89
90         while(list)
91                 {
92                 g_free(list->data);
93                 list = list->next;
94                 }
95
96         g_list_free(td->file_list);
97         td->file_list = NULL;
98 }
99
100 static void tab_completion_read_dir(TabCompData *td, const gchar *path)
101 {
102         DIR *dp;
103         struct dirent *dir;
104         GList *list = NULL;
105         gchar *pathl;
106
107         tab_completion_free_list(td);
108
109         pathl = path_from_utf8(path);
110         dp = opendir(pathl);
111         g_free(pathl);
112         if (!dp)
113                 {
114                 /* dir not found */
115                 return;
116                 }
117         while ((dir = readdir(dp)) != NULL)
118                 {
119                 gchar *name = dir->d_name;
120                 if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
121                         {
122                         list = g_list_prepend(list, path_to_utf8(name));
123                         }
124                 }
125         closedir(dp);
126
127         td->dir_path = g_strdup(path);
128         td->file_list = list;
129 }
130
131 static void tab_completion_destroy(GtkWidget *widget, gpointer data)
132 {
133         TabCompData *td = data;
134
135         tab_completion_free_list(td);
136         g_free(td->history_key);
137
138         if (td->fd) file_dialog_close(td->fd);
139         g_free(td->fd_title);
140
141         g_free(td);
142 }
143
144 static gint tab_completion_emit_enter_signal(TabCompData *td)
145 {
146         gchar *text;
147         if (!td->enter_func) return FALSE;
148
149         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
150
151         if (text[0] == '~')
152                 {
153                 gchar *t = text;
154                 text = g_strconcat(homedir(), t + 1, NULL);
155                 g_free(t);
156                 }
157
158         td->enter_func(text, td->enter_data);
159         g_free(text);
160
161         return TRUE;
162 }
163
164 static void tab_completion_emit_tab_signal(TabCompData *td)
165 {
166         gchar *text;
167         if (!td->tab_func) return;
168
169         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
170
171         if (text[0] == '~')
172                 {
173                 gchar *t = text;
174                 text = g_strconcat(homedir(), t + 1, NULL);
175                 g_free(t);
176                 }
177
178         td->tab_func(text, td->tab_data);
179         g_free(text);
180 }
181
182 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
183
184 static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
185 {
186         TabCompData *td = data;
187
188         if (event->keyval == GDK_Tab ||
189             event->keyval == GDK_BackSpace ||
190             (event->keyval >= 0x20 && event->keyval <= 0xFF) )
191                 {
192                 if (event->keyval >= 0x20 && event->keyval <= 0xFF)
193                         {
194                         gchar buf[2];
195                         gint p = -1;
196
197                         buf[0] = event->keyval;
198                         buf[1] = '\0';
199                         gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p);
200                         gtk_editable_set_position(GTK_EDITABLE(td->entry), -1);
201                         }
202
203                 /*close the menu */
204                 gtk_menu_popdown(GTK_MENU(widget));
205                 /* doing this does not emit the "selection done" signal, unref it ourselves */
206                 gtk_widget_unref(widget);
207                         
208                 return TRUE;
209                 }
210
211         return FALSE;
212 }
213
214 static void tab_completion_popup_cb(GtkWidget *widget, gpointer data)
215 {
216         gchar *name = data;
217         TabCompData *td;
218         gchar *buf;
219         gchar *ptr;
220
221         td = g_object_get_data(G_OBJECT(widget), "tab_completion_data");
222         if (!td) return;
223
224         ptr = td->dir_path + strlen(td->dir_path) - 1;
225         buf = g_strconcat(td->dir_path, (ptr[0] == '/') ? "" : "/", 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 = g_strconcat(homedir(), entry_text + 1, NULL);
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         if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
384                 {
385                 ptr = entry_dir + strlen(entry_dir) - 1;
386                 if (ptr[0] == '/')
387                         {
388                         if (home_exp)
389                                 {
390                                 gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
391                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
392                                 }
393
394                         tab_completion_read_dir(td, entry_dir);
395                         td->file_list = g_list_sort(td->file_list, simple_sort);
396                         if (td->file_list && !td->file_list->next)
397                                 {
398                                 gchar *buf;
399                                 const gchar *file;
400
401                                 file = td->file_list->data;
402                                 buf = g_strconcat(entry_dir, file, NULL);
403                                 if (isdir(buf))
404                                         {
405                                         g_free(buf);
406                                         buf = g_strconcat(entry_dir, file, "/", NULL);
407                                         }
408                                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
409                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
410                                 g_free(buf);
411                                 }
412
413 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
414
415                         else
416                                 {
417                                 tab_completion_popup_list(td, td->file_list);
418                                 }
419 #endif
420
421                         g_free(entry_dir);
422                         return home_exp;
423                         }
424                 else
425                         {
426                         gchar *buf = g_strconcat(entry_dir, "/", NULL);
427                         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
428                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
429                         g_free(buf);
430                         g_free(entry_dir);
431                         return TRUE;
432                         }
433                 }
434
435         ptr = (gchar *)filename_from_path(entry_dir);
436         if (ptr > entry_dir) ptr--;
437         ptr[0] = '\0';
438
439         if (strlen(entry_dir) == 0)
440                 {
441                 g_free(entry_dir);
442                 entry_dir = g_strdup("/");
443                 }
444
445         if (isdir(entry_dir))
446                 {
447                 GList *list;
448                 GList *poss = NULL;
449                 gint l = strlen(entry_file);
450
451                 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
452                         {
453                         tab_completion_read_dir(td, entry_dir);
454                         }
455
456                 if (strcmp(entry_dir, "/") == 0) entry_dir[0] = '\0';
457
458                 list = td->file_list;
459                 while(list)
460                         {
461                         gchar *file = list->data;
462                         if (strncmp(entry_file, file, l) == 0)
463                                 {
464                                 poss = g_list_prepend(poss, file);
465                                 }
466                         list = list->next;
467                         }
468
469                 if (poss)
470                         {
471                         if (!poss->next)
472                                 {
473                                 gchar *file = poss->data;
474                                 gchar *buf;
475
476                                 buf = g_strconcat(entry_dir, "/", file, NULL);
477
478                                 if (isdir(buf))
479                                         {
480                                         g_free(buf);
481                                         buf = g_strconcat(entry_dir, "/", file, "/", NULL);
482                                         }
483                                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
484                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
485                                 g_free(buf);
486                                 g_list_free(poss);
487                                 g_free(entry_dir);
488                                 return TRUE;
489                                 }
490                         else
491                                 {
492                                 gint c = strlen(entry_file);
493                                 gint done = FALSE;
494                                 gchar *test_file = poss->data;
495
496                                 while (!done)
497                                         {
498                                         list = poss;
499                                         if (!list) done = TRUE;
500                                         while(list && !done)
501                                                 {
502                                                 gchar *file = list->data;
503                                                 if (strlen(file) < c || strncmp(test_file, file, c) != 0)
504                                                         {
505                                                         done = TRUE;
506                                                         }
507                                                 list = list->next;
508                                                 }
509                                         c++;
510                                         }
511                                 c -= 2;
512                                 if (c > 0)
513                                         {
514                                         gchar *file;
515                                         gchar *buf;
516                                         file = g_strdup(test_file);
517                                         file[c] = '\0';
518                                         buf = g_strconcat(entry_dir, "/", file, NULL);
519                                         gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
520                                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
521
522 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
523
524                                         poss = g_list_sort(poss, simple_sort);
525                                         tab_completion_popup_list(td, poss);
526
527 #endif
528
529                                         g_free(file);
530                                         g_free(buf);
531                                         g_list_free(poss);
532                                         g_free(entry_dir);
533                                         return TRUE;
534                                         }
535                                 }
536                         g_list_free(poss);
537                         }
538                 }
539
540         g_free(entry_dir);
541
542         return FALSE;
543 }
544
545 static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
546 {
547         TabCompData *td = data;
548         gint stop_signal = FALSE;
549
550         switch (event->keyval)
551                 {
552                 case GDK_Tab:
553                         if (!(event->state & GDK_CONTROL_MASK))
554                                 {
555                                 if (tab_completion_do(td))
556                                         {
557                                         tab_completion_emit_tab_signal(td);
558                                         }
559                                 stop_signal = TRUE;
560                                 }
561                         break;
562                 case GDK_Return: case GDK_KP_Enter:
563                         if (td->fd_button &&
564                             (event->state & GDK_CONTROL_MASK))
565                                 {
566                                 tab_completion_select_show(td);
567                                 stop_signal = TRUE;
568                                 }
569                         else if (tab_completion_emit_enter_signal(td))
570                                 {
571                                 stop_signal = TRUE;
572                                 }
573                         break;
574                 default:
575                         break;
576                 }
577
578         if (stop_signal) g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
579
580         return (stop_signal);
581 }
582
583 static void tab_completion_button_pressed(GtkWidget *widget, gpointer data)
584 {
585         TabCompData *td;
586         GtkWidget *entry = data;
587
588         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
589
590         if (!td) return;
591
592         if (!GTK_WIDGET_HAS_FOCUS(entry))
593                 {
594                 gtk_widget_grab_focus(entry);
595                 }
596
597         if (tab_completion_do(td))
598                 {
599                 tab_completion_emit_tab_signal(td);
600                 }
601 }
602
603 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data)
604 {
605         GtkWidget *parent = data;
606
607         if (allocation->height > parent->allocation.height)
608                 {
609                 GtkAllocation button_allocation;
610
611                 button_allocation = button->allocation;
612                 button_allocation.height = parent->allocation.height;
613                 button_allocation.y = parent->allocation.y +
614                         (parent->allocation.height - parent->allocation.height) / 2;
615                 gtk_widget_size_allocate(button, &button_allocation);
616                 }
617 }
618
619 static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWidget *parent)
620 {
621         GtkWidget *button;
622         GtkWidget *icon;
623         GdkPixbuf *pixbuf;
624
625         button = gtk_button_new();
626         GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
627         g_signal_connect(G_OBJECT(button), "size_allocate",
628                          G_CALLBACK(tab_completion_button_size_allocate), parent);
629         g_signal_connect(G_OBJECT(button), "clicked",
630                          G_CALLBACK(tab_completion_button_pressed), entry);
631
632         pixbuf = gdk_pixbuf_new_from_inline(-1, icon_tabcomp, FALSE, NULL);
633         icon = gtk_image_new_from_pixbuf(pixbuf);
634         gdk_pixbuf_unref(pixbuf);
635         gtk_container_add(GTK_CONTAINER(button), icon);
636         gtk_widget_show(icon);
637
638         return button;
639 }
640
641 /*
642  *----------------------------------------------------------------------------
643  * public interface
644  *----------------------------------------------------------------------------
645  */
646
647 GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
648                                            const gchar *history_key, gint max_levels,
649                                            void (*enter_func)(const gchar *, gpointer), gpointer data)
650 {
651         GtkWidget *box;
652         GtkWidget *combo;
653         GtkWidget *combo_entry;
654         GtkWidget *button;
655         GList *work;
656         TabCompData *td;
657         gint n = 0;
658
659         box = gtk_hbox_new(FALSE, 0);
660
661         combo = gtk_combo_box_entry_new_text();
662         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
663         gtk_widget_show(combo);
664
665         combo_entry = GTK_BIN(combo)->child;
666 #if 0
667         gtk_combo_set_case_sensitive(GTK_COMBO(combo), TRUE);
668         gtk_combo_set_use_arrows(GTK_COMBO(combo), FALSE);
669 #endif
670
671         button = tab_completion_create_complete_button(combo_entry, combo);
672         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
673         gtk_widget_show(button);
674
675         tab_completion_add_to_entry(combo_entry, enter_func, data);
676
677         td = g_object_get_data(G_OBJECT(combo_entry), "tab_completion_data");
678         if (!td) return NULL; /* this should never happen! */
679
680         td->combo = combo;
681         td->has_history = TRUE;
682         td->history_key = g_strdup(history_key);
683         td->history_levels = max_levels;
684
685         work = history_list_get_by_key(td->history_key);
686
687         work = history_list_get_by_key(history_key);
688         while (work)
689                 {
690                 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (gchar *)work->data);
691                 work = work->next;
692                 n++;
693                 }
694
695         if (text)
696                 {
697                 gtk_entry_set_text(GTK_ENTRY(combo_entry), text);
698                 }
699         else if (n > 0)
700                 {
701                 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
702                 }
703
704         if (entry) *entry = combo_entry;
705         return box;
706 }
707
708 const gchar *tab_completion_set_to_last_history(GtkWidget *entry)
709 {
710         TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
711         const gchar *buf;
712
713         if (!td || !td->has_history) return NULL;
714
715         buf = history_list_find_last_path_by_key(td->history_key);
716         if (buf)
717                 {
718                 gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
719                 }
720
721         return buf;
722 }
723
724 void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
725 {
726         TabCompData *td;
727         GtkTreeModel *store;
728         GList *work;
729
730         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
731
732         if (!path) return;
733
734         if (!td || !td->has_history) return;
735
736         history_list_add_to_key(td->history_key, path, td->history_levels);
737
738         gtk_combo_box_set_active(GTK_COMBO_BOX(td->combo), -1);
739
740         store = gtk_combo_box_get_model(GTK_COMBO_BOX(td->combo));
741         gtk_list_store_clear(GTK_LIST_STORE(store));
742
743         work = history_list_get_by_key(td->history_key);
744         while (work)
745                 {
746                 gtk_combo_box_append_text(GTK_COMBO_BOX(td->combo), (gchar *)work->data);
747                 work = work->next;
748                 }
749 }
750
751 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text,
752                               void (*enter_func)(const gchar *, gpointer), gpointer data)
753 {
754         GtkWidget *hbox;
755         GtkWidget *button;
756         GtkWidget *newentry;
757
758         hbox = gtk_hbox_new(FALSE, 0);
759
760         newentry = gtk_entry_new();
761         if (text) gtk_entry_set_text(GTK_ENTRY(newentry), text);
762         gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0);
763         gtk_widget_show(newentry);
764
765         button = tab_completion_create_complete_button(newentry, newentry);
766         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
767         gtk_widget_show(button);
768
769         tab_completion_add_to_entry(newentry, enter_func, data);
770
771         if (entry) *entry = newentry;
772         return hbox;
773 }
774
775 void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gchar *, gpointer), gpointer data)
776 {
777         TabCompData *td;
778         if (!entry)
779                 {
780                 printf("Tab completion error: entry != NULL\n");
781                 return;
782                 }
783
784         td = g_new0(TabCompData, 1);
785         td->entry = entry;
786         td->dir_path = NULL;
787         td->file_list = NULL;
788         td->enter_func = enter_func;
789         td->enter_data = data;
790         td->tab_func = NULL;
791         td->tab_data = NULL;
792
793         td->has_history = FALSE;
794         td->history_key = NULL;
795         td->history_levels = 0;
796
797         g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td);
798
799         g_signal_connect(G_OBJECT(entry), "key_press_event",
800                          G_CALLBACK(tab_completion_key_pressed), td);
801         g_signal_connect(G_OBJECT(entry), "destroy",
802                          G_CALLBACK(tab_completion_destroy), td);
803 }
804
805 void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar *, gpointer), gpointer data)
806 {
807         TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
808
809         if (!td) return;
810
811         td->tab_func = tab_func;
812         td->tab_data = data;
813 }
814
815 gchar *remove_trailing_slash(const gchar *path)
816 {
817         gint l;
818
819         if (!path) return NULL;
820
821         l = strlen(path);
822         while (l > 1 && path[l - 1] == '/') l--;
823
824         return g_strndup(path, l);
825 }
826
827 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data)
828 {
829         TabCompData *td = data;
830
831         td->fd = NULL;
832         file_dialog_close(fd);
833 }
834
835 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data)
836 {
837         TabCompData *td = data;
838
839         gtk_entry_set_text(GTK_ENTRY(td->entry), gtk_entry_get_text(GTK_ENTRY(fd->entry)));
840
841         tab_completion_select_cancel_cb(fd, data);
842
843         tab_completion_emit_enter_signal(td);
844 }
845
846 static void tab_completion_select_show(TabCompData *td)
847 {
848         const gchar *title;
849         const gchar *path;
850
851         if (td->fd)
852                 {
853                 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog));
854                 return;
855                 }
856
857         title = (td->fd_title) ? td->fd_title : _("Select path");
858         td->fd = file_dialog_new(title, GQ_WMCLASS, "select_path", td->entry,
859                                  tab_completion_select_cancel_cb, td);
860         file_dialog_add_button(td->fd, GTK_STOCK_OK, NULL,
861                                  tab_completion_select_ok_cb, TRUE);
862
863         generic_dialog_add_message(GENERIC_DIALOG(td->fd), NULL, title, NULL);
864
865         path = gtk_entry_get_text(GTK_ENTRY(td->entry));
866         if (strlen(path) == 0) path = NULL;
867         if (td->fd_folders_only)
868                 {
869                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, NULL, NULL);
870                 }
871         else
872                 {
873                 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, "*", _("All files"));
874                 }
875
876         gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog);
877 }
878
879 static void tab_completion_select_pressed(GtkWidget *widget, gpointer data)
880 {
881         TabCompData *td = data;
882
883         tab_completion_select_show(td);
884 }
885
886 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint folders_only)
887 {
888         TabCompData *td;
889         GtkWidget *parent;
890         GtkWidget *hbox;
891
892         td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
893
894         if (!td) return;
895
896         g_free(td->fd_title);
897         td->fd_title = g_strdup(title);
898         td->fd_folders_only = folders_only;
899
900         if (td->fd_button) return;
901
902         parent = (td->combo) ? td->combo : td->entry;
903
904         hbox = gtk_widget_get_parent(parent);
905         if (!GTK_IS_BOX(hbox)) return;
906
907         td->fd_button = gtk_button_new_with_label("...");
908         g_signal_connect(G_OBJECT(td->fd_button), "size_allocate",
909                          G_CALLBACK(tab_completion_button_size_allocate), parent);
910         g_signal_connect(G_OBJECT(td->fd_button), "clicked",
911                          G_CALLBACK(tab_completion_select_pressed), td);
912
913         gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0);
914
915         gtk_widget_show(td->fd_button);
916 }
917