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