7e17079e49c4b00c2aa4542419e321e7ca320914
[ardour.git] / gtk2_ardour / library_ui.cc
1 /*
2     Copyright (C) 2000-2003 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <vector>
20 #include <string>
21 #include <cstdlib>
22 #include <cctype>
23 #include <cerrno>
24
25 #include <sys/stat.h>
26 #include <sndfile.h>
27 #include <signal.h>
28 #include <unistd.h>
29
30 #include <pbd/basename.h>
31 #include <pbd/forkexec.h>
32 #include <pbd/ftw.h>
33 #include <gtk--.h>
34 #include <gtk--/fileselection.h>
35 #include <gtkmmext/gtk_ui.h>
36 #include <ardour/audio_library.h>
37 #include <ardour/audioregion.h>
38 #include <ardour/region.h>
39 #include <ardour/session.h>
40 #include <ardour/sndfile_helpers.h>
41 #include <ardour/sndfilesource.h>
42 #include <ardour/utils.h>
43
44 #include <gtkmmext/doi.h>
45
46 #include "ardour_ui.h"
47 #include "public_editor.h"
48 #include "library_ui.h"
49 #include "prompter.h"
50 #include "gui_thread.h"
51
52 #include "i18n.h"
53
54 using namespace std;
55 using namespace ARDOUR;
56 using namespace Gtk;
57 using namespace SigC;
58
59 SoundFileSelector::SoundFileSelector ()
60         : ArdourDialog ("sound file selector"),
61           vbox(false, 4),
62           sfdb_label(_("Soundfile Library")),
63           fs_label(_("Filesystem")),
64           import_box(false, 4),
65           import_btn (X_("foo")), // force a label 
66           split_channels (_("Split Channels"), 0.0),
67           info_box (0)
68 {
69         set_title(_("ardour: soundfile selector"));
70         set_name("SoundFileSelector");
71         set_default_size (500, 400);
72         set_keyboard_input (true);
73
74         add (main_box);
75         main_box.set_border_width (6);
76
77         main_box.pack_start(vbox, true, true);
78         vbox.pack_start(notebook, true, true);
79         vbox.pack_start(import_box, false, false);
80
81         notebook.set_name("SoundFileSelectorNotebook");
82         notebook.append_page(sf_browser, fs_label);
83         notebook.append_page(sfdb_tree, sfdb_label);
84         
85         import_box.set_homogeneous (true);
86         import_box.pack_start(import_btn);
87         import_box.pack_start (split_channels);
88
89         split_channels.set_active(false);
90         split_channels.set_sensitive (false);
91         
92         delete_event.connect (slot (*this, &ArdourDialog::wm_close_event));
93         
94         import_btn.clicked.connect (slot (*this, &SoundFileSelector::import_btn_clicked));
95
96         sfdb_tree.group_selected.connect (slot(*this, &SoundFileSelector::sfdb_group_selected));
97         sfdb_tree.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), true));
98         sf_browser.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), false));
99         sf_browser.member_deselected.connect (bind (slot(*this, &SoundFileSelector::member_deselected), false));
100         sfdb_tree.deselected.connect (slot(*this, &SoundFileSelector::sfdb_deselected));
101         sf_browser.group_selected.connect (slot(*this, &SoundFileSelector::browser_group_selected));
102         notebook.switch_page.connect (slot(*this, &SoundFileSelector::page_switched));
103 }
104
105 SoundFileSelector::~SoundFileSelector()
106 {
107 }
108
109 void
110 SoundFileSelector::import_btn_clicked ()
111 {
112         vector<string> paths;
113
114         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
115         ARDOUR::Session* sess = edit.current_session();
116         if (sess) {
117                 sess->cancel_audition();
118         }
119
120         if (sfdb) {
121                 for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
122                         paths.push_back (Library->get_member_filename (*i));
123                 }
124         } else {
125                 for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
126                         paths.push_back ((*i).str);
127                 }
128         }
129
130         Action (paths, split_channels.get_active()); /* EMIT_SIGNAL */
131         
132         if (sfdb) {
133                 sfdb_tree.clear_selection ();
134         } else {
135                 sf_browser.clear_selection ();
136         }
137
138         if (hide_after_action) {
139                 hide ();
140                 Action.clear();
141         }
142         hide_after_action = false;
143
144 }
145
146 void
147 SoundFileSelector::run (string action, bool multi, bool hide_after)
148 {
149         static_cast<Label*>(import_btn.get_child())->set_text (action);
150         import_btn.set_sensitive(false);
151
152         if (multi) {
153                 split_channels.show ();
154         } else {
155                 split_channels.hide ();
156         }
157
158         multiable = multi;
159         hide_after_action = hide_after;
160
161         set_position (GTK_WIN_POS_MOUSE);
162         ArdourDialog::run ();
163 }
164
165 void
166 SoundFileSelector::hide_import_stuff()
167 {
168         import_box.hide_all();
169 }
170
171 void
172 SoundFileSelector::page_switched(Gtk::Notebook_Helpers::Page* page, guint page_num)
173 {
174         if (page_num == 1) {
175                 sfdb = true;
176                 if (!sfdb_tree.selection.empty()) {
177                         member_selected (sfdb_tree.selection.back(), true);
178                 }
179         } else {
180                 sfdb = false;
181                 if (!sf_browser.selection.empty()) {
182                         member_selected (sf_browser.selection.back().str, false);
183                 }
184         }
185 }
186
187 void
188 SoundFileSelector::sfdb_deselected()
189 {
190         import_btn.set_sensitive(false);
191 }
192
193 void
194 SoundFileSelector::browser_group_selected()
195 {
196         sfdb_group_selected();
197 }
198
199 void
200 SoundFileSelector::sfdb_group_selected()
201 {
202         import_btn.set_sensitive(false);
203         split_channels.set_sensitive(false);
204                 
205         if (info_box) {
206                 delete info_box;
207                 info_box = 0;
208         }
209 }
210
211 void
212 SoundFileSelector::member_selected(string member, bool sfdb)
213 {
214         if (info_box) {
215                 delete info_box;
216                 info_box = 0;
217         }
218
219         if (member.empty()) {
220                 return;
221         }
222
223         try { 
224                 info_box = new SoundFileBox(member, sfdb);
225         } catch (failed_constructor& err) {
226                 /* nothing to do */
227                 return;
228         }
229
230         main_box.pack_start (*info_box, false, false);
231
232         import_btn.set_sensitive (true);
233         
234         if (multiable) {
235                 split_channels.set_sensitive(true);
236         }
237 }
238
239 void
240 SoundFileSelector::member_deselected (bool sfdb)
241 {
242         bool keep_action_available;
243         string last;
244
245         if (info_box) {
246                 delete info_box;
247                 info_box = 0;
248         }
249
250         if (sfdb) {
251                 if ((keep_action_available = !sfdb_tree.selection.empty())) {
252                         last = sfdb_tree.selection.back();
253                 }
254                 
255         } else {
256                 if ((keep_action_available = !sf_browser.selection.empty())) {
257                         last = sf_browser.selection.back().str;
258                 }
259         }
260         
261         if (keep_action_available) {
262
263                 if (info_box) {
264                         delete info_box;
265                         info_box = 0;
266                 }
267
268                 try {
269                         info_box = new SoundFileBox(last, sfdb);
270                 } catch (failed_constructor& err) {
271                         /* nothing to do */
272                         return;
273                 }
274
275                 import_btn.set_sensitive (true);
276
277                 if (multiable) {
278                         split_channels.set_sensitive(true);
279                 }
280
281                 main_box.pack_start(*info_box, false, false);
282         }
283 }
284
285 void
286 SoundFileSelector::get_result (vector<string>& paths, bool& split)
287 {
288         if (sfdb) {
289                 for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
290                         paths.push_back (Library->get_member_filename (*i));
291                 }
292         } else {
293                 for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
294                         paths.push_back ((*i).str);
295                 }
296         }
297
298         split = split_channels.get_active();
299 }
300
301 SoundFileBrowser::SoundFileBrowser()
302         :
303         Gtk::VBox(false, 3)
304 {
305         fs_selector.hide_fileop_buttons();
306         fs_selector.set_filename("/");
307         
308         // This is ugly ugly ugly.  But gtk1 (and gtk2) don't give us any
309         // choice.
310         GtkFileSelection* fs_gtk = fs_selector.gtkobj();
311         file_list= Gtk::wrap((GtkCList*)(fs_gtk->file_list));
312
313         Gtk::VBox* vbox = manage(new Gtk::VBox);
314         Gtk::HBox* tmphbox = manage(new Gtk::HBox);
315         Gtk::OptionMenu* option_menu = Gtk::wrap((GtkOptionMenu*)(fs_gtk->history_pulldown));
316         option_menu->reparent(*tmphbox);
317         vbox->pack_start(*tmphbox, false, false);
318         
319         /* XXX This interface isn't supported in gtkmm.  Redo it with a BoxList&
320         vbox->set_child_packing(*option_menu, false, false); */
321         
322         Gtk::HBox* hbox = manage(new Gtk::HBox);
323         Gtk::ScrolledWindow* dir_scroll = manage(new Gtk::ScrolledWindow);
324         Gtk::ScrolledWindow* file_scroll = manage(new Gtk::ScrolledWindow);
325         dir_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
326         file_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
327         Gtk::CList* dir_list = Gtk::wrap((GtkCList*)(fs_gtk->dir_list));
328
329         dir_list->reparent(*dir_scroll);
330         file_list->reparent(*file_scroll);
331         file_list->set_selection_mode (GTK_SELECTION_MULTIPLE);
332         hbox->pack_start(*dir_scroll);
333         hbox->pack_start(*file_scroll);
334         vbox->pack_start(*hbox, true, true);
335
336         Gtk::VBox* tmpvbox = manage(new Gtk::VBox);
337         
338         Gtk::Label* selection_text = Gtk::wrap((GtkLabel*)(fs_gtk->selection_text));
339         selection_text->reparent(*tmpvbox);
340         Gtk::Entry* selection_entry= Gtk::wrap((GtkEntry*)(fs_gtk->selection_entry));
341         selection_entry->reparent(*tmpvbox);
342         vbox->pack_start(*tmpvbox, false, false);
343         
344         pack_start(*vbox, true, true);
345         
346         dir_list->select_row.connect(slot (*this, &SoundFileBrowser::dir_list_selected));
347         file_list->select_row.connect(slot (*this, &SoundFileBrowser::file_list_selected));
348         file_list->unselect_row.connect(slot (*this, &SoundFileBrowser::file_list_deselected));
349
350         dir_list->set_name("SoundFileBrowserList");
351         file_list->set_name("SoundFileBrowserList");
352 }
353
354 SoundFileBrowser::~SoundFileBrowser()
355 {
356 }
357
358 void
359 SoundFileBrowser::clear_selection ()
360 {
361         file_list->selection().clear ();
362         selection.clear ();
363 }
364
365 void
366 SoundFileBrowser::dir_list_selected(gint row, gint col, GdkEvent* ev)
367 {
368         current_member = "";
369         current_group = "";
370         
371         group_selected(); /* EMIT_SIGNAL */
372 }
373
374 void
375 SoundFileBrowser::file_list_selected(gint row, gint col, GdkEvent* ev)
376 {
377         current_group = "";
378         current_member = fs_selector.get_filename();
379
380         selection.push_back (RowTaggedString (row, current_member));
381         
382         member_selected(safety_check_file(current_member)); /* EMIT_SIGNAL */
383 }
384
385 void
386 SoundFileBrowser::file_list_deselected(gint row, gint col, GdkEvent* ev)
387 {
388         current_group = "";
389         current_member = file_list->cell (row, 0).get_text();
390
391         for (list<RowTaggedString>::iterator i = selection.begin(); i != selection.end(); ++i) {
392                 if ((*i).row == row) {
393                         selection.erase (i);
394                         break;
395                 }
396         }
397
398         member_deselected(); /* EMIT_SIGNAL */
399 }
400
401 string
402 SoundFileBrowser::safety_check_file(string file)
403 {
404         if (file.rfind(".wav") == string::npos &&
405                 file.rfind(".aiff")== string::npos &&
406                 file.rfind(".aif") == string::npos &&
407                 file.rfind(".snd") == string::npos &&
408                 file.rfind(".au")  == string::npos &&
409                 file.rfind(".raw") == string::npos &&
410                 file.rfind(".sf")  == string::npos &&
411                 file.rfind(".cdr") == string::npos &&
412                 file.rfind(".smp") == string::npos &&
413                 file.rfind(".maud")== string::npos &&
414                 file.rfind(".vwe") == string::npos &&
415                 file.rfind(".paf") == string::npos &&
416                 file.rfind(".voc") == string::npos) {
417                 return "";
418         } else {
419                 return file;
420         }
421 }
422
423 static int32_t process_node (const char *file, const struct stat *sb, int32_t flag);
424 static string length2string (const int, const int);
425
426 LibraryTree::LibraryTree ()
427         : Gtk::VBox(false, 3),
428           btn_box_top(true, 4),
429           btn_box_bottom (true, 4),
430           add_btn(_("Add to Library...")),
431           remove_btn(_("Remove...")),
432           find_btn(_("Find...")),
433           folder_btn(_("Add Folder")),
434           files_select(_("Add audio file or directory"))
435 {
436         set_border_width (3);
437
438         pack_start(hbox, true, true);
439         pack_start(btn_box_top, false, false);
440         pack_start(btn_box_bottom, false, false);
441
442         hbox.pack_start(scroll);
443         scroll.set_usize (200, 150);
444         scroll.set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
445         scroll.add_with_viewport(tree);
446         tree.set_selection_mode(GTK_SELECTION_MULTIPLE);
447
448         btn_box_top.pack_start(add_btn);
449         btn_box_top.pack_start(folder_btn);
450         btn_box_top.pack_start(remove_btn);
451
452         btn_box_bottom.pack_start(find_btn);
453
454         remove_btn.set_sensitive (false);
455
456         add_btn.clicked.connect (slot (*this, &LibraryTree::add_btn_clicked));
457         folder_btn.clicked.connect (slot(*this, &LibraryTree::folder_btn_clicked));
458         remove_btn.clicked.connect (slot(*this, &LibraryTree::remove_btn_clicked));
459         find_btn.clicked.connect (slot (*this, &LibraryTree::find_btn_clicked));
460
461         files_select.hide_fileop_buttons();
462         files_select.set_filename("/");
463         files_select.get_ok_button()->clicked.connect (slot ( *this, 
464                                 &LibraryTree::file_ok_clicked));
465         files_select.get_cancel_button()->clicked.connect (slot ( *this, 
466                                 &LibraryTree::file_cancel_clicked));
467
468
469         Library->added_group.connect (slot (*this, &LibraryTree::added_group));
470         Library->removed_group.connect (slot (*this, &LibraryTree::removed_group));
471         Library->added_member.connect (slot (*this, &LibraryTree::added_member));
472         Library->removed_member.connect (slot (*this, &LibraryTree::removed_member));
473         
474         current_group = "";
475         current_member = "";
476
477         populate ();
478 }
479
480 LibraryTree::~LibraryTree ()
481 {
482 }
483
484 void
485 LibraryTree::clear_selection ()
486 {
487         using namespace Gtk::Tree_Helpers;
488         for (SelectionList::iterator i = tree.selection().begin(); i != tree.selection().end(); ++i) {
489                 (*i)->deselect ();
490         }
491         selection.clear ();
492 }
493
494 void
495 LibraryTree::added_group (string group, string parent)
496 {
497         using namespace Gtk;
498         ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_group), group, parent));
499
500         Tree* parent_tree;
501         if (parent.length()) {
502                 parent_tree = uri_mapping[parent]->get_subtree();
503         } else {
504                 parent_tree = &tree;
505         }
506         
507         TreeItem *item = manage(new TreeItem(Library->get_label(group)));
508         Tree_Helpers::ItemList items = parent_tree->tree();
509         Tree_Helpers::ItemList::iterator i = items.begin();
510         
511         list<string> groups;
512         Library->get_groups(groups, parent);
513         list<string>::iterator j = groups.begin();
514
515         while (i != items.end() && j != groups.end()){
516                 if ((cmp_nocase(Library->get_label(group),Library->get_label(*j)) <= 0) || 
517                         !((*i)->get_subtree())){
518                         
519                         break;
520                 }
521                 ++i;
522                 ++j;
523         }
524
525         parent_tree->tree().insert (i, *item);
526         Tree *subtree = manage(new Tree());
527         item->set_subtree (*subtree);
528         item->expand();
529
530         item->select.connect (bind(slot(*this,&LibraryTree::cb_group_select), item, group));
531         
532         uri_mapping.insert(map<string, TreeItem*>::value_type(group, item));
533         uri_parent.insert(map<string,string>::value_type(group, parent));
534
535         subtree->show();
536         item->show();
537         
538         while (gtk_events_pending()){
539                 gtk_main_iteration();
540         }
541 }
542
543 void
544 LibraryTree::removed_group (string group)
545 {
546         ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_group), group));
547         
548         Gtk::TreeItem* group_item = uri_mapping[group];
549
550         Gtk::Tree* parent_tree;
551         if (uri_parent[group].length()) {
552                 parent_tree = uri_mapping[uri_parent[group]]->get_subtree();
553         } else {
554                 parent_tree = &tree;
555         }
556         
557         parent_tree->tree().remove(*group_item);
558         uri_mapping.erase(uri_mapping.find(group));
559         uri_parent.erase(uri_parent.find(group));
560         
561         while (gtk_events_pending()){
562                 gtk_main_iteration();
563         }
564 }
565
566 void
567 LibraryTree::added_member (string member, string parent)
568 {
569         using namespace Gtk;
570
571         ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_member), member, parent));
572
573         Tree* parent_tree;
574         if (parent.length()) {
575                 parent_tree = uri_mapping[parent]->get_subtree();
576         } else {
577                 parent_tree = &tree;
578         }
579         
580         TreeItem *item = manage(new TreeItem(Library->get_label(member)));
581         Tree_Helpers::ItemList items = parent_tree->tree();
582         Tree_Helpers::ItemList::iterator i = items.begin();
583         
584         list<string> members;
585         Library->get_members(members, parent);
586         list<string>::iterator j = members.begin();
587
588         while (i != items.end() && j != members.end()){
589                 if (cmp_nocase(Library->get_label(member), Library->get_label(*j)) <= 0){
590                         break;
591                 }
592                 ++i;
593                 ++j;
594         }
595         
596         parent_tree->tree().insert (i, *item);
597
598         item->select.connect 
599                         (bind(slot(*this,&LibraryTree::cb_member_select), item, member));
600         item->deselect.connect 
601                         (bind(slot(*this,&LibraryTree::cb_member_deselect), item, member));
602         
603         uri_mapping.insert(map<string, TreeItem*>::value_type(member, item));
604         uri_parent.insert(map<string,string>::value_type(member, parent));
605
606         item->show();
607         
608         while (gtk_events_pending()){
609                 gtk_main_iteration();
610         }
611 }
612
613 void
614 LibraryTree::removed_member (string member)
615 {
616         ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_member), member));
617         
618         Gtk::TreeItem* member_item = uri_mapping[member];
619
620         Gtk::Tree* parent_tree;
621         if (uri_parent[member].length()) {
622                 parent_tree = uri_mapping[uri_parent[member]]->get_subtree();
623         } else {
624                 parent_tree = &tree;
625         }
626         
627         parent_tree->tree().remove(*member_item);
628         uri_mapping.erase(uri_mapping.find(member));
629         uri_parent.erase(uri_parent.find(member));
630         
631         while (gtk_events_pending()){
632                 gtk_main_iteration();
633         }
634 }
635
636 void
637 LibraryTree::populate ()
638 {
639         subpopulate (&tree, current_group);
640 }
641
642 void
643 LibraryTree::subpopulate (Gtk::Tree* tree, string group)
644 {
645         using namespace Gtk;
646
647         list<string> groups;
648         Library->get_groups(groups, group);
649         
650         list<string>::iterator i; 
651
652         for (i = groups.begin(); i != groups.end(); ++i) {
653                 TreeItem *item = 
654                         manage(new TreeItem(Library->get_label(*i)));
655                 tree->append (*item);
656                 Tree *subtree = manage(new Tree());
657                 item->set_subtree (*subtree);
658         
659                 uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
660                 uri_parent.insert(map<string,string>::value_type(*i, group));
661
662                 item->select.connect 
663                                 (bind(slot(*this,&LibraryTree::cb_group_select), item, *i));
664
665                 subpopulate (subtree, *i);
666                 subtree->show();
667                 item->expand();
668                 item->show();
669         }
670
671         list<string> members;
672         Library->get_members(members, group);
673         for (i = members.begin(); i != members.end(); ++i) {
674                 TreeItem *item = manage(new TreeItem(Library->get_label(*i)));
675                 tree->append (*item);
676                 item->show();
677
678                 uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
679                 uri_parent.insert(map<string,string>::value_type(*i, group));
680
681                 item->select.connect 
682                                 (bind(slot(*this,&LibraryTree::cb_member_select), item, *i));
683                 item->deselect.connect 
684                         (bind(slot(*this,&LibraryTree::cb_member_deselect), item, *i));
685
686         }
687 }
688
689 void
690 LibraryTree::add_btn_clicked ()
691 {
692         files_select.show_all();
693 }
694
695 // Gah, too many globals
696 static string parent_uri;
697 static vector<string>* old_parent;
698 static vector<string>* old_parent_uri;
699
700 static void clone_ftw(void*);
701 static int32_t ftw_return;
702 static Gtk::ProgressBar* bar;
703
704 void
705 LibraryTree::file_ok_clicked ()
706 {
707         files_select.hide_all();
708
709         string* file = new string(files_select.get_filename());
710         parent_uri = current_group;
711
712         Gtk::Window* progress_win = new Gtk::Window();
713         progress_win->set_title(_("Importing"));
714         progress_win->set_policy(false, false, true);
715         Gtk::VBox* main_box = manage(new Gtk::VBox());
716         progress_win->add(*main_box);
717         bar = manage(new Gtk::ProgressBar());
718         bar->set_activity_mode(true);
719         bar->set_activity_step(15);
720         bar->set_activity_blocks(10);
721         main_box->pack_start(*bar);
722         Gtk::Button* cancel_btn = manage(new Gtk::Button(_("Cancel")));
723         main_box->pack_start(*cancel_btn);
724         cancel_btn->clicked.connect (slot (*this, &LibraryTree::cancel_import_clicked));
725         progress_win->show_all();
726         
727         clone_ftw((void*)file);
728         
729         delete progress_win;
730 }
731
732 void
733 LibraryTree::cancel_import_clicked()
734 {
735         ftw_return = 1;
736 }
737
738 void
739 clone_ftw(void* ptr)
740 {
741         string* file = (string*) ptr;
742         
743         old_parent = new vector<string>;
744         old_parent_uri = new vector<string>;
745         ftw_return = 0;
746
747         if (ftw (file->c_str(), process_node, 100) < 0){
748                 warning << compose(_("%1 not added to database"), *file) << endmsg;
749         }
750
751         delete old_parent;
752         delete old_parent_uri;
753
754         delete file;
755 }
756
757 void
758 LibraryTree::file_cancel_clicked ()
759 {
760         files_select.hide_all();
761 }
762
763 void
764 LibraryTree::folder_btn_clicked ()
765 {
766         ArdourPrompter prompter (true);
767         prompter.set_prompt(_("Folder name:"));
768
769         prompter.done.connect(Gtk::Main::quit.slot());
770         prompter.show_all();
771
772         Gtk::Main::run();
773
774         if (prompter.status == Gtkmmext::Prompter::entered) {
775                 string name;
776
777                 prompter.get_result(name);
778
779                 if (name.length()) {
780                         Library->add_group(name, current_group);
781                 }
782         }
783 }
784
785 void
786 LibraryTree::cb_group_select (Gtk::TreeItem* item, string uri)
787 {
788         current_group = uri;
789         current_member = "";
790         remove_btn.set_sensitive(true);
791
792         group_selected(); /* EMIT_SIGNAL */
793 }
794
795 void
796 LibraryTree::cb_member_select (Gtk::TreeItem* item, string uri)
797 {
798         current_member = uri;
799         current_group = "";
800         selection.push_back (uri);
801         member_selected(uri); /* EMIT_SIGNAL */
802         remove_btn.set_sensitive(true);
803 }
804
805 void
806 LibraryTree::cb_member_deselect (Gtk::TreeItem* item, string uri)
807 {
808         current_member = "";
809         current_group = "";
810         selection.remove (uri);
811
812         member_deselected(); /* EMIT_SIGNAL */
813 }
814
815 void
816 LibraryTree::find_btn_clicked ()
817 {
818         SearchSounds* search = new SearchSounds ();
819
820         search->file_chosen.connect(slot (*this, &LibraryTree::file_found));
821         search->show_all();
822 }
823
824 void
825 LibraryTree::file_found (string uri, bool multi)
826 {
827         file_chosen (Library->get_member_filename(uri), multi); /* EMIT_SIGNAL */
828 }
829
830 void
831 LibraryTree::remove_btn_clicked ()
832 {
833         if (current_member != ""){
834                 Library->remove_member(current_member);
835         } else if (current_group != ""){
836                 Library->remove_group(current_group);
837         } else {
838                 error << _("Should not be reached") << endmsg;
839         }
840
841         current_member = "";
842         current_group = "";
843         
844         remove_btn.set_sensitive(false);
845         
846         deselected(); /* EMIT_SIGNAL */
847 }
848
849 string
850 length2string (const int32_t frames, const int32_t sample_rate)
851 {
852         int secs = (int) (frames / (float) sample_rate);
853         int hrs =  secs / 3600;
854         secs -= (hrs * 3600);
855         int mins = secs / 60;
856         secs -= (mins * 60);
857
858         int total_secs = (hrs * 3600) + (mins * 60) + secs;
859         int frames_remaining = frames - (total_secs * sample_rate);
860         float fractional_secs = (float) frames_remaining / sample_rate;
861
862         char duration_str[32];
863         sprintf (duration_str, "%02d:%02d:%05.2f", hrs, mins, (float) secs + fractional_secs);
864
865         return duration_str;
866 }
867
868 int
869 process_node (const char *file, const struct stat *sb, int32_t flag)
870 {
871         bar->set_value(0.0);
872         while (gtk_events_pending()){
873                 gtk_main_iteration();
874         }
875         bar->set_value(1.0);
876
877         string s_file(file);
878
879         if (s_file.find("/.") != string::npos){
880                 return ftw_return;
881         }
882
883         if (flag == FTW_D) {
884                 string::size_type size = s_file.find_last_of('/');
885                 string label = s_file.substr(++size);
886                 
887                 while (!old_parent->empty() 
888                         && (s_file.find(old_parent->back()) == string::npos)) {
889
890                         parent_uri = old_parent_uri->back();                    
891                         old_parent_uri->pop_back();
892                         old_parent->pop_back();
893                 }
894
895                 string uri = Library->add_group(label, parent_uri);
896
897                 old_parent->push_back(s_file);
898                 old_parent_uri->push_back(parent_uri);
899                 parent_uri = uri;
900
901                 return ftw_return;
902         } else if (flag != FTW_F) {
903                 return ftw_return;
904         }
905
906         // We can't realistically check every file - or can we ?
907         char* suffix;
908         if ((suffix = strrchr (file, '.')) == 0) {
909                 return ftw_return;
910         }
911
912         if (*(suffix+1) == '\0') {
913                 return ftw_return;
914         }
915
916     if (strcasecmp (suffix+1, "wav") != 0 &&
917         strcasecmp (suffix+1, "aiff") != 0 &&
918         strcasecmp (suffix+1, "aif") != 0 &&
919         strcasecmp (suffix+1, "snd") != 0 &&
920         strcasecmp (suffix+1, "au") != 0 &&
921         strcasecmp (suffix+1, "raw") != 0 &&
922         strcasecmp (suffix+1, "sf") != 0 &&
923         strcasecmp (suffix+1, "cdr") != 0 &&
924         strcasecmp (suffix+1, "smp") != 0 &&
925         strcasecmp (suffix+1, "maud") != 0 &&
926         strcasecmp (suffix+1, "vwe") != 0 &&
927                 strcasecmp (suffix+1, "paf") != 0 &&
928                 strcasecmp (suffix+1, "voc") != 0) {
929
930         return ftw_return;
931     }
932
933         /* OK, it stands a good chance of being a sound file that we
934            might be able to handle.
935         */
936
937         SNDFILE *sf;
938         SF_INFO info;
939         if ((sf = sf_open ((char *) file, SFM_READ, &info)) < 0) {
940                 error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
941                 return ftw_return;
942         }
943         sf_close (sf);
944
945         string uri = Library->add_member(file, parent_uri);
946
947         Library->set_field(uri, "channels", compose("%1", info.channels));
948         Library->set_field(uri, "samplerate", compose("%1", info.samplerate));
949         Library->set_field(uri, "resolution", compose("%1", sndfile_data_width(info.format)));
950         Library->set_field(uri, "format", compose("%1", info.format));
951         
952         return ftw_return;
953 }
954
955 static const gchar* selector_titles[] = {
956         N_("Field"), 
957         N_("Value"), 
958         0
959 };
960
961 SoundFileBox::SoundFileBox (string uri, bool meta)
962         :
963         uri(uri),
964         metadata(meta),
965         sf_info(new SF_INFO),
966         current_pid(0),
967         fields(_fields_refiller, this, internationalize (selector_titles), 
968                    false, true),
969         main_box (false, 3),
970         top_box (true, 4),
971         bottom_box (true, 4),
972         play_btn(_("Play")),
973         stop_btn(_("Stop")),
974         add_field_btn(_("Add Field...")),
975         remove_field_btn(_("Remove Field"))
976 {
977         set_name ("SoundFileBox");
978
979         border_frame.set_label (_("Soundfile Info"));
980         border_frame.add (main_box);
981
982         pack_start (border_frame);
983         set_border_width (4);
984
985         Gtk::HBox* path_box = manage (new HBox);
986         
987         path_box->set_spacing (4);
988         path_box->pack_start (path, false, false);
989         path_box->pack_start (path_entry, true, true);
990
991         main_box.set_border_width (4);
992
993         main_box.pack_start(label, false, false);
994         main_box.pack_start(*path_box, false, false);
995         main_box.pack_start(length, false, false);
996         main_box.pack_start(format, false, false);
997         main_box.pack_start(channels, false, false);
998         main_box.pack_start(samplerate, false, false);
999         if (metadata){
1000                 main_box.pack_start(fields, true, true);
1001                 main_box.pack_start(top_box, false, false);
1002         }
1003         main_box.pack_start(bottom_box, false, false);
1004
1005         fields.set_usize(200, 150);
1006
1007         top_box.set_homogeneous(true);
1008         top_box.pack_start(add_field_btn);
1009         top_box.pack_start(remove_field_btn);
1010
1011         remove_field_btn.set_sensitive(false);
1012
1013         bottom_box.set_homogeneous(true);
1014         bottom_box.pack_start(play_btn);
1015         bottom_box.pack_start(stop_btn);
1016
1017         play_btn.clicked.connect (slot (*this, &SoundFileBox::play_btn_clicked));
1018         stop_btn.clicked.connect (slot (*this, &SoundFileBox::stop_btn_clicked));
1019
1020         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1021         ARDOUR::Session* sess = edit.current_session();
1022         if (!sess) {
1023                 play_btn.set_sensitive(false);
1024         } else {
1025                 sess->AuditionActive.connect(slot (*this, &SoundFileBox::audition_status_changed));
1026         }
1027
1028         add_field_btn.clicked.connect 
1029                         (slot (*this, &SoundFileBox::add_field_clicked));
1030         remove_field_btn.clicked.connect 
1031                         (slot (*this, &SoundFileBox::remove_field_clicked));
1032
1033         fields.selection_made.connect (slot (*this, &SoundFileBox::field_selected));
1034         fields.choice_made.connect (slot (*this, &SoundFileBox::field_chosen));
1035         
1036         Library->fields_changed.connect (slot (*this, &SoundFileBox::setup_fields));
1037
1038         if (setup_labels (uri)) {
1039                 throw failed_constructor();
1040         }
1041         
1042         show_all();
1043         stop_btn.hide();
1044 }
1045
1046 SoundFileBox::~SoundFileBox ()
1047 {
1048 }
1049
1050 void 
1051 SoundFileBox::_fields_refiller (Gtk::CList &list, void* arg)
1052 {
1053         ((SoundFileBox *) arg)->fields_refiller (list);
1054 }
1055
1056 void
1057 SoundFileBox::fields_refiller (Gtk::CList &clist)
1058 {       
1059         if (metadata) {
1060                 list<string> flist;
1061                 Library->get_fields(flist);
1062                 list<string>::iterator i;
1063         
1064                 const gchar *rowdata[3];
1065                 guint row = 0;
1066                 for (i=flist.begin(); i != flist.end(); ++i, ++row){
1067                         if (*i != "channels" && *i != "samplerate" && 
1068                                 *i != "resolution" && *i != "format") {
1069
1070                                 rowdata[0] = (*i).c_str();
1071                                 rowdata[1] = Library->get_field(uri, *i).c_str();
1072                                 clist.insert_row (row, rowdata);
1073                                 ++row;
1074                         }
1075                 }
1076
1077                 clist.column(0).set_auto_resize(true);
1078                 clist.set_sort_column (0);
1079                 clist.sort ();
1080         }
1081 }
1082
1083 int
1084 SoundFileBox::setup_labels (string uri)
1085 {
1086         SNDFILE *sf;
1087
1088         string file;
1089         if (metadata){
1090                 file = Library->get_member_filename(uri);
1091         } else {
1092                 file = uri;
1093         }
1094
1095         if ((sf = sf_open ((char *) file.c_str(), SFM_READ, sf_info)) < 0) {
1096                 error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
1097                 return -1;
1098         }
1099         
1100         if (sf_info->frames == 0 &&
1101             sf_info->channels == 0 &&
1102             sf_info->samplerate == 0 &&
1103             sf_info->format == 0 &&
1104             sf_info->sections == 0) {
1105                 /* .. ok, its not a sound file */
1106                 error << compose(_("file \"%1\" appears not to be an audio file"), file) << endmsg;
1107                 return -1;
1108         }
1109
1110         if (metadata) {
1111                 label.set_alignment (0.0f, 0.0f);
1112                 label.set_text ("Label: " + Library->get_label(uri));
1113         }
1114
1115         path.set_text ("Path: ");
1116
1117         path_entry.set_text (file);
1118         path_entry.set_position (-1);
1119
1120         path_entry.focus_in_event.connect (slot (ARDOUR_UI::generic_focus_in_event));
1121         path_entry.focus_out_event.connect (slot (ARDOUR_UI::generic_focus_out_event));
1122
1123         length.set_alignment (0.0f, 0.0f);
1124         length.set_text (compose("Length: %1", length2string(sf_info->frames, sf_info->samplerate)));
1125
1126         format.set_alignment (0.0f, 0.0f);
1127         format.set_text (compose("Format: %1, %2", 
1128                                  sndfile_major_format(sf_info->format), 
1129                                  sndfile_minor_format(sf_info->format)));
1130         
1131         channels.set_alignment (0.0f, 0.0f);
1132         channels.set_text (compose("Channels: %1", sf_info->channels));
1133         
1134         samplerate.set_alignment (0.0f, 0.0f);
1135         samplerate.set_text (compose("Samplerate: %1", sf_info->samplerate));
1136
1137         return 0;
1138 }
1139
1140 void
1141 SoundFileBox::play_btn_clicked ()
1142 {
1143         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1144         ARDOUR::Session* sess = edit.current_session();
1145         if (!sess) {
1146                 return;
1147         }
1148
1149         sess->cancel_audition();
1150         string file;
1151
1152         if (metadata) {
1153                 file = Library->get_member_filename(uri);
1154         } else {
1155                 file = uri;
1156         }
1157
1158         if (access(file.c_str(), R_OK)) {
1159                 warning << compose(_("Could not read file: %1 (%2)."), file, strerror(errno)) << endmsg;
1160                 return;
1161         }
1162         
1163         static map<string, ARDOUR::AudioRegion*> region_cache;
1164
1165         if (region_cache.find (file) == region_cache.end()) {
1166
1167                 AudioRegion::SourceList srclist;
1168                 SndFileSource* sfs;
1169                 
1170                 for (int n=0; n < sf_info->channels; ++n) {
1171                         
1172                         try {
1173                                 sfs =  new SndFileSource(file+":"+compose("%1", n), false);
1174                                 srclist.push_back(sfs);
1175                                 
1176                         } catch (failed_constructor& err) {
1177                                 error << _("Could not access soundfile: ") << file << endmsg;
1178                                 return;
1179                         }
1180                 }
1181
1182                 if (srclist.empty()) {
1183                         return;
1184                 }
1185                 
1186                 string result;
1187                 sess->region_name (result, PBD::basename(srclist[0]->name()), false);
1188                 AudioRegion* a_region = new AudioRegion(srclist, 0, srclist[0]->length(), result, 0, Region::DefaultFlags, false);
1189                 region_cache[file] = a_region;
1190         } 
1191
1192         play_btn.hide();
1193         stop_btn.show();
1194
1195         sess->audition_region(*region_cache[file]);
1196 }
1197
1198 void
1199 SoundFileBox::stop_btn_clicked ()
1200 {
1201         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1202         ARDOUR::Session* sess = edit.current_session();
1203         if (sess) {
1204                 sess->cancel_audition();
1205                 play_btn.show();
1206                 stop_btn.hide();
1207         }
1208 }
1209
1210 void
1211 SoundFileBox::audition_status_changed (bool active)
1212 {
1213         if (!active) {
1214                 Gtkmmext::UI::instance()->call_slot( slot(*this, &SoundFileBox::stop_btn_clicked));
1215         }
1216 }
1217
1218 void
1219 SoundFileBox::add_field_clicked ()
1220 {
1221         ArdourPrompter prompter (true);
1222         prompter.set_prompt(_("Field name:"));
1223         
1224         prompter.show_all();
1225         prompter.done.connect(Gtk::Main::quit.slot());
1226         
1227         Gtk::Main::run();
1228         
1229         if (prompter.status == Gtkmmext::Prompter::entered) {
1230                 string name;
1231                 
1232                 prompter.get_result(name);
1233                 
1234                 if (name.length()) {
1235                         Library->add_field(name);
1236                 }
1237         }
1238 }
1239
1240 void
1241 SoundFileBox::remove_field_clicked ()
1242 {
1243         Library->remove_field(selected_field);
1244         selected_field = "";
1245         remove_field_btn.set_sensitive(false);
1246 }
1247
1248 void
1249 SoundFileBox::setup_fields ()
1250 {
1251         ENSURE_GUI_THREAD(slot (*this, &SoundFileBox::setup_fields));
1252         
1253         fields.rescan();
1254 }
1255
1256 void
1257 SoundFileBox::field_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1258 {
1259         if (res) {
1260                 remove_field_btn.set_sensitive(true);
1261                 selected_field = selector->clist().row(res->row)[0].get_text();
1262         }
1263 }
1264
1265 void
1266 SoundFileBox::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1267 {       
1268         if (!res){
1269                 return;
1270         }
1271
1272         string field_name(selector->clist().row(res->row)[0].get_text());
1273         
1274         ArdourPrompter prompter (true);
1275         prompter.set_prompt(_("Field value:"));
1276         prompter.set_initial_text (selector->clist().row(res->row)[1].get_text());
1277         
1278         prompter.show_all();
1279         prompter.done.connect(Gtk::Main::quit.slot());
1280
1281         Gtk::Main::run();
1282         
1283         if (prompter.status == Gtkmmext::Prompter::entered) {
1284                 string data;
1285
1286                 prompter.get_result(data);
1287                 Library->set_field(uri, field_name, data);
1288         }
1289
1290         fields.rescan();
1291 }
1292
1293 SearchSounds::SearchSounds ()
1294         : ArdourDialog ("search sounds dialog"),
1295           find_btn (_("Find")),
1296           and_rbtn (_("AND")),
1297           or_rbtn (_("OR")),
1298           fields(_fields_refiller, this, internationalize(selector_titles))
1299 {
1300         set_title (_("ardour: locate soundfiles"));
1301         set_name ("AudioSearchSound");
1302
1303         add(main_box);
1304
1305         or_rbtn.set_group(and_rbtn.group());
1306         or_rbtn.set_active(true);
1307         rbtn_box.set_homogeneous(true);
1308         rbtn_box.pack_start(and_rbtn);
1309         rbtn_box.pack_start(or_rbtn);
1310
1311         bottom_box.set_homogeneous(true);
1312         bottom_box.pack_start(find_btn);
1313
1314         fields.set_usize(200, 150);
1315
1316         main_box.pack_start(fields);
1317         main_box.pack_start(rbtn_box, false, false);
1318         main_box.pack_start(bottom_box, false, false);
1319
1320         delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));
1321
1322         find_btn.clicked.connect (slot (*this, &SearchSounds::find_btn_clicked));
1323         fields.selection_made.connect (slot 
1324                                                                    (*this, &SearchSounds::field_selected));
1325
1326         show_all();
1327 }
1328
1329 SearchSounds::~SearchSounds ()
1330 {
1331 }
1332
1333 void 
1334 SearchSounds::_fields_refiller (Gtk::CList &list, void* arg)
1335 {
1336         ((SearchSounds *) arg)->fields_refiller (list);
1337 }
1338
1339 void
1340 SearchSounds::fields_refiller (Gtk::CList &clist)
1341 {       
1342         list<string> flist;
1343         Library->get_fields(flist);
1344         list<string>::iterator i;
1345         
1346         const gchar *rowdata[3];
1347         guint row;
1348         for (row=0,i=flist.begin(); i != flist.end(); ++i, ++row){
1349                 rowdata[0] = (*i).c_str();
1350                 rowdata[1] = "";
1351                 clist.insert_row (row, rowdata);
1352         }
1353
1354         clist.column(0).set_auto_resize(true);
1355         clist.set_sort_column (0);
1356         clist.sort ();
1357 }
1358
1359 void
1360 SearchSounds::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1361 {       
1362         if (!res){
1363                 return;
1364         }
1365         
1366         ArdourPrompter prompter (true);
1367         prompter.set_prompt(_("Field value:"));
1368         
1369         prompter.show_all();
1370         prompter.done.connect(Gtk::Main::quit.slot());
1371
1372         Gtk::Main::run();
1373         
1374         if (prompter.status == Gtkmmext::Prompter::entered) {
1375                 string data;
1376
1377                 prompter.get_result(data);
1378                 selector->clist().cell(res->row, 1).set_text(data);
1379         }
1380 }
1381
1382 void
1383 SearchSounds::find_btn_clicked ()
1384 {
1385         using namespace Gtk::CList_Helpers;
1386         typedef map<string,string> StringMap;
1387
1388         StringMap search_info;
1389
1390         RowList rows = fields.clist().rows();
1391         RowList::const_iterator i;
1392
1393         for (i = rows.begin(); i != rows.end(); ++i) {
1394                 Cell cfield((*i)[0]);
1395                 Cell cdata((*i)[1]);
1396                 if (cdata.get_text().length()){
1397                         search_info.insert(
1398                                 StringMap::value_type(cfield.get_text(), cdata.get_text()));
1399                 }
1400         }
1401
1402         SearchResults* results;
1403         if (and_rbtn.get_active()){
1404                 results = new SearchResults(search_info, true);
1405         } else {
1406                 results = new SearchResults(search_info, false);
1407         }
1408
1409         results->file_chosen.connect (slot (*this, &SearchSounds::file_found));
1410         results->show_all();
1411 }
1412
1413 void
1414 SearchSounds::file_found (string uri, bool multi)
1415 {
1416         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1417         ARDOUR::Session* sess = edit.current_session();
1418         if (sess) {
1419                 sess->cancel_audition();
1420         }
1421
1422         file_chosen (uri, multi); /* EMIT_SIGNAL */
1423 }
1424
1425 static const gchar* result_titles[] = { 
1426         N_("Results"), 
1427         N_("Uris"), 
1428         0
1429 };
1430
1431 SearchResults::SearchResults (map<string,string> field_values, bool and_search)
1432         : ArdourDialog ("search results dialog"),
1433           search_info(field_values),
1434           search_and (and_search),
1435           selection (""),
1436           main_box (false, 3),
1437           import_box (true, 4),
1438           import_btn (_("Import")),
1439           multichan_check (_("Create multi-channel region")),
1440           results (_results_refiller, this, internationalize (result_titles), false, true)
1441 {
1442         set_title (_("Ardour: Search Results"));
1443         set_name ("ArdourSearchResults");
1444         
1445         add(main_box);
1446         set_border_width (3);
1447         
1448         main_box.pack_start(hbox);
1449         hbox.pack_start(results);
1450
1451         main_box.pack_start(import_box, false, false);
1452         
1453         results.set_usize (200, 150);
1454         
1455         import_box.set_homogeneous(true);
1456         import_box.pack_start(import_btn);
1457         import_box.pack_start(multichan_check);
1458
1459         import_btn.set_sensitive(false);
1460         multichan_check.set_active(true);
1461         multichan_check.set_sensitive(false);
1462         
1463         delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));
1464
1465         import_btn.clicked.connect (slot (*this, &SearchResults::import_clicked));
1466
1467         results.choice_made.connect (slot (*this, &SearchResults::result_chosen));
1468
1469         show_all();
1470 }
1471
1472 SearchResults::~SearchResults ()
1473 {
1474 }
1475
1476 void 
1477 SearchResults::_results_refiller (Gtk::CList &list, void* arg)
1478 {
1479         ((SearchResults *) arg)->results_refiller (list);
1480 }
1481
1482 void 
1483 SearchResults::results_refiller (Gtk::CList &clist)
1484 {
1485         list<string> results;
1486         if (search_and) {
1487                 Library->search_members_and (results, search_info);
1488         } else {
1489                 Library->search_members_or (results, search_info);
1490         }
1491
1492         list<string>::iterator i;
1493         const gchar *rowdata[3];
1494         guint row;
1495         for (row=0, i=results.begin(); i != results.end(); ++i, ++row){
1496                 rowdata[0] = (Library->get_label(*i)).c_str();
1497                 // hide the uri in a hidden column
1498                 rowdata[1] = (*i).c_str();
1499                 clist.insert_row (row, rowdata);
1500         }
1501
1502         clist.column(1).set_visiblity(false);
1503         clist.column(0).set_auto_resize(true);
1504         clist.set_sort_column (0);
1505         clist.sort ();
1506 }
1507
1508 void
1509 SearchResults::import_clicked ()
1510 {
1511         PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1512         ARDOUR::Session* sess = edit.current_session();
1513
1514         if (sess) {
1515                 sess->cancel_audition();
1516         }
1517
1518         file_chosen(selection, multichan_check.get_active()); /* EMIT_SIGNAL */
1519 }
1520
1521 void 
1522 SearchResults::result_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1523 {
1524         if (res) {
1525                 selection = selector->clist().row(res->row)[1].get_text();
1526
1527                 if (info_box){
1528                         delete info_box;
1529                         info_box = 0;
1530                 }
1531         
1532                 try {
1533                         info_box = new SoundFileBox(selection, true);
1534                 } catch (failed_constructor& err) {
1535                         /* nothing to do */
1536                         return;
1537                 }
1538
1539                 hbox.pack_start(*info_box);
1540         }
1541 }