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