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