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