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