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