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
);
270 *files
= g_list_reverse(*files
);
275 gnt_file_free(GntFile
*file
)
277 g_free(file
->fullpath
);
278 g_free(file
->basename
);
283 location_changed(GntFileSel
*sel
, GError
**err
)
291 gnt_tree_remove_all(GNT_TREE(sel
->dirs
));
293 gnt_tree_remove_all(GNT_TREE(sel
->files
));
294 gnt_entry_set_text(GNT_ENTRY(sel
->location
), NULL
);
295 if (sel
->current
== NULL
) {
296 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(sel
), GNT_WIDGET_MAPPED
))
297 gnt_widget_draw(GNT_WIDGET(sel
));
302 * XXX: This is blocking.
307 success
= sel
->read_fn(sel
->current
, &files
, err
);
309 success
= local_read_fn(sel
->current
, &files
, err
);
311 if (!success
|| *err
) {
312 g_printerr("GntFileSel: error opening location %s (%s)\n",
313 sel
->current
, *err
? (*err
)->message
: "reason unknown");
317 for (iter
= files
; iter
; iter
= iter
->next
) {
318 GntFile
*file
= iter
->data
;
319 char *str
= file
->basename
;
320 if (file
->type
== GNT_FILE_DIR
) {
321 gnt_tree_add_row_after(GNT_TREE(sel
->dirs
), g_strdup(str
),
322 gnt_tree_create_row(GNT_TREE(sel
->dirs
), str
), NULL
, NULL
);
323 if (sel
->multiselect
&& sel
->dirsonly
&& is_tagged(sel
, str
))
324 gnt_tree_set_row_flags(GNT_TREE(sel
->dirs
), (gpointer
)str
, GNT_TEXT_FLAG_BOLD
);
325 } else if (!sel
->dirsonly
) {
327 snprintf(size
, sizeof(size
), "%ld", file
->size
);
329 gnt_tree_add_row_after(GNT_TREE(sel
->files
), g_strdup(str
),
330 gnt_tree_create_row(GNT_TREE(sel
->files
), str
, size
, ""), NULL
, NULL
);
331 if (sel
->multiselect
&& is_tagged(sel
, str
))
332 gnt_tree_set_row_flags(GNT_TREE(sel
->files
), (gpointer
)str
, GNT_TEXT_FLAG_BOLD
);
335 g_list_foreach(files
, (GFunc
)gnt_file_free
, NULL
);
337 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(sel
), GNT_WIDGET_MAPPED
))
338 gnt_widget_draw(GNT_WIDGET(sel
));
343 dir_key_pressed(GntTree
*tree
, const char *key
, GntFileSel
*sel
)
345 if (strcmp(key
, "\r") == 0) {
346 char *str
= g_strdup(gnt_tree_get_selection_data(tree
));
352 path
= g_build_filename(sel
->current
, str
, NULL
);
353 dir
= g_path_get_basename(sel
->current
);
354 if (!gnt_file_sel_set_current_location(sel
, path
)) {
355 gnt_tree_set_selected(tree
, str
);
356 } else if (strcmp(str
, "..") == 0) {
357 gnt_tree_set_selected(tree
, dir
);
359 gnt_bindable_perform_action_named(GNT_BINDABLE(tree
), "end-search", NULL
);
369 location_key_pressed(GntTree
*tree
, const char *key
, GntFileSel
*sel
)
379 if (strcmp(key
, "\r"))
382 str
= (char*)gnt_entry_get_text(GNT_ENTRY(sel
->location
));
383 if (*str
== G_DIR_SEPARATOR
)
384 path
= g_strdup(str
);
386 path
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", sel
->current
, str
);
387 str
= process_path(path
);
391 if (gnt_file_sel_set_current_location(sel
, path
))
394 path
= g_path_get_dirname(str
);
397 if (!gnt_file_sel_set_current_location(sel
, path
)) {
402 /* XXX: there needs to be a way to allow other methods for globbing,
403 * like the read_fn stuff. */
404 glob_ret
= glob(path
, GLOB_MARK
, NULL
, &gl
);
405 if (!glob_ret
) { /* XXX: do something with the return value */
406 char *loc
= g_path_get_dirname(gl
.gl_pathv
[0]);
408 stat(gl
.gl_pathv
[0], &st
);
409 gnt_file_sel_set_current_location(sel
, loc
); /* XXX: check the return value */
411 if (!S_ISDIR(st
.st_mode
) && !sel
->dirsonly
) {
412 gnt_tree_remove_all(GNT_TREE(sel
->files
));
413 for (count
= 0; count
< gl
.gl_pathc
; count
++) {
414 char *tmp
= process_path(gl
.gl_pathv
[count
]);
415 loc
= g_path_get_dirname(tmp
);
416 if (g_utf8_collate(sel
->current
, loc
) == 0) {
417 char *base
= g_path_get_basename(tmp
);
419 snprintf(size
, sizeof(size
), "%ld", (long)st
.st_size
);
420 gnt_tree_add_row_after(GNT_TREE(sel
->files
), base
,
421 gnt_tree_create_row(GNT_TREE(sel
->files
), base
, size
, ""), NULL
, NULL
);
426 gnt_widget_draw(sel
->files
);
428 } else if (sel
->files
) {
429 gnt_tree_remove_all(GNT_TREE(sel
->files
));
430 gnt_widget_draw(sel
->files
);
440 file_sel_changed(GntWidget
*widget
, gpointer old
, gpointer current
, GntFileSel
*sel
)
442 if (GNT_WIDGET_IS_FLAG_SET(widget
, GNT_WIDGET_HAS_FOCUS
)) {
443 g_free(sel
->suggest
);
445 update_location(sel
);
450 gnt_file_sel_map(GntWidget
*widget
)
452 GntFileSel
*sel
= GNT_FILE_SEL(widget
);
453 GntWidget
*hbox
, *vbox
;
455 if (sel
->current
== NULL
)
456 gnt_file_sel_set_current_location(sel
, g_get_home_dir());
458 vbox
= gnt_vbox_new(FALSE
);
459 gnt_box_set_pad(GNT_BOX(vbox
), 0);
460 gnt_box_set_alignment(GNT_BOX(vbox
), GNT_ALIGN_MID
);
462 /* The dir. and files list */
463 hbox
= gnt_hbox_new(FALSE
);
464 gnt_box_set_pad(GNT_BOX(hbox
), 0);
466 gnt_box_add_widget(GNT_BOX(hbox
), sel
->dirs
);
468 if (!sel
->dirsonly
) {
469 gnt_box_add_widget(GNT_BOX(hbox
), sel
->files
);
471 g_signal_connect(G_OBJECT(sel
->dirs
), "selection_changed", G_CALLBACK(file_sel_changed
), sel
);
474 gnt_box_add_widget(GNT_BOX(vbox
), hbox
);
475 gnt_box_add_widget(GNT_BOX(vbox
), sel
->location
);
478 hbox
= gnt_hbox_new(FALSE
);
479 gnt_box_add_widget(GNT_BOX(hbox
), sel
->cancel
);
480 gnt_box_add_widget(GNT_BOX(hbox
), sel
->select
);
481 gnt_box_add_widget(GNT_BOX(vbox
), hbox
);
483 gnt_box_add_widget(GNT_BOX(sel
), vbox
);
485 update_location(sel
);
489 toggle_tag_selection(GntBindable
*bind
, GList
*null
)
491 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
497 if (!sel
->multiselect
)
499 tree
= sel
->dirsonly
? sel
->dirs
: sel
->files
;
500 if (!gnt_widget_has_focus(tree
) ||
501 gnt_tree_is_searching(GNT_TREE(tree
)))
504 file
= gnt_tree_get_selection_data(GNT_TREE(tree
));
506 str
= gnt_file_sel_get_selected_file(sel
);
507 if ((find
= g_list_find_custom(sel
->tags
, str
, (GCompareFunc
)g_utf8_collate
)) != NULL
) {
509 sel
->tags
= g_list_delete_link(sel
->tags
, find
);
510 gnt_tree_set_row_flags(GNT_TREE(tree
), file
, GNT_TEXT_FLAG_NORMAL
);
513 sel
->tags
= g_list_prepend(sel
->tags
, str
);
514 gnt_tree_set_row_flags(GNT_TREE(tree
), file
, GNT_TEXT_FLAG_BOLD
);
517 gnt_bindable_perform_action_named(GNT_BINDABLE(tree
), "move-down", NULL
);
523 clear_tags(GntBindable
*bind
, GList
*null
)
525 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
529 if (!sel
->multiselect
)
531 tree
= sel
->dirsonly
? sel
->dirs
: sel
->files
;
532 if (!gnt_widget_has_focus(tree
) ||
533 gnt_tree_is_searching(GNT_TREE(tree
)))
536 g_list_foreach(sel
->tags
, (GFunc
)g_free
, NULL
);
537 g_list_free(sel
->tags
);
540 for (iter
= GNT_TREE(tree
)->list
; iter
; iter
= iter
->next
)
541 gnt_tree_set_row_flags(GNT_TREE(tree
), iter
->data
, GNT_TEXT_FLAG_NORMAL
);
547 up_directory(GntBindable
*bind
, GList
*null
)
550 GntFileSel
*sel
= GNT_FILE_SEL(bind
);
551 if (!gnt_widget_has_focus(sel
->dirs
) &&
552 !gnt_widget_has_focus(sel
->files
))
554 if (gnt_tree_is_searching(GNT_TREE(sel
->dirs
)) ||
555 gnt_tree_is_searching(GNT_TREE(sel
->files
)))
558 path
= g_build_filename(sel
->current
, "..", NULL
);
559 dir
= g_path_get_basename(sel
->current
);
560 if (gnt_file_sel_set_current_location(sel
, path
))
561 gnt_tree_set_selected(GNT_TREE(sel
->dirs
), dir
);
568 gnt_file_sel_size_request(GntWidget
*widget
)
571 if (widget
->priv
.height
> 0)
574 sel
= GNT_FILE_SEL(widget
);
575 sel
->dirs
->priv
.height
= 16;
576 sel
->files
->priv
.height
= 16;
577 orig_size_request(widget
);
581 gnt_file_sel_class_init(GntFileSelClass
*klass
)
583 GntBindableClass
*bindable
= GNT_BINDABLE_CLASS(klass
);
584 GntWidgetClass
*kl
= GNT_WIDGET_CLASS(klass
);
585 parent_class
= GNT_WINDOW_CLASS(klass
);
586 kl
->destroy
= gnt_file_sel_destroy
;
588 kl
->map
= gnt_file_sel_map
;
589 orig_size_request
= kl
->size_request
;
590 kl
->size_request
= gnt_file_sel_size_request
;
592 signals
[SIG_FILE_SELECTED
] =
593 g_signal_new("file_selected",
594 G_TYPE_FROM_CLASS(klass
),
596 G_STRUCT_OFFSET(GntFileSelClass
, file_selected
),
598 gnt_closure_marshal_VOID__STRING_STRING
,
599 G_TYPE_NONE
, 2, G_TYPE_STRING
, G_TYPE_STRING
);
601 gnt_bindable_class_register_action(bindable
, "toggle-tag", toggle_tag_selection
, "t", NULL
);
602 gnt_bindable_class_register_action(bindable
, "clear-tags", clear_tags
, "c", NULL
);
603 gnt_bindable_class_register_action(bindable
, "up-directory", up_directory
, GNT_KEY_BACKSPACE
, NULL
);
604 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass
), GNT_BINDABLE_CLASS(klass
));
610 gnt_file_sel_init(GTypeInstance
*instance
, gpointer
class)
612 GntFileSel
*sel
= GNT_FILE_SEL(instance
);
614 sel
->dirs
= gnt_tree_new();
615 gnt_tree_set_compare_func(GNT_TREE(sel
->dirs
), (GCompareFunc
)g_utf8_collate
);
616 gnt_tree_set_hash_fns(GNT_TREE(sel
->dirs
), g_str_hash
, g_str_equal
, g_free
);
617 gnt_tree_set_column_titles(GNT_TREE(sel
->dirs
), "Directories");
618 gnt_tree_set_show_title(GNT_TREE(sel
->dirs
), TRUE
);
619 gnt_tree_set_col_width(GNT_TREE(sel
->dirs
), 0, 20);
620 g_signal_connect(G_OBJECT(sel
->dirs
), "key_pressed", G_CALLBACK(dir_key_pressed
), sel
);
622 sel
->files
= gnt_tree_new_with_columns(2); /* Name, Size */
623 gnt_tree_set_compare_func(GNT_TREE(sel
->files
), (GCompareFunc
)g_utf8_collate
);
624 gnt_tree_set_column_titles(GNT_TREE(sel
->files
), "Filename", "Size");
625 gnt_tree_set_show_title(GNT_TREE(sel
->files
), TRUE
);
626 gnt_tree_set_col_width(GNT_TREE(sel
->files
), 0, 25);
627 gnt_tree_set_col_width(GNT_TREE(sel
->files
), 1, 10);
628 gnt_tree_set_column_is_right_aligned(GNT_TREE(sel
->files
), 1, TRUE
);
629 g_signal_connect(G_OBJECT(sel
->files
), "selection_changed", G_CALLBACK(file_sel_changed
), sel
);
631 /* The location entry */
632 sel
->location
= gnt_entry_new(NULL
);
633 g_signal_connect(G_OBJECT(sel
->location
), "key_pressed", G_CALLBACK(location_key_pressed
), sel
);
635 sel
->cancel
= gnt_button_new("Cancel");
636 sel
->select
= gnt_button_new("Select");
638 g_signal_connect_swapped(G_OBJECT(sel
->files
), "activate", G_CALLBACK(gnt_widget_activate
), sel
->select
);
639 g_signal_connect(G_OBJECT(sel
->select
), "activate", G_CALLBACK(select_activated_cb
), sel
);
642 /******************************************************************************
644 *****************************************************************************/
646 gnt_file_sel_get_gtype(void)
648 static GType type
= 0;
652 static const GTypeInfo info
= {
653 sizeof(GntFileSelClass
),
654 NULL
, /* base_init */
655 NULL
, /* base_finalize */
656 (GClassInitFunc
)gnt_file_sel_class_init
,
657 NULL
, /* class_finalize */
658 NULL
, /* class_data */
661 gnt_file_sel_init
, /* instance_init */
665 type
= g_type_register_static(GNT_TYPE_WINDOW
,
674 select_activated_cb(GntWidget
*button
, GntFileSel
*sel
)
676 char *path
= gnt_file_sel_get_selected_file(sel
);
677 char *file
= g_path_get_basename(path
);
678 g_signal_emit(sel
, signals
[SIG_FILE_SELECTED
], 0, path
, file
);
683 GntWidget
*gnt_file_sel_new(void)
685 GntWidget
*widget
= g_object_new(GNT_TYPE_FILE_SEL
, NULL
);
689 gboolean
gnt_file_sel_set_current_location(GntFileSel
*sel
, const char *path
)
692 GError
*error
= NULL
;
696 sel
->current
= process_path(path
);
697 if (!location_changed(sel
, &error
)) {
700 g_free(sel
->current
);
702 location_changed(sel
, &error
);
707 update_location(sel
);
711 void gnt_file_sel_set_dirs_only(GntFileSel
*sel
, gboolean dirs
)
713 sel
->dirsonly
= dirs
;
716 gboolean
gnt_file_sel_get_dirs_only(GntFileSel
*sel
)
718 return sel
->dirsonly
;
721 void gnt_file_sel_set_suggested_filename(GntFileSel
*sel
, const char *suggest
)
723 g_free(sel
->suggest
);
724 sel
->suggest
= g_strdup(suggest
);
727 char *gnt_file_sel_get_selected_file(GntFileSel
*sel
)
731 ret
= g_path_get_dirname(gnt_entry_get_text(GNT_ENTRY(sel
->location
)));
733 ret
= g_strdup(gnt_entry_get_text(GNT_ENTRY(sel
->location
)));
738 void gnt_file_sel_set_must_exist(GntFileSel
*sel
, gboolean must
)
740 /*XXX: What do I do with this? */
741 sel
->must_exist
= must
;
744 gboolean
gnt_file_sel_get_must_exist(GntFileSel
*sel
)
746 return sel
->must_exist
;
749 void gnt_file_sel_set_multi_select(GntFileSel
*sel
, gboolean set
)
751 sel
->multiselect
= set
;
754 GList
*gnt_file_sel_get_selected_multi_files(GntFileSel
*sel
)
756 GList
*list
= NULL
, *iter
;
757 char *str
= gnt_file_sel_get_selected_file(sel
);
759 for (iter
= sel
->tags
; iter
; iter
= iter
->next
) {
760 list
= g_list_prepend(list
, g_strdup(iter
->data
));
761 if (g_utf8_collate(str
, iter
->data
)) {
767 list
= g_list_prepend(list
, str
);
768 list
= g_list_reverse(list
);
772 void gnt_file_sel_set_read_fn(GntFileSel
*sel
, gboolean (*read_fn
)(const char *path
, GList
**files
, GError
**error
))
774 sel
->read_fn
= read_fn
;