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