Bug fix: Change all .desktop files to RDNS style
[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))
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                 else
409                         {
410                         gchar *buf = g_strconcat(entry_dir, G_DIR_SEPARATOR_S, NULL);
411                         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
412                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
413                         g_free(buf);
414                         g_free(entry_dir);
415                         return TRUE;
416                         }
417                 }
418
419         ptr = const_cast<gchar *>(filename_from_path(entry_dir));
420         if (ptr > entry_dir) ptr--;
421         ptr[0] = '\0';
422
423         if (strlen(entry_dir) == 0)
424                 {
425                 g_free(entry_dir);
426                 entry_dir = g_strdup(G_DIR_SEPARATOR_S); /** @FIXME win32 */
427                 }
428
429         if (isdir(entry_dir))
430                 {
431                 GList *list;
432                 GList *poss = nullptr;
433                 gint l = strlen(entry_file);
434
435                 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
436                         {
437                         tab_completion_read_dir(td, entry_dir);
438                         }
439
440                 list = td->file_list;
441                 while (list)
442                         {
443                         auto file = static_cast<gchar *>(list->data);
444                         if (strncmp(entry_file, file, l) == 0)
445                                 {
446                                 poss = g_list_prepend(poss, file);
447                                 }
448                         list = list->next;
449                         }
450
451                 if (poss)
452                         {
453                         if (!poss->next)
454                                 {
455                                 auto file = static_cast<gchar *>(poss->data);
456                                 gchar *buf;
457
458                                 buf = g_build_filename(entry_dir, file, NULL);
459                                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
460                                 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
461                                 g_free(buf);
462                                 g_list_free(poss);
463                                 g_free(entry_dir);
464                                 return TRUE;
465                                 }
466                         else
467                                 {
468                                 gsize c = strlen(entry_file);
469                                 gboolean done = FALSE;
470                                 auto test_file = static_cast<gchar *>(poss->data);
471
472                                 while (!done)
473                                         {
474                                         list = poss;
475                                         if (!list) done = TRUE;
476                                         while (list && !done)
477                                                 {
478                                                 auto file = static_cast<gchar *>(list->data);
479                                                 if (strlen(file) < c || strncmp(test_file, file, c) != 0)
480                                                         {
481                                                         done = TRUE;
482                                                         }
483                                                 list = list->next;
484                                                 }
485                                         c++;
486                                         }
487                                 c -= 2;
488                                 if (c > 0)
489                                         {
490                                         gchar *file;
491                                         gchar *buf;
492                                         file = g_strdup(test_file);
493                                         file[c] = '\0';
494                                         buf = g_build_filename(entry_dir, file, NULL);
495                                         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
496                                         gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
497
498 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
499
500                                         poss = g_list_sort(poss, simple_sort);
501                                         tab_completion_popup_list(td, poss);
502
503 #endif
504
505                                         g_free(file);
506                                         g_free(buf);
507                                         g_list_free(poss);
508                                         g_free(entry_dir);
509                                         return TRUE;
510                                         }
511                                 }
512                         g_list_free(poss);
513                         }
514                 }
515
516         g_free(entry_dir);
517
518         return FALSE;
519 }
520
521 static gboolean tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
522 {
523         auto td = static_cast<TabCompData *>(data);
524         gboolean stop_signal = FALSE;
525
526         switch (event->keyval)
527                 {
528                 case GDK_KEY_Tab:
529                         if (!(event->state & GDK_CONTROL_MASK))
530                                 {
531                                 if (tab_completion_do(td))
532                                         {
533                                         tab_completion_emit_tab_signal(td);
534                                         }
535                                 stop_signal = TRUE;
536                                 }
537                         break;
538                 case GDK_KEY_Return: case GDK_KEY_KP_Enter:
539                         if (td->fd_button &&
540                             (event->state & GDK_CONTROL_MASK))
541                                 {
542                                 tab_completion_select_show(td);
543                                 stop_signal = TRUE;
544                                 }
545                         else if (tab_completion_emit_enter_signal(td))
546                                 {
547                                 stop_signal = TRUE;
548                                 }
549                         break;
550                 default:
551                         break;
552                 }
553
554         if (stop_signal) g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
555
556         return (stop_signal);
557 }
558
559 static void tab_completion_button_pressed(GtkWidget *, gpointer data)
560 {
561         TabCompData *td;
562         auto entry = static_cast<GtkWidget *>(data);
563
564         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
565
566         if (!td) return;
567
568         if (!gtk_widget_has_focus(entry))
569                 {
570                 gtk_widget_grab_focus(entry);
571                 }
572
573         if (tab_completion_do(td))
574                 {
575                 tab_completion_emit_tab_signal(td);
576                 }
577 }
578
579 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data)
580 {
581         auto parent = static_cast<GtkWidget *>(data);
582         GtkAllocation parent_allocation;
583         gtk_widget_get_allocation(parent, &parent_allocation);
584
585         if (allocation->height > parent_allocation.height)
586                 {
587                 GtkAllocation button_allocation;
588
589                 gtk_widget_get_allocation(button, &button_allocation);
590                 button_allocation.height = parent_allocation.height;
591                 button_allocation.y = parent_allocation.y +
592                         (parent_allocation.height - parent_allocation.height) / 2;
593                 gtk_widget_size_allocate(button, &button_allocation);
594                 }
595 }
596
597 static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWidget *parent)
598 {
599         GtkWidget *button;
600
601         button = gtk_button_new_from_icon_name(GQ_ICON_GO_LAST, GTK_ICON_SIZE_BUTTON);
602         gtk_widget_set_can_focus(button, FALSE);
603         g_signal_connect(G_OBJECT(button), "size_allocate",
604                          G_CALLBACK(tab_completion_button_size_allocate), parent);
605         g_signal_connect(G_OBJECT(button), "clicked",
606                          G_CALLBACK(tab_completion_button_pressed), entry);
607
608         return button;
609 }
610
611 /*
612  *----------------------------------------------------------------------------
613  * public interface
614  *----------------------------------------------------------------------------
615  */
616
617 GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
618                                            const gchar *history_key, gint max_levels,
619                                            void (*enter_func)(const gchar *, gpointer), gpointer data)
620 {
621         GtkWidget *box;
622         GtkWidget *combo;
623         GtkWidget *combo_entry;
624         GtkWidget *button;
625         GList *work;
626         TabCompData *td;
627         gint n = 0;
628
629         box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
630
631         combo = gtk_combo_box_text_new_with_entry();
632         gq_gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
633         gtk_widget_show(combo);
634
635         combo_entry = gtk_bin_get_child(GTK_BIN(combo));
636
637         button = tab_completion_create_complete_button(combo_entry, combo);
638         gq_gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
639         gtk_widget_show(button);
640
641         tab_completion_add_to_entry(combo_entry, enter_func, nullptr, nullptr, data);
642
643         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(combo_entry), "tab_completion_data"));
644         if (!td) return nullptr; /* this should never happen! */
645
646         td->combo = combo;
647         td->has_history = TRUE;
648         td->history_key = g_strdup(history_key);
649         td->history_levels = max_levels;
650
651         work = history_list_get_by_key(td->history_key);
652
653         work = history_list_get_by_key(history_key);
654         while (work)
655                 {
656                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), static_cast<gchar *>(work->data));
657                 work = work->next;
658                 n++;
659                 }
660
661         if (text)
662                 {
663                 gq_gtk_entry_set_text(GTK_ENTRY(combo_entry), text);
664                 }
665         else if (n > 0)
666                 {
667                 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
668                 }
669
670         if (entry) *entry = combo_entry;
671         return box;
672 }
673
674 const gchar *tab_completion_set_to_last_history(GtkWidget *entry)
675 {
676         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
677         const gchar *buf;
678
679         if (!td || !td->has_history) return nullptr;
680
681         buf = history_list_find_last_path_by_key(td->history_key);
682         if (buf)
683                 {
684                 gq_gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
685                 }
686
687         return buf;
688 }
689
690 void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
691 {
692         TabCompData *td;
693         GtkTreeModel *store;
694         GList *work;
695         gint n = 0;
696
697         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
698
699         if (!path) return;
700
701         if (!td || !td->has_history) return;
702
703         history_list_add_to_key(td->history_key, path, td->history_levels);
704
705         gtk_combo_box_set_active(GTK_COMBO_BOX(td->combo), -1);
706
707         store = gtk_combo_box_get_model(GTK_COMBO_BOX(td->combo));
708         gtk_list_store_clear(GTK_LIST_STORE(store));
709
710         work = history_list_get_by_key(td->history_key);
711         while (work)
712                 {
713                 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(td->combo), static_cast<gchar *>(work->data));
714                 work = work->next;
715                 n++;
716                 }
717
718         if (td->tab_append_func) {
719                 td->tab_append_func(path, td->tab_append_data, n);
720         }
721 }
722
723 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text,
724                               void (*enter_func)(const gchar *, gpointer), const gchar *filter, const gchar *filter_desc, gpointer data)
725 {
726         GtkWidget *hbox;
727         GtkWidget *button;
728         GtkWidget *newentry;
729
730         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
731
732         newentry = gtk_entry_new();
733         if (text) gq_gtk_entry_set_text(GTK_ENTRY(newentry), text);
734         gq_gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0);
735         gtk_widget_show(newentry);
736
737         button = tab_completion_create_complete_button(newentry, newentry);
738         gq_gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
739         gtk_widget_show(button);
740
741         tab_completion_add_to_entry(newentry, enter_func, filter, filter_desc, data);
742         if (entry) *entry = newentry;
743         return hbox;
744 }
745
746 void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gchar *, gpointer), const gchar *filter, const gchar *filter_desc, gpointer data)
747 {
748         TabCompData *td;
749         if (!entry)
750                 {
751                 log_printf("Tab completion error: entry != NULL\n");
752                 return;
753                 }
754
755         td = g_new0(TabCompData, 1);
756
757         td->entry = entry;
758         td->enter_func = enter_func;
759         td->enter_data = data;
760         td->filter = g_strdup(filter);
761         td->filter_desc = g_strdup(filter_desc);
762
763         g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td);
764
765         g_signal_connect(G_OBJECT(entry), "key_press_event",
766                          G_CALLBACK(tab_completion_key_pressed), td);
767         g_signal_connect(G_OBJECT(entry), "destroy",
768                          G_CALLBACK(tab_completion_destroy), td);
769 }
770
771 void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar *, gpointer), gpointer data)
772 {
773         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
774
775         if (!td) return;
776
777         td->tab_func = tab_func;
778         td->tab_data = data;
779 }
780
781 /* Add a callback function called when a new entry is appended to the list */
782 void tab_completion_add_append_func(GtkWidget *entry, void (*tab_append_func)(const gchar *, gpointer, gint), gpointer data)
783 {
784         auto td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
785
786         if (!td) return;
787
788         td->tab_append_func = tab_append_func;
789         td->tab_append_data = data;
790 }
791
792 gchar *remove_trailing_slash(const gchar *path)
793 {
794         gint l;
795
796         if (!path) return nullptr;
797
798         l = strlen(path);
799         while (l > 1 && path[l - 1] == G_DIR_SEPARATOR) l--;
800
801         return g_strndup(path, l);
802 }
803
804 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data)
805 {
806         auto td = static_cast<TabCompData *>(data);
807
808         td->fd = nullptr;
809         file_dialog_close(fd);
810 }
811
812 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data)
813 {
814         auto td = static_cast<TabCompData *>(data);
815
816         gq_gtk_entry_set_text(GTK_ENTRY(td->entry), gq_gtk_entry_get_text(GTK_ENTRY(fd->entry)));
817
818         tab_completion_select_cancel_cb(fd, data);
819
820         tab_completion_emit_enter_signal(td);
821 }
822
823 static void tab_completion_select_show(TabCompData *td)
824 {
825         const gchar *title;
826         const gchar *path;
827         const gchar *filter = nullptr;
828         gchar *filter_desc = nullptr;
829
830         if (td->fd)
831                 {
832                 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog));
833                 return;
834                 }
835
836         title = (td->fd_title) ? td->fd_title : _("Select path");
837         td->fd = file_dialog_new(title, "select_path", td->entry,
838                                  tab_completion_select_cancel_cb, td);
839         file_dialog_add_button(td->fd, GQ_ICON_OK, "OK",
840                                  tab_completion_select_ok_cb, TRUE);
841
842         generic_dialog_add_message(GENERIC_DIALOG(td->fd), nullptr, title, nullptr, FALSE);
843
844         if (td->filter)
845                 {
846                 filter = td->filter;
847                 }
848         else
849                 {
850                 filter = "*";
851                 }
852         if (td->filter_desc)
853                 {
854                 filter_desc = td->filter_desc;
855                 }
856         else
857                 {
858                 filter_desc = _("All files");
859                 }
860
861         path = gq_gtk_entry_get_text(GTK_ENTRY(td->entry));
862         if (strlen(path) == 0) path = nullptr;
863         if (td->fd_folders_only)
864                 {
865                 file_dialog_add_path_widgets(td->fd, nullptr, path, td->history_key, nullptr, nullptr);
866                 }
867         else
868                 {
869                 file_dialog_add_path_widgets(td->fd, nullptr, path, td->history_key, filter, filter_desc);
870                 }
871
872         gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog);
873 }
874
875 static void tab_completion_select_pressed(GtkWidget *, gpointer data)
876 {
877         auto td = static_cast<TabCompData *>(data);
878
879         tab_completion_select_show(td);
880 }
881
882 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gboolean folders_only)
883 {
884         TabCompData *td;
885         GtkWidget *parent;
886         GtkWidget *hbox;
887
888         td = static_cast<TabCompData *>(g_object_get_data(G_OBJECT(entry), "tab_completion_data"));
889
890         if (!td) return;
891
892         g_free(td->fd_title);
893         td->fd_title = g_strdup(title);
894         td->fd_folders_only = folders_only;
895
896         if (td->fd_button) return;
897
898         parent = (td->combo) ? td->combo : td->entry;
899
900         hbox = gtk_widget_get_parent(parent);
901         if (!GTK_IS_BOX(hbox)) return;
902
903         td->fd_button = gtk_button_new_with_label("...");
904         g_signal_connect(G_OBJECT(td->fd_button), "size_allocate",
905                          G_CALLBACK(tab_completion_button_size_allocate), parent);
906         g_signal_connect(G_OBJECT(td->fd_button), "clicked",
907                          G_CALLBACK(tab_completion_select_pressed), td);
908
909         gq_gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0);
910
911         gtk_widget_show(td->fd_button);
912 }
913 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */