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