2 * GNT - The GLib Ncurses Toolkit
4 * GNT is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This library is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "gntbutton.h"
25 #include "gntfilesel.h"
27 #include "gntmarshal.h"
32 #include <sys/types.h>
46 static GntWindowClass
*parent_class
= NULL
;
47 static guint signals
[SIGS
] = { 0 };
48 static void (*orig_map
)(GntWidget
*widget
);
49 static void (*orig_size_request
)(GntWidget
*widget
);
51 static void select_activated_cb(GntWidget
*button
, GntFileSel
*sel
);
54 gnt_file_sel_destroy(GntWidget
*widget
)
56 GntFileSel
*sel
= GNT_FILE_SEL(widget
);
60 g_list_foreach(sel
->tags
, (GFunc
)g_free
, NULL
);
61 g_list_free(sel
->tags
);
65 #if !GLIB_CHECK_VERSION(2,8,0)
66 /* ripped from glib/gfileutils.c */
68 g_build_path_va (const gchar
*separator
,
72 gint separator_len
= strlen (separator
);
73 gboolean is_first
= TRUE
;
74 gboolean have_leading
= FALSE
;
75 const gchar
*single_element
= NULL
;
76 const gchar
*next_element
;
77 const gchar
*last_trailing
= NULL
;
80 result
= g_string_new (NULL
);
82 next_element
= str_array
[i
++];
90 element
= next_element
;
91 next_element
= str_array
[i
++];
95 /* Ignore empty elements */
103 strncmp (start
, separator
, separator_len
) == 0)
104 start
+= separator_len
;
107 end
= start
+ strlen (start
);
110 while (end
>= start
+ separator_len
&&
111 strncmp (end
- separator_len
, separator
, separator_len
) == 0)
112 end
-= separator_len
;
115 while (last_trailing
>= element
+ separator_len
&&
116 strncmp (last_trailing
- separator_len
, separator
, separator_len
) == 0)
117 last_trailing
-= separator_len
;
120 /* If the leading and trailing separator strings are in the
121 * same element and overlap, the result is exactly that element
123 if (last_trailing
<= start
)
124 single_element
= element
;
126 g_string_append_len (result
, element
, start
- element
);
129 single_element
= NULL
;
136 g_string_append (result
, separator
);
138 g_string_append_len (result
, start
, end
- start
);
142 if (single_element
) {
143 g_string_free (result
, TRUE
);
144 return g_strdup (single_element
);
147 g_string_append (result
, last_trailing
);
149 return g_string_free (result
, FALSE
);
154 g_build_pathv (const gchar
*separator
,
160 return g_build_path_va (separator
, args
);
166 process_path(const char *path
)
168 char **splits
= NULL
;
172 splits
= g_strsplit(path
, G_DIR_SEPARATOR_S
, -1);
173 for (i
= 0, j
= 0; splits
[i
]; i
++) {
174 if (strcmp(splits
[i
], ".") == 0) {
175 } else if (strcmp(splits
[i
], "..") == 0) {
181 splits
[j
] = splits
[i
];
189 str
= g_build_pathv(G_DIR_SEPARATOR_S
, splits
);
190 ret
= g_strdup_printf(G_DIR_SEPARATOR_S
"%s", str
);
197 update_location(GntFileSel
*sel
)
201 tmp
= sel
->suggest
? sel
->suggest
:
202 (const char*)gnt_tree_get_selection_data(sel
->dirsonly
? GNT_TREE(sel
->dirs
) : GNT_TREE(sel
->files
));
203 old
= g_strdup_printf("%s%s%s", SAFE(sel
->current
), SAFE(sel
->current
)[1] ? G_DIR_SEPARATOR_S
: "", tmp
? tmp
: "");
204 gnt_entry_set_text(GNT_ENTRY(sel
->location
), old
);
209 is_tagged(GntFileSel
*sel
, const char *f
)
211 char *ret
= g_strdup_printf("%s%s%s", sel
->current
, sel
->current
[1] ? G_DIR_SEPARATOR_S
: "", f
);
212 gboolean find
= g_list_find_custom(sel
->tags
, ret
, (GCompareFunc
)g_utf8_collate
) != NULL
;
217 GntFile
* gnt_file_new_dir(const char *name
)
219 GntFile
*file
= g_new0(GntFile
, 1);
220 file
->basename
= g_strdup(name
);
221 file
->type
= GNT_FILE_DIR
;
225 GntFile
* gnt_file_new(const char *name
, unsigned long size
)
227 GntFile
*file
= g_new0(GntFile
, 1);
228 file
->basename
= g_strdup(name
);
229 file
->type
= GNT_FILE_REGULAR
;
235 local_read_fn(const char *path
, GList
**files
, GError
**error
)
241 dir
= g_dir_open(path
, 0, error
);
242 if (dir
== NULL
|| (error
&& *error
)) {
247 if (*path
!= '\0' && strcmp(path
, G_DIR_SEPARATOR_S
)) {
248 file
= gnt_file_new_dir("..");
249 *files
= g_list_prepend(*files
, file
);
252 while ((str
= g_dir_read_name(dir
)) != NULL
) {
253 char *fp
= g_build_filename(path
, str
, NULL
);
257 g_printerr("Error stating location %s\n", fp
);
259 if (S_ISDIR(st
.st_mode
)) {
260 file
= gnt_file_new_dir(str
);
262 file
= gnt_file_new(str
, (long)st
.st_size
);
264 *files
= g_list_prepend(*files
, file
);
269 *files
= g_list_reverse(*files
);
274 gnt_file_free(GntFile
*file
)
276 g_free(file
->fullpath
);
277 g_free(file
->basename
);
282 location_changed(GntFileSel
*sel
, GError
**err
)
290 gnt_tree_remove_all(GNT_TREE(sel
->dirs
));
292 gnt_tree_remove_all(GNT_TREE(sel
->files
));
293 gnt_entry_set_text(GNT_ENTRY(sel
->location
), NULL
);
294 if (sel
->current
== NULL
) {
295 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(sel
), GNT_WIDGET_MAPPED
))
296 gnt_widget_draw(GNT_WIDGET(sel
));
301 * XXX: This is blocking.
306 success
= sel
->read_fn(sel
->current
, &files
, err
);
308 success
= local_read_fn(sel
->current
, &files
, err
);
310 if (!success
|| *err
) {
311 g_printerr("GntFileSel: error opening location %s (%s)\n",
312 sel
->current
, *err
? (*err
)->message
: "reason unknown");
316 for (iter
= files
; iter
; iter
= iter
->next
) {
317 GntFile
*file
= iter
->data
;
318 char *str
= file
->basename
;
319 if (file
->type
== GNT_FILE_DIR
) {
320 gnt_tree_add_row_after(GNT_TREE(sel
->dirs
), g_strdup(str
),
321 gnt_tree_create_row(GNT_TREE(sel
->dirs
), str
), NULL
, NULL
);
322 if (sel
->multiselect
&& sel
->dirsonly
&& is_tagged(sel
, str
))
323 gnt_tree_set_row_flags(GNT_TREE(sel
->dirs
), (gpointer
)str
, GNT_TEXT_FLAG_BOLD
);
324 } else if (!sel
->dirsonly
) {
326 snprintf(size
, sizeof(size
), "%ld", file
->size
);
328 gnt_tree_add_row_after(GNT_TREE(sel
->files
), g_strdup(str
),
329 gnt_tree_create_row(GNT_TREE(sel
->files
), str
, size
, ""), NULL
, NULL
);
330 if (sel
->multiselect
&& is_tagged(sel
, str
))
331 gnt_tree_set_row_flags(GNT_TREE(sel
->files
), (gpointer
)str
, GNT_TEXT_FLAG_BOLD
);
334 g_list_foreach(files
, (GFunc
)gnt_file_free
, NULL
);
336 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(sel
), GNT_WIDGET_MAPPED
))
337 gnt_widget_draw(GNT_WIDGET(sel
));
342 dir_key_pressed(GntTree
*tree
, const char *key
, GntFileSel
*sel
)
344 if (strcmp(key
, "\r") == 0) {
345 char *str
= g_strdup(gnt_tree_get_selection_data(tree
));
351 path
= g_build_filename(sel
->current
, str
, NULL
);
352 dir
= g_path_get_basename(sel
->current
);
353 if (!gnt_file_sel_set_current_location(sel
, path
)) {
354 gnt_tree_set_selected(tree
, str
);
355 } else if (strcmp(str
, "..") == 0) {
356 gnt_tree_set_selected(tree
, dir
);
358 gnt_bindable_perform_action_named(GNT_BINDABLE(tree
), "end-search", NULL
);
368 location_key_pressed(GntTree
*tree
, const char *key
, GntFileSel
*sel
)
378 if (strcmp(key
, "\r"))
381 str
= (char*)gnt_entry_get_text(GNT_ENTRY(sel
->location
));
382 if (*str
== G_DIR_SEPARATOR
)
383 path
= g_strdup(str
);
385 path
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", sel
->current
, str
);
386 str
= process_path(path
);
390 if (gnt_file_sel_set_current_location(sel
, path
))
393 path
= g_path_get_dirname(str
);
396 if (!gnt_file_sel_set_current_location(sel
, path
)) {
401 /* XXX: there needs to be a way to allow other methods for globbing,
402 * like the read_fn stuff. */
403 glob_ret
= glob(path
, GLOB_MARK
, NULL
, &gl
);
404 if (!glob_ret
) { /* XXX: do something with the return value */
405 char *loc
= g_path_get_dirname(gl
.gl_pathv
[0]);
407 stat(gl
.gl_pathv
[0], &st
);
408 gnt_file_sel_set_current_location(sel
, loc
); /* XXX: check the return value */
410 if (!S_ISDIR(st
.st_mode
) && !sel
->dirsonly
) {
411 gnt_tree_remove_all(GNT_TREE(sel
->files
));
412 for (count
= 0; count
< gl
.gl_pathc
; count
++) {
413 char *tmp
= process_path(gl
.gl_pathv
[count
]);
414 loc
= g_path_get_dirname(tmp
);
415 if (g_utf8_collate(sel
->current
, loc
) == 0) {
416 char *base
= g_path_get_basename(tmp
);
418 snprintf(size
, sizeof(size
), "%ld", (long)st
.st_size
);
419 gnt_tree_add_row_after(GNT_TREE(sel
->files
), base
,
420 gnt_tree_create_row(GNT_TREE(sel
->files
), base
, size
, ""), NULL
, NULL
);
425 gnt_widget_draw(sel
->files
);
427 } else if (sel
->files
) {
428 gnt_tree_remove_all(GNT_TREE(sel
->files
));
429 gnt_widget_draw(sel
->files
);
439 file_sel_changed(GntWidget
*widget
, gpointer old
, gpointer current
, GntFileSel
*sel
)
441 if (GNT_WIDGET_IS_FLAG_SET(widget
, GNT_WIDGET_HAS_FOCUS
)) {
442 g_free(sel
->suggest
);
444 update_location(sel
);
449 gnt_file_sel_map(GntWidget
*widget
)
451 GntFileSel
*sel
= GNT_FILE_SEL(widget
);
452 GntWidget
*hbox
, *vbox
;
454 if (sel
->current
== NULL
)
455 gnt_file_sel_set_current_location(sel
, g_get_home_dir());
457 vbox
= gnt_vbox_new(FALSE
);
458 gnt_box_set_pad(GNT_BOX(vbox
), 0);
459 gnt_box_set_alignment(GNT_BOX(vbox
), GNT_ALIGN_MID
);
461 /* The dir. and files list */
462 hbox
= gnt_hbox_new(FALSE
);
463 gnt_box_set_pad(GNT_BOX(hbox
), 0);
465 gnt_box_add_widget(GNT_BOX(hbox
), sel
->dirs
);
467 if (!sel
->dirsonly
) {
468 gnt_box_add_widget(GNT_BOX(hbox
), sel
->files
);
470 g_signal_connect(G_OBJECT(sel
->dirs
), "selection_changed", G_CALLBACK(file_sel_changed
), sel
);
473 gnt_box_add_widget(GNT_BOX(vbox
), hbox
);
474 gnt_box_add_widget(GNT_BOX(vbox
), sel
->location
);
477 hbox
= gnt_hbox_new(FALSE
);
478 gnt_box_add_widget(GNT_BOX(hbox
), sel
->cancel
);
479 gnt_box_add_widget(GNT_BOX(hbox
), sel
->select
);
480 gnt_box_add_widget(GNT_BOX(vbox
), hbox
);
482 gnt_box_add_widget(GNT_BOX(sel
), vbox
);
484 update_location(sel
);
488 toggle_tag_selection(GntBindable
*bind
, GList
*null
)
490 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
496 if (!sel
->multiselect
)
498 tree
= sel
->dirsonly
? sel
->dirs
: sel
->files
;
499 if (!gnt_widget_has_focus(tree
) ||
500 gnt_tree_is_searching(GNT_TREE(tree
)))
503 file
= gnt_tree_get_selection_data(GNT_TREE(tree
));
505 str
= gnt_file_sel_get_selected_file(sel
);
506 if ((find
= g_list_find_custom(sel
->tags
, str
, (GCompareFunc
)g_utf8_collate
)) != NULL
) {
508 sel
->tags
= g_list_delete_link(sel
->tags
, find
);
509 gnt_tree_set_row_flags(GNT_TREE(tree
), file
, GNT_TEXT_FLAG_NORMAL
);
512 sel
->tags
= g_list_prepend(sel
->tags
, str
);
513 gnt_tree_set_row_flags(GNT_TREE(tree
), file
, GNT_TEXT_FLAG_BOLD
);
516 gnt_bindable_perform_action_named(GNT_BINDABLE(tree
), "move-down", NULL
);
522 clear_tags(GntBindable
*bind
, GList
*null
)
524 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
528 if (!sel
->multiselect
)
530 tree
= sel
->dirsonly
? sel
->dirs
: sel
->files
;
531 if (!gnt_widget_has_focus(tree
) ||
532 gnt_tree_is_searching(GNT_TREE(tree
)))
535 g_list_foreach(sel
->tags
, (GFunc
)g_free
, NULL
);
536 g_list_free(sel
->tags
);
539 for (iter
= GNT_TREE(tree
)->list
; iter
; iter
= iter
->next
)
540 gnt_tree_set_row_flags(GNT_TREE(tree
), iter
->data
, GNT_TEXT_FLAG_NORMAL
);
546 up_directory(GntBindable
*bind
, GList
*null
)
549 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
550 if (!gnt_widget_has_focus(sel
->dirs
) &&
551 !gnt_widget_has_focus(sel
->files
))
553 if (gnt_tree_is_searching(GNT_TREE(sel
->dirs
)) ||
554 gnt_tree_is_searching(GNT_TREE(sel
->files
)))
557 path
= g_build_filename(sel
->current
, "..", NULL
);
558 dir
= g_path_get_basename(sel
->current
);
559 if (gnt_file_sel_set_current_location(sel
, path
))
560 gnt_tree_set_selected(GNT_TREE(sel
->dirs
), dir
);
567 gnt_file_sel_size_request(GntWidget
*widget
)
570 if (widget
->priv
.height
> 0)
573 sel
= GNT_FILE_SEL(widget
);
574 sel
->dirs
->priv
.height
= 16;
575 sel
->files
->priv
.height
= 16;
576 orig_size_request(widget
);
580 gnt_file_sel_class_init(GntFileSelClass
*klass
)
582 GntBindableClass
*bindable
= GNT_BINDABLE_CLASS(klass
);
583 GntWidgetClass
*kl
= GNT_WIDGET_CLASS(klass
);
584 parent_class
= GNT_WINDOW_CLASS(klass
);
585 kl
->destroy
= gnt_file_sel_destroy
;
587 kl
->map
= gnt_file_sel_map
;
588 orig_size_request
= kl
->size_request
;
589 kl
->size_request
= gnt_file_sel_size_request
;
591 signals
[SIG_FILE_SELECTED
] =
592 g_signal_new("file_selected",
593 G_TYPE_FROM_CLASS(klass
),
595 G_STRUCT_OFFSET(GntFileSelClass
, file_selected
),
597 gnt_closure_marshal_VOID__STRING_STRING
,
598 G_TYPE_NONE
, 2, G_TYPE_STRING
, G_TYPE_STRING
);
600 gnt_bindable_class_register_action(bindable
, "toggle-tag", toggle_tag_selection
, "t", NULL
);
601 gnt_bindable_class_register_action(bindable
, "clear-tags", clear_tags
, "c", NULL
);
602 gnt_bindable_class_register_action(bindable
, "up-directory", up_directory
, GNT_KEY_BACKSPACE
, NULL
);
603 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass
), GNT_BINDABLE_CLASS(klass
));
609 gnt_file_sel_init(GTypeInstance
*instance
, gpointer
class)
611 GntFileSel
*sel
= GNT_FILE_SEL(instance
);
613 sel
->dirs
= gnt_tree_new();
614 gnt_tree_set_compare_func(GNT_TREE(sel
->dirs
), (GCompareFunc
)g_utf8_collate
);
615 gnt_tree_set_hash_fns(GNT_TREE(sel
->dirs
), g_str_hash
, g_str_equal
, g_free
);
616 gnt_tree_set_column_titles(GNT_TREE(sel
->dirs
), "Directories");
617 gnt_tree_set_show_title(GNT_TREE(sel
->dirs
), TRUE
);
618 gnt_tree_set_col_width(GNT_TREE(sel
->dirs
), 0, 20);
619 g_signal_connect(G_OBJECT(sel
->dirs
), "key_pressed", G_CALLBACK(dir_key_pressed
), sel
);
621 sel
->files
= gnt_tree_new_with_columns(2); /* Name, Size */
622 gnt_tree_set_compare_func(GNT_TREE(sel
->files
), (GCompareFunc
)g_utf8_collate
);
623 gnt_tree_set_column_titles(GNT_TREE(sel
->files
), "Filename", "Size");
624 gnt_tree_set_show_title(GNT_TREE(sel
->files
), TRUE
);
625 gnt_tree_set_col_width(GNT_TREE(sel
->files
), 0, 25);
626 gnt_tree_set_col_width(GNT_TREE(sel
->files
), 1, 10);
627 gnt_tree_set_column_is_right_aligned(GNT_TREE(sel
->files
), 1, TRUE
);
628 g_signal_connect(G_OBJECT(sel
->files
), "selection_changed", G_CALLBACK(file_sel_changed
), sel
);
630 /* The location entry */
631 sel
->location
= gnt_entry_new(NULL
);
632 g_signal_connect(G_OBJECT(sel
->location
), "key_pressed", G_CALLBACK(location_key_pressed
), sel
);
634 sel
->cancel
= gnt_button_new("Cancel");
635 sel
->select
= gnt_button_new("Select");
637 g_signal_connect_swapped(G_OBJECT(sel
->files
), "activate", G_CALLBACK(gnt_widget_activate
), sel
->select
);
638 g_signal_connect(G_OBJECT(sel
->select
), "activate", G_CALLBACK(select_activated_cb
), sel
);
641 /******************************************************************************
643 *****************************************************************************/
645 gnt_file_sel_get_gtype(void)
647 static GType type
= 0;
651 static const GTypeInfo info
= {
652 sizeof(GntFileSelClass
),
653 NULL
, /* base_init */
654 NULL
, /* base_finalize */
655 (GClassInitFunc
)gnt_file_sel_class_init
,
656 NULL
, /* class_finalize */
657 NULL
, /* class_data */
660 gnt_file_sel_init
, /* instance_init */
664 type
= g_type_register_static(GNT_TYPE_WINDOW
,
673 select_activated_cb(GntWidget
*button
, GntFileSel
*sel
)
675 char *path
= gnt_file_sel_get_selected_file(sel
);
676 char *file
= g_path_get_basename(path
);
677 g_signal_emit(sel
, signals
[SIG_FILE_SELECTED
], 0, path
, file
);
682 GntWidget
*gnt_file_sel_new(void)
684 GntWidget
*widget
= g_object_new(GNT_TYPE_FILE_SEL
, NULL
);
688 gboolean
gnt_file_sel_set_current_location(GntFileSel
*sel
, const char *path
)
691 GError
*error
= NULL
;
695 sel
->current
= process_path(path
);
696 if (!location_changed(sel
, &error
)) {
699 g_free(sel
->current
);
701 location_changed(sel
, &error
);
706 update_location(sel
);
710 void gnt_file_sel_set_dirs_only(GntFileSel
*sel
, gboolean dirs
)
712 sel
->dirsonly
= dirs
;
715 gboolean
gnt_file_sel_get_dirs_only(GntFileSel
*sel
)
717 return sel
->dirsonly
;
720 void gnt_file_sel_set_suggested_filename(GntFileSel
*sel
, const char *suggest
)
722 g_free(sel
->suggest
);
723 sel
->suggest
= g_strdup(suggest
);
726 char *gnt_file_sel_get_selected_file(GntFileSel
*sel
)
730 ret
= g_path_get_dirname(gnt_entry_get_text(GNT_ENTRY(sel
->location
)));
732 ret
= g_strdup(gnt_entry_get_text(GNT_ENTRY(sel
->location
)));
737 void gnt_file_sel_set_must_exist(GntFileSel
*sel
, gboolean must
)
739 /*XXX: What do I do with this? */
740 sel
->must_exist
= must
;
743 gboolean
gnt_file_sel_get_must_exist(GntFileSel
*sel
)
745 return sel
->must_exist
;
748 void gnt_file_sel_set_multi_select(GntFileSel
*sel
, gboolean set
)
750 sel
->multiselect
= set
;
753 GList
*gnt_file_sel_get_selected_multi_files(GntFileSel
*sel
)
755 GList
*list
= NULL
, *iter
;
756 char *str
= gnt_file_sel_get_selected_file(sel
);
758 for (iter
= sel
->tags
; iter
; iter
= iter
->next
) {
759 list
= g_list_prepend(list
, g_strdup(iter
->data
));
760 if (g_utf8_collate(str
, iter
->data
)) {
766 list
= g_list_prepend(list
, str
);
767 list
= g_list_reverse(list
);
771 void gnt_file_sel_set_read_fn(GntFileSel
*sel
, gboolean (*read_fn
)(const char *path
, GList
**files
, GError
**error
))
773 sel
->read_fn
= read_fn
;