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