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