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