69ad7beb13f7b8c6315a5db4e1289a27185ecffa
[geeqie.git] / src / ui-tabcomp.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <cstring>
23 #include <dirent.h>
24 #include <sys/types.h>
25
26 #include "main.h"
27 #include "ui-tabcomp.h"
28
29 #include "history-list.h"
30 #include "misc.h"       /* expand_tilde() */
31 #include "ui-fileops.h"
32 #include "ui-utildlg.h"
33
34 /* define this to enable a pop-up menu that shows possible matches
35  * #define TAB_COMPLETION_ENABLE_POPUP_MENU
36  */
37 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1
38 #define TAB_COMP_POPUP_MAX 1000
39
40 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
41 #include "ui-menu.h"
42 #endif
43
44
45 /**
46  * @file
47  * ----------------------------------------------------------------
48  * Tab completion routines, can be connected to any gtkentry widget
49  * using the tab_completion_add_to_entry() function.
50  *
51  * Use remove_trailing_slash() to strip the trailing G_DIR_SEPARATOR.
52  *
53  * ----------------------------------------------------------------
54  */
55
56 struct TabCompData
57 {
58         GtkWidget *entry;
59         gchar *dir_path;
60         GList *file_list;
61         void (*enter_func)(const gchar *, gpointer);
62         void (*tab_func)(const gchar *, gpointer);
63         void (*tab_append_func)(const gchar *, gpointer, gint);
64
65         gpointer enter_data;
66         gpointer tab_data;
67         gpointer tab_append_data;
68
69         GtkWidget *combo;
70         gboolean has_history;
71         gchar *history_key;
72         gint history_levels;
73
74         FileDialog *fd;
75         gchar *fd_title;
76         gboolean fd_folders_only;
77         GtkWidget *fd_button;
78         gchar *filter;
79         gchar *filter_desc;
80
81         guint choices;
82 };
83
84
85 static void tab_completion_select_show(TabCompData *td);
86 static gint tab_completion_do(TabCompData *td);
87
88 static void tab_completion_free_list(TabCompData *td)
89 {
90         g_free(td->dir_path);
91         td->dir_path = nullptr;
92
93         g_list_free_full(td->file_list, g_free);
94         td->file_list = nullptr;
95 }
96
97 static void tab_completion_read_dir(TabCompData *td, const gchar *path)
98 {
99         DIR *dp;
100         struct dirent *dir;
101         GList *list = nullptr;
102         gchar *pathl;
103
104         tab_completion_free_list(td);
105
106         pathl = path_from_utf8(path);
107         dp = opendir(pathl);
108         if (!dp)
109                 {
110                 /* dir not found */
111                 g_free(pathl);
112                 return;
113                 }
114         while ((dir = readdir(dp)) != nullptr)
115                 {
116                 gchar *name = dir->d_name;
117                 if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0 &&
118                                                 (name[0] != '.' || options->file_filter.show_hidden_files))
119                         {
120                         gchar *abspath = g_build_filename(pathl, name, NULL);
121
122                         if (g_file_test(abspath, G_FILE_TEST_IS_DIR))
123                                 {
124                                 gchar *dname = g_strconcat(name, G_DIR_SEPARATOR_S, NULL);
125                                 list = g_list_prepend(list, path_to_utf8(dname));
126                                 g_free(dname);
127                                 }
128                         else
129                                 {
130                                 list = g_list_prepend(list, path_to_utf8(name));
131                                 }
132                         g_free(abspath);
133                         }
134                 }
135         closedir(dp);
136
137         td->dir_path = g_strdup(path);
138         td->file_list = list;
139         g_free(pathl);
140 }
141
142 static void tab_completion_destroy(GtkWidget *, gpointer data)
143 {
144         auto td = static_cast<TabCompData *>(data);
145
146         tab_completion_free_list(td);
147         g_free(td->history_key);
148
149         if (td->fd) file_dialog_close(td->fd);
150         g_free(td->fd_title);
151
152         g_free(td->filter);
153         g_free(td->filter_desc);
154
155         g_free(td);
156 }
157
158 static gchar *tab_completion_get_text(TabCompData *td)
159 {
160         gchar *text;
161
162         text = g_strdup(gq_gtk_entry_get_text(GTK_ENTRY(td->entry)));
163
164         if (text[0] == '~')
165                 {
166                 gchar *t = text;
167                 text = expand_tilde(text);
168                 g_free(t);
169                 }
170
171         return text;
172 }
173
174 static gboolean tab_completion_emit_enter_signal(TabCompData *td)
175 {
176         gchar *text;
177         if (!td->enter_func) return FALSE;
178
179         text = tab_completion_get_text(td);
180         td->enter_func(text, td->enter_data);
181         g_free(text);
182
183         return TRUE;
184 }
185
186 static void tab_completion_emit_tab_signal(TabCompData *td)
187 {
188         gchar *text;
189         if (!td->tab_func) return;
190
191         text = tab_completion_get_text(td);
192         td->tab_func(text, td->tab_data);
193         g_free(text);
194 }
195
196 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
197 void tab_completion_iter_menu_items(GtkWidget *widget, gpointer data)
198 {
199         auto td = static_cast<TabCompData *>(data);
200         GtkWidget *child;
201
202         if (!gtk_widget_get_visible(widget)) return;
203
204         child = gtk_bin_get_child(GTK_BIN(widget));
205         if (GTK_IS_LABEL(child)) {
206                 const gchar *text = gtk_label_get_text(GTK_LABEL(child));
207                 const gchar *entry_text = gq_gtk_entry_get_text(GTK_ENTRY(td->entry));
208                 const gchar *prefix = filename_from_path(entry_text);
209                 guint prefix_len = strlen(prefix);
210
211                 if (strlen(text) < prefix_len || strncmp(text, prefix, prefix_len) != 0)
212                         {
213                         /* Hide menu items not matching */
214                         gtk_widget_hide(widget);
215                         }
216                 else
217                         {
218                         /* Count how many choices are left in the menu */
219                         td->choices++;
220                         }
221         }
222 }
223
224 static gboolean tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
225 {
226         auto td = static_cast<TabCompData *>(data);
227
228         if (event->keyval == GDK_KEY_Tab ||
229             event->keyval == GDK_KEY_BackSpace ||
230             (event->keyval >= 0x20 && event->keyval <= 0xFF) )
231                 {
232                 if (event->keyval >= 0x20 && event->keyval <= 0xFF)
233                         {
234                         gchar buf[2];
235                         gint p = -1;
236
237                         buf[0] = event->keyval;
238                         buf[1] = '\0';
239                         gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p);
240                         gtk_editable_set_position(GTK_EDITABLE(td->entry), -1);
241
242                         /* Reduce the number of entries in the menu */
243                         td->choices = 0;
244                         gtk_container_foreach(GTK_CONTAINER(widget), tab_completion_iter_menu_items, td);
245                         if (td->choices > 1) return TRUE; /* multiple choices */
246                         if (td->choices > 0) tab_completion_do(td); /* one choice */
247                         }
248
249                 /* close the menu */
250                 gtk_menu_popdown(GTK_MENU(widget));
251                 /* doing this does not emit the "selection done" signal, unref it ourselves */
252                 g_object_unref(widget);
253                 return TRUE;
254                 }
255
256         return FALSE;
257 }
258
259 static void tab_completion_popup_cb(GtkWidget *widget, gpointer data)
260 {
261         auto name = static_cast<gchar *>(data);
262         TabCompData *td;
263         gchar *buf;
264
265         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(widget), "tab_completion_data"));
266         if (!td) return;
267
268         buf = g_build_filename(td->dir_path, name, NULL);
269         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
270         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
271         g_free(buf);
272
273         tab_completion_emit_tab_signal(td);
274 }
275
276 static void tab_completion_popup_list(TabCompData *td, GList *list)
277 {
278         GtkWidget *menu;
279         GList *work;
280         gint count = 0;
281
282         if (!list) return;
283
284 #if 0
285         /*
286          * well, the menu would be too long anyway...
287          * (listing /dev causes gtk+ window allocation errors, -> too big a window)
288          * this is why menu popups are disabled, this really should be a popup scrollable listview.
289          */
290         if (g_list_length(list) > 200) return;
291 #endif
292
293         menu = popup_menu_short_lived();
294
295         work = list;
296         while (work && count < TAB_COMP_POPUP_MAX)
297                 {
298                 auto name = static_cast<gchar *>(work->data);
299                 GtkWidget *item;
300
301                 item = menu_item_add_simple(menu, name, G_CALLBACK(tab_completion_popup_cb), name);
302                 g_object_set_data(G_OBJECT(item), "tab_completion_data", td);
303
304                 work = work->next;
305                 count++;
306                 }
307
308         g_signal_connect(G_OBJECT(menu), "key_press_event",
309                          G_CALLBACK(tab_completion_popup_key_press), td);
310
311         gtk_menu_popup_at_widget(GTK_MENU(menu), td->entry, GDK_GRAVITY_NORTH_EAST, GDK_GRAVITY_NORTH, nullptr);
312 }
313
314 #ifndef CASE_SORT
315 #define CASE_SORT strcmp
316 #endif
317
318 static gint simple_sort(gconstpointer a, gconstpointer b)
319 {
320         return CASE_SORT((gchar *)a, (gchar *)b);
321 }
322
323 #endif
324
325 static gboolean tab_completion_do(TabCompData *td)
326 {
327         const gchar *entry_text = gq_gtk_entry_get_text(GTK_ENTRY(td->entry));
328         const gchar *entry_file;
329         gchar *entry_dir;
330         gchar *ptr;
331         gboolean home_exp = FALSE;
332
333         if (entry_text[0] == '\0')
334                 {
335                 entry_dir = g_strdup(G_DIR_SEPARATOR_S); /** @FIXME root directory win32 */
336                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
337                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
338                 g_free(entry_dir);
339                 return FALSE;
340                 }
341
342         /* home dir expansion */
343         if (entry_text[0] == '~')
344                 {
345                 entry_dir = expand_tilde(entry_text);
346                 home_exp = TRUE;
347                 }
348         else
349                 {
350                 entry_dir = g_strdup(entry_text);
351                 }
352
353         if (isfile(entry_dir))
354                 {
355                 if (home_exp)
356                         {
357                         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
358                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
359                         }
360                 g_free(entry_dir);
361                 return home_exp;
362                 }
363
364         entry_file = filename_from_path(entry_text);
365
366         if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
367                 {
368                 ptr = entry_dir + strlen(entry_dir) - 1;
369                 if (ptr[0] == G_DIR_SEPARATOR)
370                         {
371                         if (home_exp)
372                                 {
373                                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
374                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
375                                 }
376
377                         tab_completion_read_dir(td, entry_dir);
378                         td->file_list = g_list_sort(td->file_list, simple_sort);
379                         if (td->file_list && !td->file_list->next)
380                                 {
381                                 gchar *buf;
382                                 const gchar *file;
383
384                                 file = static_cast<const gchar *>(td->file_list->data);
385                                 buf = g_build_filename(entry_dir, file, NULL);
386                                 if (isdir(buf))
387                                         {
388                                         gchar *tmp = g_strconcat(buf, G_DIR_SEPARATOR_S, NULL);
389                                         g_free(buf);
390                                         buf = tmp;
391                                         }
392                                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
393                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
394                                 g_free(buf);
395                                 }
396
397 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
398
399                         else
400                                 {
401                                 tab_completion_popup_list(td, td->file_list);
402                                 }
403 #endif
404
405                         g_free(entry_dir);
406                         return home_exp;
407                         }
408
409                 gchar *buf = g_strconcat(entry_dir, G_DIR_SEPARATOR_S, NULL);
410                 gq_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                 g_free(entry_dir);
414                 return TRUE;
415                 }
416
417         ptr = const_cast<gchar *>(filename_from_path(entry_dir));
418         if (ptr > entry_dir) ptr--;
419         ptr[0] = '\0';
420
421         if (strlen(entry_dir) == 0)
422                 {
423                 g_free(entry_dir);
424                 entry_dir = g_strdup(G_DIR_SEPARATOR_S); /** @FIXME win32 */
425                 }
426
427         if (isdir(entry_dir))
428                 {
429                 GList *list;
430                 GList *poss = nullptr;
431                 gint l = strlen(entry_file);
432
433                 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
434                         {
435                         tab_completion_read_dir(td, entry_dir);
436                         }
437
438                 list = td->file_list;
439                 while (list)
440                         {
441                         auto file = static_cast<gchar *>(list->data);
442                         if (strncmp(entry_file, file, l) == 0)
443                                 {
444                                 poss = g_list_prepend(poss, file);
445                                 }
446                         list = list->next;
447                         }
448
449                 if (poss)
450                         {
451                         if (!poss->next)
452                                 {
453                                 auto file = static_cast<gchar *>(poss->data);
454                                 gchar *buf;
455
456                                 buf = g_build_filename(entry_dir, file, NULL);
457                                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
458                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
459                                 g_free(buf);
460                                 g_list_free(poss);
461                                 g_free(entry_dir);
462                                 return TRUE;
463                                 }
464
465                         gsize c = strlen(entry_file);
466                         gboolean done = FALSE;
467                         auto test_file = static_cast<gchar *>(poss->data);
468
469                         while (!done)
470                                 {
471                                 list = poss;
472                                 if (!list) done = TRUE;
473                                 while (list && !done)
474                                         {
475                                         auto file = static_cast<gchar *>(list->data);
476                                         if (strlen(file) < c || strncmp(test_file, file, c) != 0)
477                                                 {
478                                                 done = TRUE;
479                                                 }
480                                         list = list->next;
481                                         }
482                                 c++;
483                                 }
484                         c -= 2;
485                         if (c > 0)
486                                 {
487                                 gchar *file;
488                                 gchar *buf;
489                                 file = g_strdup(test_file);
490                                 file[c] = '\0';
491                                 buf = g_build_filename(entry_dir, file, NULL);
492                                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
493                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
494
495 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
496
497                                 poss = g_list_sort(poss, simple_sort);
498                                 tab_completion_popup_list(td, poss);
499
500 #endif
501
502                                 g_free(file);
503                                 g_free(buf);
504                                 g_list_free(poss);
505                                 g_free(entry_dir);
506                                 return TRUE;
507                                 }
508
509                         g_list_free(poss);
510                         }
511                 }
512
513         g_free(entry_dir);
514
515         return FALSE;
516 }
517
518 static gboolean tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
519 {
520         auto td = static_cast<TabCompData *>(data);
521         gboolean stop_signal = FALSE;
522
523         switch (event->keyval)
524                 {
525                 case GDK_KEY_Tab:
526                         if (!(event->state & GDK_CONTROL_MASK))
527                                 {
528                                 if (tab_completion_do(td))
529                                         {
530                                         tab_completion_emit_tab_signal(td);
531                                         }
532                                 stop_signal = TRUE;
533                                 }
534                         break;
535                 case GDK_KEY_Return: case GDK_KEY_KP_Enter:
536                         if (td->fd_button &&
537                             (event->state & GDK_CONTROL_MASK))
538                                 {
539                                 tab_completion_select_show(td);
540                                 stop_signal = TRUE;
541                                 }
542                         else if (tab_completion_emit_enter_signal(td))
543                                 {
544                                 stop_signal = TRUE;
545                                 }
546                         break;
547                 default:
548                         break;
549                 }
550
551         if (stop_signal) g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
552
553         return (stop_signal);
554 }
555
556 static void tab_completion_button_pressed(GtkWidget *, gpointer data)
557 {
558         TabCompData *td;
559         auto entry = static_cast<GtkWidget *>(data);
560
561         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
562
563         if (!td) return;
564
565         if (!gtk_widget_has_focus(entry))
566                 {
567                 gtk_widget_grab_focus(entry);
568                 }
569
570         if (tab_completion_do(td))
571                 {
572                 tab_completion_emit_tab_signal(td);
573                 }
574 }
575
576 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data)
577 {
578         auto parent = static_cast<GtkWidget *>(data);
579         GtkAllocation parent_allocation;
580         gtk_widget_get_allocation(parent, &parent_allocation);
581
582         if (allocation->height > parent_allocation.height)
583                 {
584                 GtkAllocation button_allocation;
585
586                 gtk_widget_get_allocation(button, &button_allocation);
587                 button_allocation.height = parent_allocation.height;
588                 button_allocation.y = parent_allocation.y;
589                 gtk_widget_size_allocate(button, &button_allocation);
590                 }
591 }
592
593 static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWidget *parent)
594 {
595         GtkWidget *button;
596
597         button = gtk_button_new_from_icon_name(GQ_ICON_GO_LAST, GTK_ICON_SIZE_BUTTON);
598         gtk_widget_set_can_focus(button, FALSE);
599         g_signal_connect(G_OBJECT(button), "size_allocate",
600                          G_CALLBACK(tab_completion_button_size_allocate), parent);
601         g_signal_connect(G_OBJECT(button), "clicked",
602                          G_CALLBACK(tab_completion_button_pressed), entry);
603
604         return button;
605 }
606
607 /*
608  *----------------------------------------------------------------------------
609  * public interface
610  *----------------------------------------------------------------------------
611  */
612
613 GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
614                                            const gchar *history_key, gint max_levels,
615                                            void (*enter_func)(const gchar *, gpointer), gpointer data)
616 {
617         GtkWidget *box;
618         GtkWidget *combo;
619         GtkWidget *combo_entry;
620         GtkWidget *button;
621         GList *work;
622         TabCompData *td;
623         gint n = 0;
624
625         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
626
627         combo = gtk_combo_box_text_new_with_entry();
628         gq_gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
629         gtk_widget_show(combo);
630
631         combo_entry = gtk_bin_get_child(GTK_BIN(combo));
632
633         button = tab_completion_create_complete_button(combo_entry, combo);
634         gq_gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
635         gtk_widget_show(button);
636
637         tab_completion_add_to_entry(combo_entry, enter_func, nullptr, nullptr, data);
638
639         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(combo_entry), "tab_completion_data"));
640         if (!td) return nullptr; /* this should never happen! */
641
642         td->combo = combo;
643         td->has_history = TRUE;
644         td->history_key = g_strdup(history_key);
645         td->history_levels = max_levels;
646
647         work = history_list_get_by_key(td->history_key);
648
649         work = history_list_get_by_key(history_key);
650         while (work)
651                 {
652                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), static_cast<gchar *>(work->data));
653                 work = work->next;
654                 n++;
655                 }
656
657         if (text)
658                 {
659                 gq_gtk_entry_set_text(GTK_ENTRY(combo_entry), text);
660                 }
661         else if (n > 0)
662                 {
663                 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
664                 }
665
666         if (entry) *entry = combo_entry;
667         return box;
668 }
669
670 const gchar *tab_completion_set_to_last_history(GtkWidget *entry)
671 {
672         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
673         const gchar *buf;
674
675         if (!td || !td->has_history) return nullptr;
676
677         buf = history_list_find_last_path_by_key(td->history_key);
678         if (buf)
679                 {
680                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
681                 }
682
683         return buf;
684 }
685
686 void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
687 {
688         TabCompData *td;
689         GtkTreeModel *store;
690         GList *work;
691         gint n = 0;
692
693         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
694
695         if (!path) return;
696
697         if (!td || !td->has_history) return;
698
699         history_list_add_to_key(td->history_key, path, td->history_levels);
700
701         gtk_combo_box_set_active(GTK_COMBO_BOX(td->combo), -1);
702
703         store = gtk_combo_box_get_model(GTK_COMBO_BOX(td->combo));
704         gtk_list_store_clear(GTK_LIST_STORE(store));
705
706         work = history_list_get_by_key(td->history_key);
707         while (work)
708                 {
709                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(td->combo), static_cast<gchar *>(work->data));
710                 work = work->next;
711                 n++;
712                 }
713
714         if (td->tab_append_func) {
715                 td->tab_append_func(path, td->tab_append_data, n);
716         }
717 }
718
719 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text,
720                               void (*enter_func)(const gchar *, gpointer), const gchar *filter, const gchar *filter_desc, gpointer data)
721 {
722         GtkWidget *hbox;
723         GtkWidget *button;
724         GtkWidget *newentry;
725
726         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
727
728         newentry = gtk_entry_new();
729         if (text) gq_gtk_entry_set_text(GTK_ENTRY(newentry), text);
730         gq_gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0);
731         gtk_widget_show(newentry);
732
733         button = tab_completion_create_complete_button(newentry, newentry);
734         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
735         gtk_widget_show(button);
736
737         tab_completion_add_to_entry(newentry, enter_func, filter, filter_desc, data);
738         if (entry) *entry = newentry;
739         return hbox;
740 }
741
742 void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gchar *, gpointer), const gchar *filter, const gchar *filter_desc, gpointer data)
743 {
744         TabCompData *td;
745         if (!entry)
746                 {
747                 log_printf("Tab completion error: entry != NULL\n");
748                 return;
749                 }
750
751         td = g_new0(TabCompData, 1);
752
753         td->entry = entry;
754         td->enter_func = enter_func;
755         td->enter_data = data;
756         td->filter = g_strdup(filter);
757         td->filter_desc = g_strdup(filter_desc);
758
759         g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td);
760
761         g_signal_connect(G_OBJECT(entry), "key_press_event",
762                          G_CALLBACK(tab_completion_key_pressed), td);
763         g_signal_connect(G_OBJECT(entry), "destroy",
764                          G_CALLBACK(tab_completion_destroy), td);
765 }
766
767 void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar *, gpointer), gpointer data)
768 {
769         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
770
771         if (!td) return;
772
773         td->tab_func = tab_func;
774         td->tab_data = data;
775 }
776
777 /* Add a callback function called when a new entry is appended to the list */
778 void tab_completion_add_append_func(GtkWidget *entry, void (*tab_append_func)(const gchar *, gpointer, gint), gpointer data)
779 {
780         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
781
782         if (!td) return;
783
784         td->tab_append_func = tab_append_func;
785         td->tab_append_data = data;
786 }
787
788 gchar *remove_trailing_slash(const gchar *path)
789 {
790         gint l;
791
792         if (!path) return nullptr;
793
794         l = strlen(path);
795         while (l > 1 && path[l - 1] == G_DIR_SEPARATOR) l--;
796
797         return g_strndup(path, l);
798 }
799
800 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data)
801 {
802         auto td = static_cast<TabCompData *>(data);
803
804         td->fd = nullptr;
805         file_dialog_close(fd);
806 }
807
808 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data)
809 {
810         auto td = static_cast<TabCompData *>(data);
811
812         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), gq_gtk_entry_get_text(GTK_ENTRY(fd->entry)));
813
814         tab_completion_select_cancel_cb(fd, data);
815
816         tab_completion_emit_enter_signal(td);
817 }
818
819 static void tab_completion_select_show(TabCompData *td)
820 {
821         const gchar *title;
822         const gchar *path;
823         const gchar *filter = nullptr;
824         gchar *filter_desc = nullptr;
825
826         if (td->fd)
827                 {
828                 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog));
829                 return;
830                 }
831
832         title = (td->fd_title) ? td->fd_title : _("Select path");
833         td->fd = file_dialog_new(title, "select_path", td->entry,
834                                  tab_completion_select_cancel_cb, td);
835         file_dialog_add_button(td->fd, GQ_ICON_OK, "OK",
836                                  tab_completion_select_ok_cb, TRUE);
837
838         generic_dialog_add_message(GENERIC_DIALOG(td->fd), nullptr, title, nullptr, FALSE);
839
840         if (td->filter)
841                 {
842                 filter = td->filter;
843                 }
844         else
845                 {
846                 filter = "*";
847                 }
848         if (td->filter_desc)
849                 {
850                 filter_desc = td->filter_desc;
851                 }
852         else
853                 {
854                 filter_desc = _("All files");
855                 }
856
857         path = gq_gtk_entry_get_text(GTK_ENTRY(td->entry));
858         if (strlen(path) == 0) path = nullptr;
859         if (td->fd_folders_only)
860                 {
861                 file_dialog_add_path_widgets(td->fd, nullptr, path, td->history_key, nullptr, nullptr);
862                 }
863         else
864                 {
865                 file_dialog_add_path_widgets(td->fd, nullptr, path, td->history_key, filter, filter_desc);
866                 }
867
868         gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog);
869 }
870
871 static void tab_completion_select_pressed(GtkWidget *, gpointer data)
872 {
873         auto td = static_cast<TabCompData *>(data);
874
875         tab_completion_select_show(td);
876 }
877
878 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gboolean folders_only)
879 {
880         TabCompData *td;
881         GtkWidget *parent;
882         GtkWidget *hbox;
883
884         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
885
886         if (!td) return;
887
888         g_free(td->fd_title);
889         td->fd_title = g_strdup(title);
890         td->fd_folders_only = folders_only;
891
892         if (td->fd_button) return;
893
894         parent = (td->combo) ? td->combo : td->entry;
895
896         hbox = gtk_widget_get_parent(parent);
897         if (!GTK_IS_BOX(hbox)) return;
898
899         td->fd_button = gtk_button_new_with_label("...");
900         g_signal_connect(G_OBJECT(td->fd_button), "size_allocate",
901                          G_CALLBACK(tab_completion_button_size_allocate), parent);
902         g_signal_connect(G_OBJECT(td->fd_button), "clicked",
903                          G_CALLBACK(tab_completion_select_pressed), td);
904
905         gq_gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0);
906
907         gtk_widget_show(td->fd_button);
908 }
909 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */