2 Copyright (C) 2000-2003 Paul Davis
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.
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.
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.
30 #include <pbd/basename.h>
31 #include <pbd/forkexec.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>
44 #include <gtkmmext/doi.h>
46 #include "ardour_ui.h"
47 #include "public_editor.h"
48 #include "library_ui.h"
50 #include "gui_thread.h"
55 using namespace ARDOUR;
59 SoundFileSelector::SoundFileSelector ()
60 : ArdourDialog ("sound file selector"),
62 sfdb_label(_("Soundfile Library")),
63 fs_label(_("Filesystem")),
65 import_btn (X_("foo")), // force a label
66 split_channels (_("Split Channels"), 0.0),
69 set_title(_("ardour: soundfile selector"));
70 set_name("SoundFileSelector");
71 set_default_size (500, 400);
72 set_keyboard_input (true);
75 main_box.set_border_width (6);
77 main_box.pack_start(vbox, true, true);
78 vbox.pack_start(notebook, true, true);
79 vbox.pack_start(import_box, false, false);
81 notebook.set_name("SoundFileSelectorNotebook");
82 notebook.append_page(sf_browser, fs_label);
83 notebook.append_page(sfdb_tree, sfdb_label);
85 import_box.set_homogeneous (true);
86 import_box.pack_start(import_btn);
87 import_box.pack_start (split_channels);
89 split_channels.set_active(false);
90 split_channels.set_sensitive (false);
92 delete_event.connect (slot (*this, &ArdourDialog::wm_close_event));
94 import_btn.clicked.connect (slot (*this, &SoundFileSelector::import_btn_clicked));
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));
105 SoundFileSelector::~SoundFileSelector()
110 SoundFileSelector::import_btn_clicked ()
112 vector<string> paths;
114 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
115 ARDOUR::Session* sess = edit.current_session();
117 sess->cancel_audition();
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));
125 for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
126 paths.push_back ((*i).str);
130 Action (paths, split_channels.get_active()); /* EMIT_SIGNAL */
133 sfdb_tree.clear_selection ();
135 sf_browser.clear_selection ();
138 if (hide_after_action) {
142 hide_after_action = false;
147 SoundFileSelector::run (string action, bool multi, bool hide_after)
149 static_cast<Label*>(import_btn.get_child())->set_text (action);
150 import_btn.set_sensitive(false);
153 split_channels.show ();
155 split_channels.hide ();
159 hide_after_action = hide_after;
161 set_position (GTK_WIN_POS_MOUSE);
162 ArdourDialog::run ();
166 SoundFileSelector::hide_import_stuff()
168 import_box.hide_all();
172 SoundFileSelector::page_switched(Gtk::Notebook_Helpers::Page* page, guint page_num)
176 if (!sfdb_tree.selection.empty()) {
177 member_selected (sfdb_tree.selection.back(), true);
181 if (!sf_browser.selection.empty()) {
182 member_selected (sf_browser.selection.back().str, false);
188 SoundFileSelector::sfdb_deselected()
190 import_btn.set_sensitive(false);
194 SoundFileSelector::browser_group_selected()
196 sfdb_group_selected();
200 SoundFileSelector::sfdb_group_selected()
202 import_btn.set_sensitive(false);
203 split_channels.set_sensitive(false);
212 SoundFileSelector::member_selected(string member, bool sfdb)
219 if (member.empty()) {
224 info_box = new SoundFileBox(member, sfdb);
225 } catch (failed_constructor& err) {
230 main_box.pack_start (*info_box, false, false);
232 import_btn.set_sensitive (true);
235 split_channels.set_sensitive(true);
240 SoundFileSelector::member_deselected (bool sfdb)
242 bool keep_action_available;
251 if ((keep_action_available = !sfdb_tree.selection.empty())) {
252 last = sfdb_tree.selection.back();
256 if ((keep_action_available = !sf_browser.selection.empty())) {
257 last = sf_browser.selection.back().str;
261 if (keep_action_available) {
269 info_box = new SoundFileBox(last, sfdb);
270 } catch (failed_constructor& err) {
275 import_btn.set_sensitive (true);
278 split_channels.set_sensitive(true);
281 main_box.pack_start(*info_box, false, false);
286 SoundFileSelector::get_result (vector<string>& paths, bool& split)
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));
293 for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
294 paths.push_back ((*i).str);
298 split = split_channels.get_active();
301 SoundFileBrowser::SoundFileBrowser()
305 fs_selector.hide_fileop_buttons();
306 fs_selector.set_filename("/");
308 // This is ugly ugly ugly. But gtk1 (and gtk2) don't give us any
310 GtkFileSelection* fs_gtk = fs_selector.gtkobj();
311 file_list= Gtk::wrap((GtkCList*)(fs_gtk->file_list));
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);
319 /* XXX This interface isn't supported in gtkmm. Redo it with a BoxList&
320 vbox->set_child_packing(*option_menu, false, false); */
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));
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);
336 Gtk::VBox* tmpvbox = manage(new Gtk::VBox);
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);
344 pack_start(*vbox, true, true);
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));
350 dir_list->set_name("SoundFileBrowserList");
351 file_list->set_name("SoundFileBrowserList");
354 SoundFileBrowser::~SoundFileBrowser()
359 SoundFileBrowser::clear_selection ()
361 file_list->selection().clear ();
366 SoundFileBrowser::dir_list_selected(gint row, gint col, GdkEvent* ev)
371 group_selected(); /* EMIT_SIGNAL */
375 SoundFileBrowser::file_list_selected(gint row, gint col, GdkEvent* ev)
378 current_member = fs_selector.get_filename();
380 selection.push_back (RowTaggedString (row, current_member));
382 member_selected(safety_check_file(current_member)); /* EMIT_SIGNAL */
386 SoundFileBrowser::file_list_deselected(gint row, gint col, GdkEvent* ev)
389 current_member = file_list->cell (row, 0).get_text();
391 for (list<RowTaggedString>::iterator i = selection.begin(); i != selection.end(); ++i) {
392 if ((*i).row == row) {
398 member_deselected(); /* EMIT_SIGNAL */
402 SoundFileBrowser::safety_check_file(string file)
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) {
423 static int32_t process_node (const char *file, const struct stat *sb, int32_t flag);
424 static string length2string (const int, const int);
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"))
436 set_border_width (3);
438 pack_start(hbox, true, true);
439 pack_start(btn_box_top, false, false);
440 pack_start(btn_box_bottom, false, false);
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);
448 btn_box_top.pack_start(add_btn);
449 btn_box_top.pack_start(folder_btn);
450 btn_box_top.pack_start(remove_btn);
452 btn_box_bottom.pack_start(find_btn);
454 remove_btn.set_sensitive (false);
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));
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));
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));
480 LibraryTree::~LibraryTree ()
485 LibraryTree::clear_selection ()
487 using namespace Gtk::Tree_Helpers;
488 for (SelectionList::iterator i = tree.selection().begin(); i != tree.selection().end(); ++i) {
495 LibraryTree::added_group (string group, string parent)
498 ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_group), group, parent));
501 if (parent.length()) {
502 parent_tree = uri_mapping[parent]->get_subtree();
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();
512 Library->get_groups(groups, parent);
513 list<string>::iterator j = groups.begin();
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())){
525 parent_tree->tree().insert (i, *item);
526 Tree *subtree = manage(new Tree());
527 item->set_subtree (*subtree);
530 item->select.connect (bind(slot(*this,&LibraryTree::cb_group_select), item, group));
532 uri_mapping.insert(map<string, TreeItem*>::value_type(group, item));
533 uri_parent.insert(map<string,string>::value_type(group, parent));
538 while (gtk_events_pending()){
539 gtk_main_iteration();
544 LibraryTree::removed_group (string group)
546 ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_group), group));
548 Gtk::TreeItem* group_item = uri_mapping[group];
550 Gtk::Tree* parent_tree;
551 if (uri_parent[group].length()) {
552 parent_tree = uri_mapping[uri_parent[group]]->get_subtree();
557 parent_tree->tree().remove(*group_item);
558 uri_mapping.erase(uri_mapping.find(group));
559 uri_parent.erase(uri_parent.find(group));
561 while (gtk_events_pending()){
562 gtk_main_iteration();
567 LibraryTree::added_member (string member, string parent)
571 ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_member), member, parent));
574 if (parent.length()) {
575 parent_tree = uri_mapping[parent]->get_subtree();
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();
584 list<string> members;
585 Library->get_members(members, parent);
586 list<string>::iterator j = members.begin();
588 while (i != items.end() && j != members.end()){
589 if (cmp_nocase(Library->get_label(member), Library->get_label(*j)) <= 0){
596 parent_tree->tree().insert (i, *item);
599 (bind(slot(*this,&LibraryTree::cb_member_select), item, member));
600 item->deselect.connect
601 (bind(slot(*this,&LibraryTree::cb_member_deselect), item, member));
603 uri_mapping.insert(map<string, TreeItem*>::value_type(member, item));
604 uri_parent.insert(map<string,string>::value_type(member, parent));
608 while (gtk_events_pending()){
609 gtk_main_iteration();
614 LibraryTree::removed_member (string member)
616 ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_member), member));
618 Gtk::TreeItem* member_item = uri_mapping[member];
620 Gtk::Tree* parent_tree;
621 if (uri_parent[member].length()) {
622 parent_tree = uri_mapping[uri_parent[member]]->get_subtree();
627 parent_tree->tree().remove(*member_item);
628 uri_mapping.erase(uri_mapping.find(member));
629 uri_parent.erase(uri_parent.find(member));
631 while (gtk_events_pending()){
632 gtk_main_iteration();
637 LibraryTree::populate ()
639 subpopulate (&tree, current_group);
643 LibraryTree::subpopulate (Gtk::Tree* tree, string group)
648 Library->get_groups(groups, group);
650 list<string>::iterator i;
652 for (i = groups.begin(); i != groups.end(); ++i) {
654 manage(new TreeItem(Library->get_label(*i)));
655 tree->append (*item);
656 Tree *subtree = manage(new Tree());
657 item->set_subtree (*subtree);
659 uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
660 uri_parent.insert(map<string,string>::value_type(*i, group));
663 (bind(slot(*this,&LibraryTree::cb_group_select), item, *i));
665 subpopulate (subtree, *i);
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);
678 uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
679 uri_parent.insert(map<string,string>::value_type(*i, group));
682 (bind(slot(*this,&LibraryTree::cb_member_select), item, *i));
683 item->deselect.connect
684 (bind(slot(*this,&LibraryTree::cb_member_deselect), item, *i));
690 LibraryTree::add_btn_clicked ()
692 files_select.show_all();
695 // Gah, too many globals
696 static string parent_uri;
697 static vector<string>* old_parent;
698 static vector<string>* old_parent_uri;
700 static void clone_ftw(void*);
701 static int32_t ftw_return;
702 static Gtk::ProgressBar* bar;
705 LibraryTree::file_ok_clicked ()
707 files_select.hide_all();
709 string* file = new string(files_select.get_filename());
710 parent_uri = current_group;
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();
727 clone_ftw((void*)file);
733 LibraryTree::cancel_import_clicked()
741 string* file = (string*) ptr;
743 old_parent = new vector<string>;
744 old_parent_uri = new vector<string>;
747 if (ftw (file->c_str(), process_node, 100) < 0){
748 warning << compose(_("%1 not added to database"), *file) << endmsg;
752 delete old_parent_uri;
758 LibraryTree::file_cancel_clicked ()
760 files_select.hide_all();
764 LibraryTree::folder_btn_clicked ()
766 ArdourPrompter prompter (true);
767 prompter.set_prompt(_("Folder name:"));
769 prompter.done.connect(Gtk::Main::quit.slot());
774 if (prompter.status == Gtkmmext::Prompter::entered) {
777 prompter.get_result(name);
780 Library->add_group(name, current_group);
786 LibraryTree::cb_group_select (Gtk::TreeItem* item, string uri)
790 remove_btn.set_sensitive(true);
792 group_selected(); /* EMIT_SIGNAL */
796 LibraryTree::cb_member_select (Gtk::TreeItem* item, string uri)
798 current_member = uri;
800 selection.push_back (uri);
801 member_selected(uri); /* EMIT_SIGNAL */
802 remove_btn.set_sensitive(true);
806 LibraryTree::cb_member_deselect (Gtk::TreeItem* item, string uri)
810 selection.remove (uri);
812 member_deselected(); /* EMIT_SIGNAL */
816 LibraryTree::find_btn_clicked ()
818 SearchSounds* search = new SearchSounds ();
820 search->file_chosen.connect(slot (*this, &LibraryTree::file_found));
825 LibraryTree::file_found (string uri, bool multi)
827 file_chosen (Library->get_member_filename(uri), multi); /* EMIT_SIGNAL */
831 LibraryTree::remove_btn_clicked ()
833 if (current_member != ""){
834 Library->remove_member(current_member);
835 } else if (current_group != ""){
836 Library->remove_group(current_group);
838 error << _("Should not be reached") << endmsg;
844 remove_btn.set_sensitive(false);
846 deselected(); /* EMIT_SIGNAL */
850 length2string (const int32_t frames, const int32_t sample_rate)
852 int secs = (int) (frames / (float) sample_rate);
853 int hrs = secs / 3600;
854 secs -= (hrs * 3600);
855 int mins = secs / 60;
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;
862 char duration_str[32];
863 sprintf (duration_str, "%02d:%02d:%05.2f", hrs, mins, (float) secs + fractional_secs);
869 process_node (const char *file, const struct stat *sb, int32_t flag)
872 while (gtk_events_pending()){
873 gtk_main_iteration();
879 if (s_file.find("/.") != string::npos){
884 string::size_type size = s_file.find_last_of('/');
885 string label = s_file.substr(++size);
887 while (!old_parent->empty()
888 && (s_file.find(old_parent->back()) == string::npos)) {
890 parent_uri = old_parent_uri->back();
891 old_parent_uri->pop_back();
892 old_parent->pop_back();
895 string uri = Library->add_group(label, parent_uri);
897 old_parent->push_back(s_file);
898 old_parent_uri->push_back(parent_uri);
902 } else if (flag != FTW_F) {
906 // We can't realistically check every file - or can we ?
908 if ((suffix = strrchr (file, '.')) == 0) {
912 if (*(suffix+1) == '\0') {
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) {
933 /* OK, it stands a good chance of being a sound file that we
934 might be able to handle.
939 if ((sf = sf_open ((char *) file, SFM_READ, &info)) < 0) {
940 error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
945 string uri = Library->add_member(file, parent_uri);
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));
955 static const gchar* selector_titles[] = {
961 SoundFileBox::SoundFileBox (string uri, bool meta)
965 sf_info(new SF_INFO),
967 fields(_fields_refiller, this, internationalize (selector_titles),
971 bottom_box (true, 4),
974 add_field_btn(_("Add Field...")),
975 remove_field_btn(_("Remove Field"))
977 set_name ("SoundFileBox");
979 border_frame.set_label (_("Soundfile Info"));
980 border_frame.add (main_box);
982 pack_start (border_frame);
983 set_border_width (4);
985 Gtk::HBox* path_box = manage (new HBox);
987 path_box->set_spacing (4);
988 path_box->pack_start (path, false, false);
989 path_box->pack_start (path_entry, true, true);
991 main_box.set_border_width (4);
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);
1000 main_box.pack_start(fields, true, true);
1001 main_box.pack_start(top_box, false, false);
1003 main_box.pack_start(bottom_box, false, false);
1005 fields.set_usize(200, 150);
1007 top_box.set_homogeneous(true);
1008 top_box.pack_start(add_field_btn);
1009 top_box.pack_start(remove_field_btn);
1011 remove_field_btn.set_sensitive(false);
1013 bottom_box.set_homogeneous(true);
1014 bottom_box.pack_start(play_btn);
1015 bottom_box.pack_start(stop_btn);
1017 play_btn.clicked.connect (slot (*this, &SoundFileBox::play_btn_clicked));
1018 stop_btn.clicked.connect (slot (*this, &SoundFileBox::stop_btn_clicked));
1020 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1021 ARDOUR::Session* sess = edit.current_session();
1023 play_btn.set_sensitive(false);
1025 sess->AuditionActive.connect(slot (*this, &SoundFileBox::audition_status_changed));
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));
1033 fields.selection_made.connect (slot (*this, &SoundFileBox::field_selected));
1034 fields.choice_made.connect (slot (*this, &SoundFileBox::field_chosen));
1036 Library->fields_changed.connect (slot (*this, &SoundFileBox::setup_fields));
1038 if (setup_labels (uri)) {
1039 throw failed_constructor();
1046 SoundFileBox::~SoundFileBox ()
1051 SoundFileBox::_fields_refiller (Gtk::CList &list, void* arg)
1053 ((SoundFileBox *) arg)->fields_refiller (list);
1057 SoundFileBox::fields_refiller (Gtk::CList &clist)
1061 Library->get_fields(flist);
1062 list<string>::iterator i;
1064 const gchar *rowdata[3];
1066 for (i=flist.begin(); i != flist.end(); ++i, ++row){
1067 if (*i != "channels" && *i != "samplerate" &&
1068 *i != "resolution" && *i != "format") {
1070 rowdata[0] = (*i).c_str();
1071 rowdata[1] = Library->get_field(uri, *i).c_str();
1072 clist.insert_row (row, rowdata);
1077 clist.column(0).set_auto_resize(true);
1078 clist.set_sort_column (0);
1084 SoundFileBox::setup_labels (string uri)
1090 file = Library->get_member_filename(uri);
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;
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;
1111 label.set_alignment (0.0f, 0.0f);
1112 label.set_text ("Label: " + Library->get_label(uri));
1115 path.set_text ("Path: ");
1117 path_entry.set_text (file);
1118 path_entry.set_position (-1);
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));
1123 length.set_alignment (0.0f, 0.0f);
1124 length.set_text (compose("Length: %1", length2string(sf_info->frames, sf_info->samplerate)));
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)));
1131 channels.set_alignment (0.0f, 0.0f);
1132 channels.set_text (compose("Channels: %1", sf_info->channels));
1134 samplerate.set_alignment (0.0f, 0.0f);
1135 samplerate.set_text (compose("Samplerate: %1", sf_info->samplerate));
1141 SoundFileBox::play_btn_clicked ()
1143 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1144 ARDOUR::Session* sess = edit.current_session();
1149 sess->cancel_audition();
1153 file = Library->get_member_filename(uri);
1158 if (access(file.c_str(), R_OK)) {
1159 warning << compose(_("Could not read file: %1 (%2)."), file, strerror(errno)) << endmsg;
1163 static map<string, ARDOUR::AudioRegion*> region_cache;
1165 if (region_cache.find (file) == region_cache.end()) {
1167 AudioRegion::SourceList srclist;
1170 for (int n=0; n < sf_info->channels; ++n) {
1173 sfs = new SndFileSource(file+":"+compose("%1", n), false);
1174 srclist.push_back(sfs);
1176 } catch (failed_constructor& err) {
1177 error << _("Could not access soundfile: ") << file << endmsg;
1182 if (srclist.empty()) {
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;
1195 sess->audition_region(*region_cache[file]);
1199 SoundFileBox::stop_btn_clicked ()
1201 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1202 ARDOUR::Session* sess = edit.current_session();
1204 sess->cancel_audition();
1211 SoundFileBox::audition_status_changed (bool active)
1214 Gtkmmext::UI::instance()->call_slot( slot(*this, &SoundFileBox::stop_btn_clicked));
1219 SoundFileBox::add_field_clicked ()
1221 ArdourPrompter prompter (true);
1222 prompter.set_prompt(_("Field name:"));
1224 prompter.show_all();
1225 prompter.done.connect(Gtk::Main::quit.slot());
1229 if (prompter.status == Gtkmmext::Prompter::entered) {
1232 prompter.get_result(name);
1234 if (name.length()) {
1235 Library->add_field(name);
1241 SoundFileBox::remove_field_clicked ()
1243 Library->remove_field(selected_field);
1244 selected_field = "";
1245 remove_field_btn.set_sensitive(false);
1249 SoundFileBox::setup_fields ()
1251 ENSURE_GUI_THREAD(slot (*this, &SoundFileBox::setup_fields));
1257 SoundFileBox::field_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1260 remove_field_btn.set_sensitive(true);
1261 selected_field = selector->clist().row(res->row)[0].get_text();
1266 SoundFileBox::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1272 string field_name(selector->clist().row(res->row)[0].get_text());
1274 ArdourPrompter prompter (true);
1275 prompter.set_prompt(_("Field value:"));
1276 prompter.set_initial_text (selector->clist().row(res->row)[1].get_text());
1278 prompter.show_all();
1279 prompter.done.connect(Gtk::Main::quit.slot());
1283 if (prompter.status == Gtkmmext::Prompter::entered) {
1286 prompter.get_result(data);
1287 Library->set_field(uri, field_name, data);
1293 SearchSounds::SearchSounds ()
1294 : ArdourDialog ("search sounds dialog"),
1295 find_btn (_("Find")),
1296 and_rbtn (_("AND")),
1298 fields(_fields_refiller, this, internationalize(selector_titles))
1300 set_title (_("ardour: locate soundfiles"));
1301 set_name ("AudioSearchSound");
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);
1311 bottom_box.set_homogeneous(true);
1312 bottom_box.pack_start(find_btn);
1314 fields.set_usize(200, 150);
1316 main_box.pack_start(fields);
1317 main_box.pack_start(rbtn_box, false, false);
1318 main_box.pack_start(bottom_box, false, false);
1320 delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));
1322 find_btn.clicked.connect (slot (*this, &SearchSounds::find_btn_clicked));
1323 fields.selection_made.connect (slot
1324 (*this, &SearchSounds::field_selected));
1329 SearchSounds::~SearchSounds ()
1334 SearchSounds::_fields_refiller (Gtk::CList &list, void* arg)
1336 ((SearchSounds *) arg)->fields_refiller (list);
1340 SearchSounds::fields_refiller (Gtk::CList &clist)
1343 Library->get_fields(flist);
1344 list<string>::iterator i;
1346 const gchar *rowdata[3];
1348 for (row=0,i=flist.begin(); i != flist.end(); ++i, ++row){
1349 rowdata[0] = (*i).c_str();
1351 clist.insert_row (row, rowdata);
1354 clist.column(0).set_auto_resize(true);
1355 clist.set_sort_column (0);
1360 SearchSounds::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1366 ArdourPrompter prompter (true);
1367 prompter.set_prompt(_("Field value:"));
1369 prompter.show_all();
1370 prompter.done.connect(Gtk::Main::quit.slot());
1374 if (prompter.status == Gtkmmext::Prompter::entered) {
1377 prompter.get_result(data);
1378 selector->clist().cell(res->row, 1).set_text(data);
1383 SearchSounds::find_btn_clicked ()
1385 using namespace Gtk::CList_Helpers;
1386 typedef map<string,string> StringMap;
1388 StringMap search_info;
1390 RowList rows = fields.clist().rows();
1391 RowList::const_iterator i;
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()){
1398 StringMap::value_type(cfield.get_text(), cdata.get_text()));
1402 SearchResults* results;
1403 if (and_rbtn.get_active()){
1404 results = new SearchResults(search_info, true);
1406 results = new SearchResults(search_info, false);
1409 results->file_chosen.connect (slot (*this, &SearchSounds::file_found));
1410 results->show_all();
1414 SearchSounds::file_found (string uri, bool multi)
1416 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1417 ARDOUR::Session* sess = edit.current_session();
1419 sess->cancel_audition();
1422 file_chosen (uri, multi); /* EMIT_SIGNAL */
1425 static const gchar* result_titles[] = {
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),
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)
1442 set_title (_("Ardour: Search Results"));
1443 set_name ("ArdourSearchResults");
1446 set_border_width (3);
1448 main_box.pack_start(hbox);
1449 hbox.pack_start(results);
1451 main_box.pack_start(import_box, false, false);
1453 results.set_usize (200, 150);
1455 import_box.set_homogeneous(true);
1456 import_box.pack_start(import_btn);
1457 import_box.pack_start(multichan_check);
1459 import_btn.set_sensitive(false);
1460 multichan_check.set_active(true);
1461 multichan_check.set_sensitive(false);
1463 delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));
1465 import_btn.clicked.connect (slot (*this, &SearchResults::import_clicked));
1467 results.choice_made.connect (slot (*this, &SearchResults::result_chosen));
1472 SearchResults::~SearchResults ()
1477 SearchResults::_results_refiller (Gtk::CList &list, void* arg)
1479 ((SearchResults *) arg)->results_refiller (list);
1483 SearchResults::results_refiller (Gtk::CList &clist)
1485 list<string> results;
1487 Library->search_members_and (results, search_info);
1489 Library->search_members_or (results, search_info);
1492 list<string>::iterator i;
1493 const gchar *rowdata[3];
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);
1502 clist.column(1).set_visiblity(false);
1503 clist.column(0).set_auto_resize(true);
1504 clist.set_sort_column (0);
1509 SearchResults::import_clicked ()
1511 PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
1512 ARDOUR::Session* sess = edit.current_session();
1515 sess->cancel_audition();
1518 file_chosen(selection, multichan_check.get_active()); /* EMIT_SIGNAL */
1522 SearchResults::result_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
1525 selection = selector->clist().row(res->row)[1].get_text();
1533 info_box = new SoundFileBox(selection, true);
1534 } catch (failed_constructor& err) {
1539 hbox.pack_start(*info_box);