opaque xfade patch + a version of the editor ruler/playhead/click patch
[ardour.git] / gtk2_ardour / sfdb_ui.cc
1 /*
2     Copyright (C) 2005-2006 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
20 #include <map>
21 #include <cerrno>
22 #include <sstream>
23
24 #include <sys/stat.h>
25
26 #include <gtkmm/box.h>
27 #include <gtkmm/stock.h>
28 #include <glibmm/fileutils.h>
29
30 #include <pbd/convert.h>
31 #include <pbd/tokenizer.h>
32
33 #include <gtkmm2ext/utils.h>
34
35 #include <ardour/audio_library.h>
36 #include <ardour/audioregion.h>
37 #include <ardour/audiofilesource.h>
38 #include <ardour/region_factory.h>
39 #include <ardour/source_factory.h>
40
41 #include "ardour_ui.h"
42 #include "editing.h"
43 #include "gui_thread.h"
44 #include "prompter.h"
45 #include "sfdb_ui.h"
46 #include "editing.h"
47 #include "utils.h"
48
49 #include "i18n.h"
50
51 using namespace ARDOUR;
52 using namespace PBD;
53 using namespace std;
54 using namespace Gtk;
55 using namespace Editing;
56
57 Glib::ustring SoundFileBrowser::persistent_folder;
58
59 SoundFileBox::SoundFileBox ()
60         :
61         _session(0),
62         current_pid(0),
63         main_box (false, 3),
64         bottom_box (true, 4),
65         play_btn (Stock::MEDIA_PLAY),
66         stop_btn (Stock::MEDIA_STOP),
67         apply_btn ()
68 {
69         set_name (X_("SoundFileBox"));
70         
71         set_size_request (250, 250);
72         
73         Label* label = manage (new Label);
74         label->set_use_markup (true);
75         label->set_text (_("<b>Soundfile Info</b>"));
76
77         border_frame.set_label_widget (*label);
78         border_frame.add (main_box);
79
80         Label* tag_label = manage(new Label(_("comma seperated tags")));
81
82         pack_start (border_frame, FALSE, FALSE);
83         set_border_width (4);
84
85         main_box.set_border_width (4);
86
87         Gtk::Image* w = manage (new Image (Stock::APPLY, ICON_SIZE_BUTTON));
88         apply_btn.set_image (*w);
89         apply_btn.set_label (_("Set tags"));
90
91         main_box.pack_start(length, false, false);
92         main_box.pack_start(format, false, false);
93         main_box.pack_start(channels, false, false);
94         main_box.pack_start(samplerate, false, false);
95         main_box.pack_start(timecode, false, false);
96         main_box.pack_start(*tag_label, false, false);
97
98         HBox* hbox = manage (new HBox);
99         hbox->pack_start (apply_btn, false, false);
100         hbox->pack_start (tags_entry, true, true);
101
102         main_box.pack_start(*hbox, false, false);
103         main_box.pack_start(bottom_box, false, false);
104
105         bottom_box.set_homogeneous(true);
106         bottom_box.pack_start(play_btn);
107         bottom_box.pack_start(stop_btn);
108
109         play_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::audition));
110         stop_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::stop_btn_clicked));
111         apply_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::apply_btn_clicked));
112         tags_entry.signal_activate().connect (mem_fun (*this, &SoundFileBox::apply_btn_clicked));
113
114         length.set_alignment (0.0f, 0.0f);
115         format.set_alignment (0.0f, 0.0f);
116         channels.set_alignment (0.0f, 0.0f);
117         samplerate.set_alignment (0.0f, 0.0f);
118         timecode.set_alignment (0.0f, 0.0f);
119
120         stop_btn.set_no_show_all (true);
121         stop_btn.hide();
122 }
123
124 void
125 SoundFileBox::set_session(Session* s)
126 {
127         _session = s;
128
129         if (!_session) {
130                 play_btn.set_sensitive(false);
131         } else {
132                 _session->AuditionActive.connect(mem_fun (*this, &SoundFileBox::audition_status_changed));
133         }
134 }
135
136 bool
137 SoundFileBox::setup_labels (const Glib::ustring& filename) 
138 {
139         path = filename;
140
141         string error_msg;
142
143         if(!AudioFileSource::get_soundfile_info (filename, sf_info, error_msg)) {
144                 length.set_text (_("Length: n/a"));
145                 format.set_text (_("Format: n/a"));
146                 channels.set_text (_("Channels: n/a"));
147                 samplerate.set_text (_("Sample rate: n/a"));
148                 timecode.set_text (_("Timecode: n/a"));
149                 tags_entry.set_text ("");
150                 
151                 tags_entry.set_sensitive (false);
152                 play_btn.set_sensitive (false);
153                 apply_btn.set_sensitive (false);
154                 
155                 return false;
156         }
157
158         length.set_text (string_compose(_("Length: %1"), length2string(sf_info.length, sf_info.samplerate)));
159         format.set_text (sf_info.format_name);
160         channels.set_text (string_compose(_("Channels: %1"), sf_info.channels));
161         samplerate.set_text (string_compose(_("Samplerate: %1"), sf_info.samplerate));
162         timecode.set_text (string_compose (_("Timecode: %1"), length2string(sf_info.timecode, sf_info.samplerate)));
163
164         // this is a hack that is fixed in trunk, i think (august 26th, 2007)
165
166         vector<string> tags = Library->get_tags (string ("//") + filename);
167         
168         stringstream tag_string;
169         for (vector<string>::iterator i = tags.begin(); i != tags.end(); ++i) {
170                 if (i != tags.begin()) {
171                         tag_string << ", ";
172                 }
173                 tag_string << *i;
174         }
175         tags_entry.set_text (tag_string.str());
176         
177         tags_entry.set_sensitive (true);
178         if (_session) {
179                 play_btn.set_sensitive (true);
180         }
181         apply_btn.set_sensitive (true);
182         
183         return true;
184 }
185
186 bool
187 SoundFileBox::tags_entry_left (GdkEventFocus* event)
188 {       
189         apply_btn_clicked ();
190         return true;
191 }
192
193 void
194 SoundFileBox::audition ()
195 {
196         if (!_session) {
197                 return;
198         }
199
200         _session->cancel_audition();
201
202         if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
203                 warning << string_compose(_("Could not read file: %1 (%2)."), path, strerror(errno)) << endmsg;
204                 return;
205         }
206
207         typedef std::map<Glib::ustring, boost::shared_ptr<AudioRegion> > RegionCache; 
208         static  RegionCache region_cache;
209         RegionCache::iterator the_region;
210
211         if ((the_region = region_cache.find (path)) == region_cache.end()) {
212
213                 SourceList srclist;
214                 boost::shared_ptr<AudioFileSource> afs;
215                 
216                 for (int n = 0; n < sf_info.channels; ++n) {
217                         try {
218                                 afs = boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createReadable (*_session, path, n, AudioFileSource::Flag (0)));
219                                 srclist.push_back(afs);
220
221                         } catch (failed_constructor& err) {
222                                 error << _("Could not access soundfile: ") << path << endmsg;
223                                 return;
224                         }
225                 }
226
227                 if (srclist.empty()) {
228                         return;
229                 }
230
231                 string rname;
232
233                 _session->region_name (rname, Glib::path_get_basename(srclist[0]->name()), false);
234
235                 pair<string,boost::shared_ptr<AudioRegion> > newpair;
236                 pair<RegionCache::iterator,bool> res;
237
238                 newpair.first = path;
239                 newpair.second = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (srclist, 0, srclist[0]->length(), rname, 0, Region::DefaultFlags, false));
240
241                 res = region_cache.insert (newpair);
242                 the_region = res.first;
243         }
244
245         play_btn.hide();
246         stop_btn.show();
247
248         boost::shared_ptr<Region> r = boost::static_pointer_cast<Region> (the_region->second);
249
250         _session->audition_region(r);
251 }
252
253 void
254 SoundFileBox::stop_btn_clicked ()
255 {
256         if (_session) {
257                 _session->cancel_audition();
258                 play_btn.show();
259                 stop_btn.hide();
260         }
261 }
262
263 void
264 SoundFileBox::apply_btn_clicked ()
265 {
266         string tag_string = tags_entry.get_text ();
267
268         if (tag_string.empty()) {
269                 return;
270         }
271
272         vector<string> tags;
273
274         if (!PBD::tokenize (tag_string, string(","), std::back_inserter (tags), true)) {
275                 warning << _("SoundFileBox: Could not tokenize string: ") << tag_string << endmsg;
276                 return;
277         }
278         
279         Library->set_tags (string ("//") + path, tags);
280         Library->save_changes ();
281 }
282
283 void
284 SoundFileBox::audition_status_changed (bool active)
285 {
286         ENSURE_GUI_THREAD(bind (mem_fun (*this, &SoundFileBox::audition_status_changed), active));
287         
288         if (!active) {
289                 stop_btn_clicked ();
290         }
291 }
292
293 SoundFileBrowser::SoundFileBrowser (string title, ARDOUR::Session* s)
294         : ArdourDialog (title, false),
295           found_list (ListStore::create(found_list_columns)),
296           chooser (FILE_CHOOSER_ACTION_OPEN),
297           found_list_view (found_list),
298           found_search_btn (_("Search"))
299 {
300
301         VBox* vbox;
302         HBox* hbox;
303
304         vbox = manage (new VBox);
305         vbox->pack_start (preview, false, false);
306
307
308         hbox = manage (new HBox);
309         hbox->set_spacing (6);
310         hbox->pack_start (notebook, true, true);
311         hbox->pack_start (*vbox, false, false);
312         
313         get_vbox()->pack_start (*hbox, true, true);
314
315         hbox = manage(new HBox);
316         hbox->pack_start (found_entry);
317         hbox->pack_start (found_search_btn);
318         
319         vbox = manage(new VBox);
320         vbox->pack_start (*hbox, PACK_SHRINK);
321         vbox->pack_start (found_list_view);
322         found_list_view.append_column(_("Paths"), found_list_columns.pathname);
323         
324         chooser.set_border_width (12);
325
326         notebook.append_page (chooser, _("Search Files"));
327         notebook.append_page (*vbox, _("Search Tags"));
328
329         found_list_view.get_selection()->set_mode (SELECTION_MULTIPLE);
330         found_list_view.signal_row_activated().connect (mem_fun (*this, &SoundFileBrowser::found_list_view_activated));
331
332         custom_filter.add_custom (FILE_FILTER_FILENAME, mem_fun(*this, &SoundFileBrowser::on_custom));
333         custom_filter.set_name (_("Probable audio files"));
334
335         matchall_filter.add_pattern ("*.*");
336         matchall_filter.set_name (_("All files"));
337
338         chooser.add_filter (custom_filter);
339         chooser.add_filter (matchall_filter);
340         chooser.set_select_multiple (true);
341         chooser.signal_update_preview().connect(mem_fun(*this, &SoundFileBrowser::update_preview));
342         chooser.signal_file_activated().connect (mem_fun (*this, &SoundFileBrowser::chooser_file_activated));
343
344         if (!persistent_folder.empty()) {
345                 chooser.set_current_folder (persistent_folder);
346         }
347
348         found_list_view.get_selection()->signal_changed().connect(mem_fun(*this, &SoundFileBrowser::found_list_view_selected));
349         
350         found_search_btn.signal_clicked().connect(mem_fun(*this, &SoundFileBrowser::found_search_clicked));
351         found_entry.signal_activate().connect(mem_fun(*this, &SoundFileBrowser::found_search_clicked));
352
353         add_button (Stock::OK, RESPONSE_OK);
354         add_button (Stock::CANCEL, RESPONSE_CANCEL);
355
356         set_response_sensitive (RESPONSE_OK, false);
357         
358         set_session (s);
359 }
360
361 SoundFileBrowser::~SoundFileBrowser ()
362 {
363         persistent_folder = chooser.get_current_folder();
364 }
365
366 void
367 SoundFileBrowser::chooser_file_activated ()
368 {
369         preview.audition ();
370 }
371
372 void
373 SoundFileBrowser::found_list_view_activated (const TreeModel::Path& path, TreeViewColumn* col)
374 {
375         preview.audition ();
376 }
377
378 void
379 SoundFileBrowser::set_session (Session* s)
380 {
381         preview.set_session(s);
382 }
383
384 bool
385 SoundFileBrowser::on_custom (const FileFilter::Info& filter_info)
386 {
387         return AudioFileSource::safe_file_extension(filter_info.filename);
388 }
389
390 void
391 SoundFileBrowser::update_preview ()
392 {
393         preview.setup_labels (chooser.get_filename());
394         set_response_sensitive (RESPONSE_OK, true);
395 }
396
397 void
398 SoundFileBrowser::found_list_view_selected ()
399 {
400         Glib::ustring file;
401         
402         TreeView::Selection::ListHandle_Path rows = found_list_view.get_selection()->get_selected_rows ();
403         
404         if (!rows.empty()) {
405                 TreeIter iter = found_list->get_iter(*rows.begin());
406                 file = (*iter)[found_list_columns.pathname];
407                 chooser.set_filename (file);
408         }
409         preview.setup_labels (file);
410         set_response_sensitive (RESPONSE_OK, true);
411 }
412
413 void
414 SoundFileBrowser::found_search_clicked ()
415 {
416         string tag_string = found_entry.get_text ();
417
418         vector<string> tags;
419
420         if (!PBD::tokenize (tag_string, string(","), std::back_inserter (tags), true)) {
421                 warning << _("SoundFileBrowser: Could not tokenize string: ") << tag_string << endmsg;
422                 return;
423         }
424         
425         vector<string> results;
426         Library->search_members_and (results, tags);
427         
428         found_list->clear();
429         for (vector<string>::iterator i = results.begin(); i != results.end(); ++i) {
430                 TreeModel::iterator new_row = found_list->append();
431                 TreeModel::Row row = *new_row;
432                 string path = Glib::filename_from_uri (string ("file:") + *i);
433                 row[found_list_columns.pathname] = path;
434         }
435 }
436
437 vector<Glib::ustring>
438 SoundFileBrowser::get_paths ()
439 {
440         vector<Glib::ustring> results;
441         
442         int n = notebook.get_current_page ();
443         
444         if (n == 0) {
445                 vector<Glib::ustring> filenames = chooser.get_filenames();
446                 vector<Glib::ustring>::iterator i;
447                 for (i = filenames.begin(); i != filenames.end(); ++i) {
448                         struct stat buf;
449                         if ((!stat((*i).c_str(), &buf)) && S_ISREG(buf.st_mode)) {
450                                 results.push_back (*i);
451                         }
452                 }
453                 return results;
454                 
455         } else {
456                 
457                 typedef TreeView::Selection::ListHandle_Path ListPath;
458                 
459                 ListPath rows = found_list_view.get_selection()->get_selected_rows ();
460                 for (ListPath::iterator i = rows.begin() ; i != rows.end(); ++i) {
461                         TreeIter iter = found_list->get_iter(*i);
462                         Glib::ustring str = (*iter)[found_list_columns.pathname];
463                         
464                         results.push_back (str);
465                 }
466                 return results;
467         }
468 }
469
470 SoundFileChooser::SoundFileChooser (string title, ARDOUR::Session* s)
471         : ArdourDialog (title, false),
472           browser (title, s)
473 {
474         set_default_size (700, 300);
475
476         get_vbox()->pack_start (browser, false, false);
477
478         add_button (Stock::OPEN, RESPONSE_OK);
479         add_button (Stock::CANCEL, RESPONSE_CANCEL);
480         
481         browser.chooser.set_select_multiple (false);
482         browser.found_list_view.get_selection()->set_mode (SELECTION_SINGLE);
483
484         show_all ();
485 }
486
487 Glib::ustring
488 SoundFileChooser::get_filename ()
489 {
490         vector<Glib::ustring> paths;
491
492         paths = browser.get_paths ();
493
494         if (paths.empty()) {
495                 return Glib::ustring ();
496         }
497         
498         if (!Glib::file_test (paths.front(), Glib::FILE_TEST_EXISTS|Glib::FILE_TEST_IS_REGULAR)) {
499                 return Glib::ustring();
500         }
501         
502         return paths.front();
503 }
504
505
506 SoundFileOptionsDialog::SoundFileOptionsDialog (Window& parent, const vector<Glib::ustring>& p, int selected_tracks)
507         : ArdourDialog (parent, _("External Audio Options"), false),
508           mode (ImportAsTrack),
509           paths (p),
510           split_files (_("Split non-mono files")),
511           merge_stereo (_("Use files as single stereo track")),
512           as_tracks (rgroup1, _("Use files as new tracks")),
513           to_tracks (rgroup1, _("Add files to selected tracks")),
514           as_regions (rgroup1, _("Add files to region list")),
515           as_tape_tracks (rgroup1, _("Add files as new tape tracks")),
516           import (rgroup2, _("Copy to Ardour-native files")),
517           embed (rgroup2, _("Use file without copying")),
518           selected_track_cnt (selected_tracks)
519 {
520         selection_includes_multichannel = check_multichannel_status (paths);
521
522         block_two.set_border_width (12);
523         block_three.set_border_width (12);
524         block_four.set_border_width (12);
525         
526         block_two.pack_start (as_tracks, false, false);
527         block_two.pack_start (to_tracks, false, false);
528         block_two.pack_start (as_regions, false, false);
529         block_two.pack_start (as_tape_tracks, false, false);
530
531         as_tracks.signal_toggled().connect (mem_fun (*this, &SoundFileOptionsDialog::mode_changed));
532         to_tracks.signal_toggled().connect (mem_fun (*this, &SoundFileOptionsDialog::mode_changed));
533         as_regions.signal_toggled().connect (mem_fun (*this, &SoundFileOptionsDialog::mode_changed));
534         as_tape_tracks.signal_toggled().connect (mem_fun (*this, &SoundFileOptionsDialog::mode_changed));
535         
536         block_three.pack_start (merge_stereo, false, false);
537         block_three.pack_start (split_files, false, false);
538         
539         block_four.pack_start (import, false, false);
540         block_four.pack_start (embed, false, false);
541         
542         get_vbox()->set_spacing (12);
543         get_vbox()->pack_start (block_two, false, false);
544         get_vbox()->pack_start (block_three, false, false);
545         get_vbox()->pack_start (block_four, false, false);
546
547         if (selected_track_cnt == 0) {
548                 to_tracks.set_sensitive (false);
549         } else {
550                 to_tracks.set_sensitive (true);
551         }
552         
553         mode_changed ();
554         
555         add_button (Stock::OK, RESPONSE_OK);
556         add_button (Stock::CANCEL, RESPONSE_CANCEL);
557 }
558
559 void
560 SoundFileOptionsDialog::mode_changed ()
561 {
562         if (as_tracks.get_active()) {
563                 mode = ImportAsTrack;
564         } else if (to_tracks.get_active()) {
565                 mode = ImportToTrack;
566         } else if (as_regions.get_active()) {
567                 mode = ImportAsRegion;
568         } else {
569                 mode = ImportAsTapeTrack;
570         }
571
572         if ((mode == ImportAsTrack || mode == ImportAsTapeTrack) && paths.size() == 2) {
573                 merge_stereo.set_sensitive (true);
574         } else {
575                 merge_stereo.set_sensitive (false);
576         }
577
578         if (selection_includes_multichannel) {
579                 split_files.set_sensitive (true);
580         } else {
581                 split_files.set_sensitive (false);
582         }
583 }       
584
585
586 bool
587 SoundFileOptionsDialog::check_multichannel_status (const vector<Glib::ustring>& paths)
588 {
589         SNDFILE* sf;
590         SF_INFO info;
591         
592         for (vector<Glib::ustring>::const_iterator i = paths.begin(); i != paths.end(); ++i) {
593
594                 info.format = 0; // libsndfile says to clear this before sf_open().
595                 
596                 if ((sf = sf_open ((char*) (*i).c_str(), SFM_READ, &info)) != 0) { 
597                         sf_close (sf);
598                         if (info.channels > 1) {
599                                 return true;
600                         }
601                 }
602         }
603
604         return false;
605 }
606