Logo Search packages:      
Sourcecode: ardour version File versions  Download package

library_ui.cc

/*
    Copyright (C) 2000-2003 Paul Davis

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <vector>
#include <string>
#include <cstdlib>
#include <cctype>
#include <cerrno>

#include <sys/stat.h>
#include <sndfile.h>
#include <signal.h>
#include <unistd.h>

#include <pbd/basename.h>
#include <pbd/forkexec.h>
#include <pbd/ftw.h>
#include <gtk--.h>
#include <gtk--/fileselection.h>
#include <gtkmmext/gtk_ui.h>
#include <ardour/audio_library.h>
#include <ardour/audioregion.h>
#include <ardour/region.h>
#include <ardour/session.h>
#include <ardour/sndfile_helpers.h>
#include <ardour/sndfilesource.h>
#include <ardour/utils.h>

#include <gtkmmext/doi.h>

#include "ardour_ui.h"
#include "public_editor.h"
#include "library_ui.h"
#include "prompter.h"
#include "gui_thread.h"

#include "i18n.h"

using namespace std;
using namespace ARDOUR;
using namespace Gtk;
using namespace SigC;

SoundFileSelector::SoundFileSelector ()
      : ArdourDialog ("sound file selector"),
        vbox(false, 4),
        sfdb_label(_("Soundfile Library")),
        fs_label(_("Filesystem")),
        import_box(false, 4),
        import_btn (X_("foo")), // force a label 
        split_channels (_("Split Channels"), 0.0),
        info_box (0)
{
      set_title(_("ardour: soundfile selector"));
      set_name("SoundFileSelector");
      set_default_size (500, 400);
      set_keyboard_input (true);

      add (main_box);
      main_box.set_border_width (6);

      main_box.pack_start(vbox, true, true);
      vbox.pack_start(notebook, true, true);
      vbox.pack_start(import_box, false, false);

      notebook.set_name("SoundFileSelectorNotebook");
      notebook.append_page(sf_browser, fs_label);
      notebook.append_page(sfdb_tree, sfdb_label);
      
      import_box.set_homogeneous (true);
      import_box.pack_start(import_btn);
      import_box.pack_start (split_channels);

      split_channels.set_active(false);
      split_channels.set_sensitive (false);
      
      delete_event.connect (slot (*this, &ArdourDialog::wm_close_event));
      
      import_btn.clicked.connect (slot (*this, &SoundFileSelector::import_btn_clicked));

      sfdb_tree.group_selected.connect (slot(*this, &SoundFileSelector::sfdb_group_selected));
      sfdb_tree.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), true));
      sf_browser.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), false));
      sf_browser.member_deselected.connect (bind (slot(*this, &SoundFileSelector::member_deselected), false));
      sfdb_tree.deselected.connect (slot(*this, &SoundFileSelector::sfdb_deselected));
      sf_browser.group_selected.connect (slot(*this, &SoundFileSelector::browser_group_selected));
      notebook.switch_page.connect (slot(*this, &SoundFileSelector::page_switched));
}

SoundFileSelector::~SoundFileSelector()
{
}

void
SoundFileSelector::import_btn_clicked ()
{
      vector<string> paths;

      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();
      if (sess) {
            sess->cancel_audition();
      }

      if (sfdb) {
            for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
                  paths.push_back (Library->get_member_filename (*i));
            }
      } else {
            for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
                  paths.push_back ((*i).str);
            }
      }

      Action (paths, split_channels.get_active()); /* EMIT_SIGNAL */
      
      if (sfdb) {
            sfdb_tree.clear_selection ();
      } else {
            sf_browser.clear_selection ();
      }

      if (hide_after_action) {
              hide ();
              Action.clear();
      }
      hide_after_action = false;

}

void
SoundFileSelector::run (string action, bool multi, bool hide_after)
{
      static_cast<Label*>(import_btn.get_child())->set_text (action);
      import_btn.set_sensitive(false);

      if (multi) {
            split_channels.show ();
      } else {
            split_channels.hide ();
      }

      multiable = multi;
      hide_after_action = hide_after;

      set_position (GTK_WIN_POS_MOUSE);
      ArdourDialog::run ();
}

void
SoundFileSelector::hide_import_stuff()
{
      import_box.hide_all();
}

void
SoundFileSelector::page_switched(Gtk::Notebook_Helpers::Page* page, guint page_num)
{
      if (page_num == 1) {
            sfdb = true;
            if (!sfdb_tree.selection.empty()) {
                  member_selected (sfdb_tree.selection.back(), true);
            }
      } else {
            sfdb = false;
            if (!sf_browser.selection.empty()) {
                  member_selected (sf_browser.selection.back().str, false);
            }
      }
}

void
SoundFileSelector::sfdb_deselected()
{
      import_btn.set_sensitive(false);
}

void
SoundFileSelector::browser_group_selected()
{
      sfdb_group_selected();
}

void
SoundFileSelector::sfdb_group_selected()
{
      import_btn.set_sensitive(false);
      split_channels.set_sensitive(false);
            
      if (info_box) {
            delete info_box;
            info_box = 0;
      }
}

void
SoundFileSelector::member_selected(string member, bool sfdb)
{
      if (info_box) {
            delete info_box;
            info_box = 0;
      }

      if (member.empty()) {
            return;
      }

      try { 
            info_box = new SoundFileBox(member, sfdb);
      } catch (failed_constructor& err) {
            /* nothing to do */
            return;
      }

      main_box.pack_start (*info_box, false, false);

      import_btn.set_sensitive (true);
      
      if (multiable) {
            split_channels.set_sensitive(true);
      }
}

void
SoundFileSelector::member_deselected (bool sfdb)
{
      bool keep_action_available;
      string last;

      if (info_box) {
            delete info_box;
            info_box = 0;
      }

      if (sfdb) {
            if ((keep_action_available = !sfdb_tree.selection.empty())) {
                  last = sfdb_tree.selection.back();
            }
            
      } else {
            if ((keep_action_available = !sf_browser.selection.empty())) {
                  last = sf_browser.selection.back().str;
            }
      }
      
      if (keep_action_available) {

            if (info_box) {
                  delete info_box;
                  info_box = 0;
            }

            try {
                  info_box = new SoundFileBox(last, sfdb);
            } catch (failed_constructor& err) {
                  /* nothing to do */
                  return;
            }

            import_btn.set_sensitive (true);

            if (multiable) {
                  split_channels.set_sensitive(true);
            }

            main_box.pack_start(*info_box, false, false);
      }
}

void
SoundFileSelector::get_result (vector<string>& paths, bool& split)
{
      if (sfdb) {
            for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
                  paths.push_back (Library->get_member_filename (*i));
            }
      } else {
            for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
                  paths.push_back ((*i).str);
            }
      }

      split = split_channels.get_active();
}

SoundFileBrowser::SoundFileBrowser()
      :
      Gtk::VBox(false, 3)
{
      fs_selector.hide_fileop_buttons();
      fs_selector.set_filename("/");
      
      // This is ugly ugly ugly.  But gtk1 (and gtk2) don't give us any
      // choice.
      GtkFileSelection* fs_gtk = fs_selector.gtkobj();
      file_list= Gtk::wrap((GtkCList*)(fs_gtk->file_list));

      Gtk::VBox* vbox = manage(new Gtk::VBox);
      Gtk::HBox* tmphbox = manage(new Gtk::HBox);
      Gtk::OptionMenu* option_menu = Gtk::wrap((GtkOptionMenu*)(fs_gtk->history_pulldown));
      option_menu->reparent(*tmphbox);
      vbox->pack_start(*tmphbox, false, false);
      
      /* XXX This interface isn't supported in gtkmm.  Redo it with a BoxList&
      vbox->set_child_packing(*option_menu, false, false); */
      
      Gtk::HBox* hbox = manage(new Gtk::HBox);
      Gtk::ScrolledWindow* dir_scroll = manage(new Gtk::ScrolledWindow);
      Gtk::ScrolledWindow* file_scroll = manage(new Gtk::ScrolledWindow);
      dir_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      file_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      Gtk::CList* dir_list = Gtk::wrap((GtkCList*)(fs_gtk->dir_list));

      dir_list->reparent(*dir_scroll);
      file_list->reparent(*file_scroll);
      file_list->set_selection_mode (GTK_SELECTION_MULTIPLE);
      hbox->pack_start(*dir_scroll);
      hbox->pack_start(*file_scroll);
      vbox->pack_start(*hbox, true, true);

      Gtk::VBox* tmpvbox = manage(new Gtk::VBox);
      
      Gtk::Label* selection_text = Gtk::wrap((GtkLabel*)(fs_gtk->selection_text));
      selection_text->reparent(*tmpvbox);
      Gtk::Entry* selection_entry= Gtk::wrap((GtkEntry*)(fs_gtk->selection_entry));
      selection_entry->reparent(*tmpvbox);
      vbox->pack_start(*tmpvbox, false, false);
      
      pack_start(*vbox, true, true);
      
      dir_list->select_row.connect(slot (*this, &SoundFileBrowser::dir_list_selected));
      file_list->select_row.connect(slot (*this, &SoundFileBrowser::file_list_selected));
      file_list->unselect_row.connect(slot (*this, &SoundFileBrowser::file_list_deselected));

      dir_list->set_name("SoundFileBrowserList");
      file_list->set_name("SoundFileBrowserList");
}

SoundFileBrowser::~SoundFileBrowser()
{
}

void
SoundFileBrowser::clear_selection ()
{
      file_list->selection().clear ();
      selection.clear ();
}

void
SoundFileBrowser::dir_list_selected(gint row, gint col, GdkEvent* ev)
{
      current_member = "";
      current_group = "";
      
      group_selected(); /* EMIT_SIGNAL */
}

void
SoundFileBrowser::file_list_selected(gint row, gint col, GdkEvent* ev)
{
      current_group = "";
      current_member = fs_selector.get_filename();

      selection.push_back (RowTaggedString (row, current_member));
      
      member_selected(safety_check_file(current_member)); /* EMIT_SIGNAL */
}

void
SoundFileBrowser::file_list_deselected(gint row, gint col, GdkEvent* ev)
{
      current_group = "";
      current_member = file_list->cell (row, 0).get_text();

      for (list<RowTaggedString>::iterator i = selection.begin(); i != selection.end(); ++i) {
            if ((*i).row == row) {
                  selection.erase (i);
                  break;
            }
      }

      member_deselected(); /* EMIT_SIGNAL */
}

string
SoundFileBrowser::safety_check_file(string file)
{
      if (file.rfind(".wav") == string::npos &&
            file.rfind(".aiff")== string::npos &&
            file.rfind(".aif") == string::npos &&
            file.rfind(".snd") == string::npos &&
            file.rfind(".au")  == string::npos &&
            file.rfind(".raw") == string::npos &&
            file.rfind(".sf")  == string::npos &&
            file.rfind(".cdr") == string::npos &&
            file.rfind(".smp") == string::npos &&
            file.rfind(".maud")== string::npos &&
            file.rfind(".vwe") == string::npos &&
            file.rfind(".paf") == string::npos &&
            file.rfind(".voc") == string::npos) {
            return "";
      } else {
            return file;
      }
}

static int32_t process_node (const char *file, const struct stat *sb, int32_t flag);
static string length2string (const int, const int);

LibraryTree::LibraryTree ()
      : Gtk::VBox(false, 3),
        btn_box_top(true, 4),
        btn_box_bottom (true, 4),
        add_btn(_("Add to Library...")),
        remove_btn(_("Remove...")),
        find_btn(_("Find...")),
        folder_btn(_("Add Folder")),
        files_select(_("Add audio file or directory"))
{
      set_border_width (3);

      pack_start(hbox, true, true);
      pack_start(btn_box_top, false, false);
      pack_start(btn_box_bottom, false, false);

      hbox.pack_start(scroll);
      scroll.set_usize (200, 150);
      scroll.set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
      scroll.add_with_viewport(tree);
      tree.set_selection_mode(GTK_SELECTION_MULTIPLE);

      btn_box_top.pack_start(add_btn);
      btn_box_top.pack_start(folder_btn);
      btn_box_top.pack_start(remove_btn);

      btn_box_bottom.pack_start(find_btn);

      remove_btn.set_sensitive (false);

      add_btn.clicked.connect (slot (*this, &LibraryTree::add_btn_clicked));
      folder_btn.clicked.connect (slot(*this, &LibraryTree::folder_btn_clicked));
      remove_btn.clicked.connect (slot(*this, &LibraryTree::remove_btn_clicked));
      find_btn.clicked.connect (slot (*this, &LibraryTree::find_btn_clicked));

      files_select.hide_fileop_buttons();
      files_select.set_filename("/");
      files_select.get_ok_button()->clicked.connect (slot ( *this, 
                        &LibraryTree::file_ok_clicked));
      files_select.get_cancel_button()->clicked.connect (slot ( *this, 
                        &LibraryTree::file_cancel_clicked));


      Library->added_group.connect (slot (*this, &LibraryTree::added_group));
      Library->removed_group.connect (slot (*this, &LibraryTree::removed_group));
      Library->added_member.connect (slot (*this, &LibraryTree::added_member));
      Library->removed_member.connect (slot (*this, &LibraryTree::removed_member));
      
      current_group = "";
      current_member = "";

      populate ();
}

LibraryTree::~LibraryTree ()
{
}

void
LibraryTree::clear_selection ()
{
      using namespace Gtk::Tree_Helpers;
      for (SelectionList::iterator i = tree.selection().begin(); i != tree.selection().end(); ++i) {
            (*i)->deselect ();
      }
      selection.clear ();
}

void
LibraryTree::added_group (string group, string parent)
{
      using namespace Gtk;
      ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_group), group, parent));

      Tree* parent_tree;
      if (parent.length()) {
            parent_tree = uri_mapping[parent]->get_subtree();
      } else {
            parent_tree = &tree;
      }
      
      TreeItem *item = manage(new TreeItem(Library->get_label(group)));
      Tree_Helpers::ItemList items = parent_tree->tree();
      Tree_Helpers::ItemList::iterator i = items.begin();
      
      list<string> groups;
      Library->get_groups(groups, parent);
      list<string>::iterator j = groups.begin();

      while (i != items.end() && j != groups.end()){
            if ((cmp_nocase(Library->get_label(group),Library->get_label(*j)) <= 0) || 
                  !((*i)->get_subtree())){
                  
                  break;
            }
            ++i;
            ++j;
      }

      parent_tree->tree().insert (i, *item);
      Tree *subtree = manage(new Tree());
      item->set_subtree (*subtree);
      item->expand();

      item->select.connect (bind(slot(*this,&LibraryTree::cb_group_select), item, group));
      
      uri_mapping.insert(map<string, TreeItem*>::value_type(group, item));
      uri_parent.insert(map<string,string>::value_type(group, parent));

      subtree->show();
      item->show();
      
      while (gtk_events_pending()){
            gtk_main_iteration();
      }
}

void
LibraryTree::removed_group (string group)
{
      ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_group), group));
      
      Gtk::TreeItem* group_item = uri_mapping[group];

      Gtk::Tree* parent_tree;
      if (uri_parent[group].length()) {
            parent_tree = uri_mapping[uri_parent[group]]->get_subtree();
      } else {
            parent_tree = &tree;
      }
      
      parent_tree->tree().remove(*group_item);
      uri_mapping.erase(uri_mapping.find(group));
      uri_parent.erase(uri_parent.find(group));
      
      while (gtk_events_pending()){
            gtk_main_iteration();
      }
}

void
LibraryTree::added_member (string member, string parent)
{
      using namespace Gtk;

      ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_member), member, parent));

      Tree* parent_tree;
      if (parent.length()) {
            parent_tree = uri_mapping[parent]->get_subtree();
      } else {
            parent_tree = &tree;
      }
      
      TreeItem *item = manage(new TreeItem(Library->get_label(member)));
      Tree_Helpers::ItemList items = parent_tree->tree();
      Tree_Helpers::ItemList::iterator i = items.begin();
      
      list<string> members;
      Library->get_members(members, parent);
      list<string>::iterator j = members.begin();

      while (i != items.end() && j != members.end()){
            if (cmp_nocase(Library->get_label(member), Library->get_label(*j)) <= 0){
                  break;
            }
            ++i;
            ++j;
      }
      
      parent_tree->tree().insert (i, *item);

      item->select.connect 
                  (bind(slot(*this,&LibraryTree::cb_member_select), item, member));
      item->deselect.connect 
                  (bind(slot(*this,&LibraryTree::cb_member_deselect), item, member));
      
      uri_mapping.insert(map<string, TreeItem*>::value_type(member, item));
      uri_parent.insert(map<string,string>::value_type(member, parent));

      item->show();
      
      while (gtk_events_pending()){
            gtk_main_iteration();
      }
}

void
LibraryTree::removed_member (string member)
{
      ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_member), member));
      
      Gtk::TreeItem* member_item = uri_mapping[member];

      Gtk::Tree* parent_tree;
      if (uri_parent[member].length()) {
            parent_tree = uri_mapping[uri_parent[member]]->get_subtree();
      } else {
            parent_tree = &tree;
      }
      
      parent_tree->tree().remove(*member_item);
      uri_mapping.erase(uri_mapping.find(member));
      uri_parent.erase(uri_parent.find(member));
      
      while (gtk_events_pending()){
            gtk_main_iteration();
      }
}

void
LibraryTree::populate ()
{
      subpopulate (&tree, current_group);
}

void
LibraryTree::subpopulate (Gtk::Tree* tree, string group)
{
      using namespace Gtk;

      list<string> groups;
      Library->get_groups(groups, group);
      
      list<string>::iterator i; 

      for (i = groups.begin(); i != groups.end(); ++i) {
            TreeItem *item = 
                  manage(new TreeItem(Library->get_label(*i)));
            tree->append (*item);
            Tree *subtree = manage(new Tree());
            item->set_subtree (*subtree);
      
            uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
            uri_parent.insert(map<string,string>::value_type(*i, group));

            item->select.connect 
                        (bind(slot(*this,&LibraryTree::cb_group_select), item, *i));

            subpopulate (subtree, *i);
            subtree->show();
            item->expand();
            item->show();
      }

      list<string> members;
      Library->get_members(members, group);
      for (i = members.begin(); i != members.end(); ++i) {
            TreeItem *item = manage(new TreeItem(Library->get_label(*i)));
            tree->append (*item);
            item->show();

            uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
            uri_parent.insert(map<string,string>::value_type(*i, group));

            item->select.connect 
                        (bind(slot(*this,&LibraryTree::cb_member_select), item, *i));
            item->deselect.connect 
                  (bind(slot(*this,&LibraryTree::cb_member_deselect), item, *i));

      }
}

void
LibraryTree::add_btn_clicked ()
{
      files_select.show_all();
}

// Gah, too many globals
static string parent_uri;
static vector<string>* old_parent;
static vector<string>* old_parent_uri;

static void clone_ftw(void*);
static int32_t ftw_return;
static Gtk::ProgressBar* bar;

void
LibraryTree::file_ok_clicked ()
{
      files_select.hide_all();

      string* file = new string(files_select.get_filename());
      parent_uri = current_group;

      Gtk::Window* progress_win = new Gtk::Window();
      progress_win->set_title(_("Importing"));
      progress_win->set_policy(false, false, true);
      Gtk::VBox* main_box = manage(new Gtk::VBox());
      progress_win->add(*main_box);
      bar = manage(new Gtk::ProgressBar());
      bar->set_activity_mode(true);
      bar->set_activity_step(15);
      bar->set_activity_blocks(10);
      main_box->pack_start(*bar);
      Gtk::Button* cancel_btn = manage(new Gtk::Button(_("Cancel")));
      main_box->pack_start(*cancel_btn);
      cancel_btn->clicked.connect (slot (*this, &LibraryTree::cancel_import_clicked));
      progress_win->show_all();
      
      clone_ftw((void*)file);
      
      delete progress_win;
}

void
LibraryTree::cancel_import_clicked()
{
      ftw_return = 1;
}

void
clone_ftw(void* ptr)
{
      string* file = (string*) ptr;
      
      old_parent = new vector<string>;
      old_parent_uri = new vector<string>;
      ftw_return = 0;

      if (ftw (file->c_str(), process_node, 100) < 0){
            warning << compose(_("%1 not added to database"), *file) << endmsg;
      }

      delete old_parent;
      delete old_parent_uri;

      delete file;
}

void
LibraryTree::file_cancel_clicked ()
{
      files_select.hide_all();
}

void
LibraryTree::folder_btn_clicked ()
{
      ArdourPrompter prompter (true);
      prompter.set_prompt(_("Folder name:"));

      prompter.done.connect(Gtk::Main::quit.slot());
      prompter.show_all();

      Gtk::Main::run();

      if (prompter.status == Gtkmmext::Prompter::entered) {
            string name;

            prompter.get_result(name);

            if (name.length()) {
                  Library->add_group(name, current_group);
            }
      }
}

void
LibraryTree::cb_group_select (Gtk::TreeItem* item, string uri)
{
      current_group = uri;
      current_member = "";
      remove_btn.set_sensitive(true);

      group_selected(); /* EMIT_SIGNAL */
}

void
LibraryTree::cb_member_select (Gtk::TreeItem* item, string uri)
{
      current_member = uri;
      current_group = "";
      selection.push_back (uri);
      member_selected(uri); /* EMIT_SIGNAL */
      remove_btn.set_sensitive(true);
}

void
LibraryTree::cb_member_deselect (Gtk::TreeItem* item, string uri)
{
      current_member = "";
      current_group = "";
      selection.remove (uri);

      member_deselected(); /* EMIT_SIGNAL */
}

void
LibraryTree::find_btn_clicked ()
{
      SearchSounds* search = new SearchSounds ();

      search->file_chosen.connect(slot (*this, &LibraryTree::file_found));
      search->show_all();
}

void
LibraryTree::file_found (string uri, bool multi)
{
      file_chosen (Library->get_member_filename(uri), multi); /* EMIT_SIGNAL */
}

void
LibraryTree::remove_btn_clicked ()
{
      if (current_member != ""){
            Library->remove_member(current_member);
      } else if (current_group != ""){
            Library->remove_group(current_group);
      } else {
            error << _("Should not be reached") << endmsg;
      }

      current_member = "";
      current_group = "";
      
      remove_btn.set_sensitive(false);
      
      deselected(); /* EMIT_SIGNAL */
}

string
length2string (const int32_t frames, const int32_t sample_rate)
{
      int secs = (int) (frames / (float) sample_rate);
      int hrs =  secs / 3600;
      secs -= (hrs * 3600);
      int mins = secs / 60;
      secs -= (mins * 60);

      int total_secs = (hrs * 3600) + (mins * 60) + secs;
      int frames_remaining = frames - (total_secs * sample_rate);
      float fractional_secs = (float) frames_remaining / sample_rate;

      char duration_str[32];
      sprintf (duration_str, "%02d:%02d:%05.2f", hrs, mins, (float) secs + fractional_secs);

      return duration_str;
}

int
process_node (const char *file, const struct stat *sb, int32_t flag)
{
      bar->set_value(0.0);
      while (gtk_events_pending()){
            gtk_main_iteration();
      }
      bar->set_value(1.0);

      string s_file(file);

      if (s_file.find("/.") != string::npos){
            return ftw_return;
      }

      if (flag == FTW_D) {
            string::size_type size = s_file.find_last_of('/');
            string label = s_file.substr(++size);
            
            while (!old_parent->empty() 
                  && (s_file.find(old_parent->back()) == string::npos)) {

                  parent_uri = old_parent_uri->back();                  
                  old_parent_uri->pop_back();
                  old_parent->pop_back();
            }

            string uri = Library->add_group(label, parent_uri);

            old_parent->push_back(s_file);
            old_parent_uri->push_back(parent_uri);
            parent_uri = uri;

            return ftw_return;
      } else if (flag != FTW_F) {
            return ftw_return;
      }

      // We can't realistically check every file - or can we ?
      char* suffix;
      if ((suffix = strrchr (file, '.')) == 0) {
            return ftw_return;
      }

      if (*(suffix+1) == '\0') {
            return ftw_return;
      }

    if (strcasecmp (suffix+1, "wav") != 0 &&
        strcasecmp (suffix+1, "aiff") != 0 &&
        strcasecmp (suffix+1, "aif") != 0 &&
        strcasecmp (suffix+1, "snd") != 0 &&
        strcasecmp (suffix+1, "au") != 0 &&
        strcasecmp (suffix+1, "raw") != 0 &&
        strcasecmp (suffix+1, "sf") != 0 &&
        strcasecmp (suffix+1, "cdr") != 0 &&
        strcasecmp (suffix+1, "smp") != 0 &&
        strcasecmp (suffix+1, "maud") != 0 &&
        strcasecmp (suffix+1, "vwe") != 0 &&
            strcasecmp (suffix+1, "paf") != 0 &&
            strcasecmp (suffix+1, "voc") != 0) {

        return ftw_return;
    }

      /* OK, it stands a good chance of being a sound file that we
         might be able to handle.
      */

      SNDFILE *sf;
      SF_INFO info;
      if ((sf = sf_open ((char *) file, SFM_READ, &info)) < 0) {
            error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
            return ftw_return;
      }
      sf_close (sf);

      string uri = Library->add_member(file, parent_uri);

      Library->set_field(uri, "channels", compose("%1", info.channels));
      Library->set_field(uri, "samplerate", compose("%1", info.samplerate));
      Library->set_field(uri, "resolution", compose("%1", sndfile_data_width(info.format)));
      Library->set_field(uri, "format", compose("%1", info.format));
      
      return ftw_return;
}

static const gchar* selector_titles[] = {
      N_("Field"), 
      N_("Value"), 
      0
};

SoundFileBox::SoundFileBox (string uri, bool meta)
      :
      uri(uri),
      metadata(meta),
      sf_info(new SF_INFO),
      current_pid(0),
      fields(_fields_refiller, this, internationalize (selector_titles), 
               false, true),
      main_box (false, 3),
      top_box (true, 4),
      bottom_box (true, 4),
      play_btn(_("Play")),
      stop_btn(_("Stop")),
      add_field_btn(_("Add Field...")),
      remove_field_btn(_("Remove Field"))
{
      set_name ("SoundFileBox");

      border_frame.set_label (_("Soundfile Info"));
      border_frame.add (main_box);

      pack_start (border_frame);
      set_border_width (4);

      Gtk::HBox* path_box = manage (new HBox);
      
      path_box->set_spacing (4);
      path_box->pack_start (path, false, false);
      path_box->pack_start (path_entry, true, true);

      main_box.set_border_width (4);

      main_box.pack_start(label, false, false);
      main_box.pack_start(*path_box, false, false);
      main_box.pack_start(length, false, false);
      main_box.pack_start(format, false, false);
      main_box.pack_start(channels, false, false);
      main_box.pack_start(samplerate, false, false);
      if (metadata){
            main_box.pack_start(fields, true, true);
            main_box.pack_start(top_box, false, false);
      }
      main_box.pack_start(bottom_box, false, false);

      fields.set_usize(200, 150);

      top_box.set_homogeneous(true);
      top_box.pack_start(add_field_btn);
      top_box.pack_start(remove_field_btn);

      remove_field_btn.set_sensitive(false);

      bottom_box.set_homogeneous(true);
      bottom_box.pack_start(play_btn);
      bottom_box.pack_start(stop_btn);

      play_btn.clicked.connect (slot (*this, &SoundFileBox::play_btn_clicked));
      stop_btn.clicked.connect (slot (*this, &SoundFileBox::stop_btn_clicked));

      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();
      if (!sess) {
            play_btn.set_sensitive(false);
      } else {
            sess->AuditionActive.connect(slot (*this, &SoundFileBox::audition_status_changed));
      }

      add_field_btn.clicked.connect 
                  (slot (*this, &SoundFileBox::add_field_clicked));
      remove_field_btn.clicked.connect 
                  (slot (*this, &SoundFileBox::remove_field_clicked));

      fields.selection_made.connect (slot (*this, &SoundFileBox::field_selected));
      fields.choice_made.connect (slot (*this, &SoundFileBox::field_chosen));
      
      Library->fields_changed.connect (slot (*this, &SoundFileBox::setup_fields));

      if (setup_labels (uri)) {
            throw failed_constructor();
      }
      
      show_all();
      stop_btn.hide();
}

SoundFileBox::~SoundFileBox ()
{
}

void 
SoundFileBox::_fields_refiller (Gtk::CList &list, void* arg)
{
      ((SoundFileBox *) arg)->fields_refiller (list);
}

void
SoundFileBox::fields_refiller (Gtk::CList &clist)
{     
      if (metadata) {
            list<string> flist;
            Library->get_fields(flist);
            list<string>::iterator i;
      
            const gchar *rowdata[3];
            guint row = 0;
            for (i=flist.begin(); i != flist.end(); ++i, ++row){
                  if (*i != "channels" && *i != "samplerate" && 
                        *i != "resolution" && *i != "format") {

                        rowdata[0] = (*i).c_str();
                        rowdata[1] = Library->get_field(uri, *i).c_str();
                        clist.insert_row (row, rowdata);
                        ++row;
                  }
            }

            clist.column(0).set_auto_resize(true);
            clist.set_sort_column (0);
            clist.sort ();
      }
}

int
SoundFileBox::setup_labels (string uri)
{
      SNDFILE *sf;

      string file;
      if (metadata){
            file = Library->get_member_filename(uri);
      } else {
            file = uri;
      }

      if ((sf = sf_open ((char *) file.c_str(), SFM_READ, sf_info)) < 0) {
            error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
            return -1;
      }
      
      if (sf_info->frames == 0 &&
          sf_info->channels == 0 &&
          sf_info->samplerate == 0 &&
          sf_info->format == 0 &&
          sf_info->sections == 0) {
            /* .. ok, its not a sound file */
            error << compose(_("file \"%1\" appears not to be an audio file"), file) << endmsg;
            return -1;
      }

      if (metadata) {
            label.set_alignment (0.0f, 0.0f);
            label.set_text ("Label: " + Library->get_label(uri));
      }

      path.set_text ("Path: ");

      path_entry.set_text (file);
      path_entry.set_position (-1);

      path_entry.focus_in_event.connect (slot (ARDOUR_UI::generic_focus_in_event));
      path_entry.focus_out_event.connect (slot (ARDOUR_UI::generic_focus_out_event));

      length.set_alignment (0.0f, 0.0f);
      length.set_text (compose("Length: %1", length2string(sf_info->frames, sf_info->samplerate)));

      format.set_alignment (0.0f, 0.0f);
      format.set_text (compose("Format: %1, %2", 
                         sndfile_major_format(sf_info->format), 
                         sndfile_minor_format(sf_info->format)));
      
      channels.set_alignment (0.0f, 0.0f);
      channels.set_text (compose("Channels: %1", sf_info->channels));
      
      samplerate.set_alignment (0.0f, 0.0f);
      samplerate.set_text (compose("Samplerate: %1", sf_info->samplerate));

      return 0;
}

void
SoundFileBox::play_btn_clicked ()
{
      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();
      if (!sess) {
            return;
      }

      sess->cancel_audition();
      string file;

      if (metadata) {
            file = Library->get_member_filename(uri);
      } else {
            file = uri;
      }

      if (access(file.c_str(), R_OK)) {
            warning << compose(_("Could not read file: %1 (%2)."), file, strerror(errno)) << endmsg;
            return;
      }
      
      static map<string, ARDOUR::AudioRegion*> region_cache;

      if (region_cache.find (file) == region_cache.end()) {

            AudioRegion::SourceList srclist;
            SndFileSource* sfs;
            
            for (int n=0; n < sf_info->channels; ++n) {
                  
                  try {
                        sfs =  new SndFileSource(file+":"+compose("%1", n), false);
                        srclist.push_back(sfs);
                        
                  } catch (failed_constructor& err) {
                        error << _("Could not access soundfile: ") << file << endmsg;
                        return;
                  }
            }

            if (srclist.empty()) {
                  return;
            }
            
            string result;
            sess->region_name (result, PBD::basename(srclist[0]->name()), false);
            AudioRegion* a_region = new AudioRegion(srclist, 0, srclist[0]->length(), result, 0, Region::DefaultFlags, false);
            region_cache[file] = a_region;
      } 

      play_btn.hide();
      stop_btn.show();

      sess->audition_region(*region_cache[file]);
}

void
SoundFileBox::stop_btn_clicked ()
{
      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();
      if (sess) {
            sess->cancel_audition();
            play_btn.show();
            stop_btn.hide();
      }
}

void
SoundFileBox::audition_status_changed (bool active)
{
      if (!active) {
            Gtkmmext::UI::instance()->call_slot( slot(*this, &SoundFileBox::stop_btn_clicked));
      }
}

void
SoundFileBox::add_field_clicked ()
{
      ArdourPrompter prompter (true);
      prompter.set_prompt(_("Field name:"));
      
      prompter.show_all();
      prompter.done.connect(Gtk::Main::quit.slot());
      
      Gtk::Main::run();
      
      if (prompter.status == Gtkmmext::Prompter::entered) {
            string name;
            
            prompter.get_result(name);
            
            if (name.length()) {
                  Library->add_field(name);
            }
      }
}

void
SoundFileBox::remove_field_clicked ()
{
      Library->remove_field(selected_field);
      selected_field = "";
      remove_field_btn.set_sensitive(false);
}

void
SoundFileBox::setup_fields ()
{
      ENSURE_GUI_THREAD(slot (*this, &SoundFileBox::setup_fields));
      
      fields.rescan();
}

void
SoundFileBox::field_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{
      if (res) {
            remove_field_btn.set_sensitive(true);
            selected_field = selector->clist().row(res->row)[0].get_text();
      }
}

void
SoundFileBox::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{     
      if (!res){
            return;
      }

      string field_name(selector->clist().row(res->row)[0].get_text());
      
      ArdourPrompter prompter (true);
      prompter.set_prompt(_("Field value:"));
      prompter.set_initial_text (selector->clist().row(res->row)[1].get_text());
      
      prompter.show_all();
      prompter.done.connect(Gtk::Main::quit.slot());

      Gtk::Main::run();
      
      if (prompter.status == Gtkmmext::Prompter::entered) {
            string data;

            prompter.get_result(data);
            Library->set_field(uri, field_name, data);
      }

      fields.rescan();
}

SearchSounds::SearchSounds ()
      : ArdourDialog ("search sounds dialog"),
        find_btn (_("Find")),
        and_rbtn (_("AND")),
        or_rbtn (_("OR")),
        fields(_fields_refiller, this, internationalize(selector_titles))
{
      set_title (_("ardour: locate soundfiles"));
      set_name ("AudioSearchSound");

      add(main_box);

      or_rbtn.set_group(and_rbtn.group());
      or_rbtn.set_active(true);
      rbtn_box.set_homogeneous(true);
      rbtn_box.pack_start(and_rbtn);
      rbtn_box.pack_start(or_rbtn);

      bottom_box.set_homogeneous(true);
      bottom_box.pack_start(find_btn);

      fields.set_usize(200, 150);

      main_box.pack_start(fields);
      main_box.pack_start(rbtn_box, false, false);
      main_box.pack_start(bottom_box, false, false);

      delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));

      find_btn.clicked.connect (slot (*this, &SearchSounds::find_btn_clicked));
      fields.selection_made.connect (slot 
                                                   (*this, &SearchSounds::field_selected));

      show_all();
}

SearchSounds::~SearchSounds ()
{
}

void 
SearchSounds::_fields_refiller (Gtk::CList &list, void* arg)
{
      ((SearchSounds *) arg)->fields_refiller (list);
}

void
SearchSounds::fields_refiller (Gtk::CList &clist)
{     
      list<string> flist;
      Library->get_fields(flist);
      list<string>::iterator i;
      
      const gchar *rowdata[3];
      guint row;
      for (row=0,i=flist.begin(); i != flist.end(); ++i, ++row){
            rowdata[0] = (*i).c_str();
            rowdata[1] = "";
            clist.insert_row (row, rowdata);
      }

      clist.column(0).set_auto_resize(true);
      clist.set_sort_column (0);
      clist.sort ();
}

void
SearchSounds::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{     
      if (!res){
            return;
      }
      
      ArdourPrompter prompter (true);
      prompter.set_prompt(_("Field value:"));
      
      prompter.show_all();
      prompter.done.connect(Gtk::Main::quit.slot());

      Gtk::Main::run();
      
      if (prompter.status == Gtkmmext::Prompter::entered) {
            string data;

            prompter.get_result(data);
            selector->clist().cell(res->row, 1).set_text(data);
      }
}

void
SearchSounds::find_btn_clicked ()
{
      using namespace Gtk::CList_Helpers;
      typedef map<string,string> StringMap;

      StringMap search_info;

      RowList rows = fields.clist().rows();
      RowList::const_iterator i;

      for (i = rows.begin(); i != rows.end(); ++i) {
            Cell cfield((*i)[0]);
            Cell cdata((*i)[1]);
            if (cdata.get_text().length()){
                  search_info.insert(
                        StringMap::value_type(cfield.get_text(), cdata.get_text()));
            }
      }

      SearchResults* results;
      if (and_rbtn.get_active()){
            results = new SearchResults(search_info, true);
      } else {
            results = new SearchResults(search_info, false);
      }

      results->file_chosen.connect (slot (*this, &SearchSounds::file_found));
      results->show_all();
}

void
SearchSounds::file_found (string uri, bool multi)
{
      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();
      if (sess) {
            sess->cancel_audition();
      }

      file_chosen (uri, multi); /* EMIT_SIGNAL */
}

static const gchar* result_titles[] = { 
      N_("Results"), 
      N_("Uris"), 
      0
};

SearchResults::SearchResults (map<string,string> field_values, bool and_search)
      : ArdourDialog ("search results dialog"),
        search_info(field_values),
        search_and (and_search),
        selection (""),
        main_box (false, 3),
        import_box (true, 4),
        import_btn (_("Import")),
        multichan_check (_("Create multi-channel region")),
        results (_results_refiller, this, internationalize (result_titles), false, true)
{
      set_title (_("Ardour: Search Results"));
      set_name ("ArdourSearchResults");
      
      add(main_box);
      set_border_width (3);
      
      main_box.pack_start(hbox);
      hbox.pack_start(results);

      main_box.pack_start(import_box, false, false);
      
      results.set_usize (200, 150);
      
      import_box.set_homogeneous(true);
      import_box.pack_start(import_btn);
      import_box.pack_start(multichan_check);

      import_btn.set_sensitive(false);
      multichan_check.set_active(true);
      multichan_check.set_sensitive(false);
      
      delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));

      import_btn.clicked.connect (slot (*this, &SearchResults::import_clicked));

      results.choice_made.connect (slot (*this, &SearchResults::result_chosen));

      show_all();
}

SearchResults::~SearchResults ()
{
}

void 
SearchResults::_results_refiller (Gtk::CList &list, void* arg)
{
      ((SearchResults *) arg)->results_refiller (list);
}

void 
SearchResults::results_refiller (Gtk::CList &clist)
{
      list<string> results;
      if (search_and) {
            Library->search_members_and (results, search_info);
      } else {
            Library->search_members_or (results, search_info);
      }

      list<string>::iterator i;
      const gchar *rowdata[3];
      guint row;
      for (row=0, i=results.begin(); i != results.end(); ++i, ++row){
            rowdata[0] = (Library->get_label(*i)).c_str();
            // hide the uri in a hidden column
            rowdata[1] = (*i).c_str();
            clist.insert_row (row, rowdata);
      }

      clist.column(1).set_visiblity(false);
      clist.column(0).set_auto_resize(true);
      clist.set_sort_column (0);
      clist.sort ();
}

void
SearchResults::import_clicked ()
{
      PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
      ARDOUR::Session* sess = edit.current_session();

      if (sess) {
            sess->cancel_audition();
      }

      file_chosen(selection, multichan_check.get_active()); /* EMIT_SIGNAL */
}

void 
SearchResults::result_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{
      if (res) {
            selection = selector->clist().row(res->row)[1].get_text();

            if (info_box){
                  delete info_box;
                  info_box = 0;
            }
      
            try {
                  info_box = new SoundFileBox(selection, true);
            } catch (failed_constructor& err) {
                  /* nothing to do */
                  return;
            }

            hbox.pack_start(*info_box);
      }
}

Generated by  Doxygen 1.6.0   Back to index