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