comparison src/ui_tabcomp.c @ 9:d907d608745f

Sync to GQview 1.5.9 release. ######## DO NOT BASE ENHANCEMENTS OR TRANSLATION UPDATES ON CODE IN THIS CVS! This CVS is never up to date with current development and is provided solely for reference purposes, please use the latest official release package when making any changes or translation updates. ########
author gqview
date Sat, 26 Feb 2005 00:13:35 +0000
parents
children 147f4c4b9025
comparison
equal deleted inserted replaced
8:e0d0593d519e 9:d907d608745f
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