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