PBD::strip_whitespace_edges() returns the empty string if the passed string is
[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 <gtkmm/box.h>
25 #include <gtkmm/stock.h>
26
27 #include <pbd/convert.h>
28 #include <pbd/whitespace.h>
29
30 #include <gtkmm2ext/utils.h>
31
32 #include <ardour/audio_library.h>
33 #include <ardour/audioregion.h>
34 #include <ardour/audiofilesource.h>
35 #include <ardour/region_factory.h>
36 #include <ardour/source_factory.h>
37
38 #include "ardour_ui.h"
39 #include "editing.h"
40 #include "gui_thread.h"
41 #include "prompter.h"
42 #include "sfdb_ui.h"
43 #include "utils.h"
44
45 #include "i18n.h"
46
47 using namespace ARDOUR;
48 using namespace PBD;
49 using namespace std;
50
51 SoundFileBox::SoundFileBox ()
52         :
53         _session(0),
54         current_pid(0),
55         main_box (false, 3),
56         top_box (true, 4),
57         bottom_box (true, 4),
58         play_btn(_("Play")),
59         stop_btn(_("Stop")),
60         apply_btn(_("Apply"))
61 {
62         set_name (X_("SoundFileBox"));
63         border_frame.set_label (_("Soundfile Info"));
64         border_frame.add (main_box);
65
66         pack_start (border_frame);
67         set_border_width (4);
68
69         main_box.set_border_width (4);
70
71         main_box.pack_start(length, false, false);
72         main_box.pack_start(format, false, false);
73         main_box.pack_start(channels, false, false);
74         main_box.pack_start(samplerate, false, false);
75         main_box.pack_start(timecode, false, false);
76         main_box.pack_start(tags_entry, true, true);
77         main_box.pack_start(top_box, false, false);
78         main_box.pack_start(bottom_box, false, false);
79
80         top_box.set_homogeneous(true);
81         top_box.pack_start(apply_btn);
82
83         bottom_box.set_homogeneous(true);
84         bottom_box.pack_start(play_btn);
85         bottom_box.pack_start(stop_btn);
86
87 //      tags_entry.signal_focus_out_event().connect (mem_fun (*this, &SoundFileBox::tags_entry_left));
88         play_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::play_btn_clicked));
89         stop_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::stop_btn_clicked));
90         apply_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::apply_btn_clicked));
91
92         show_all();
93         stop_btn.hide();
94 }
95
96 void
97 SoundFileBox::set_session(Session* s)
98 {
99         _session = s;
100
101         if (!_session) {
102                 play_btn.set_sensitive(false);
103         } else {
104                 _session->AuditionActive.connect(mem_fun (*this, &SoundFileBox::audition_status_changed));
105         }
106 }
107
108 bool
109 SoundFileBox::setup_labels (string filename) 
110 {
111         path = filename;
112
113         string error_msg;
114
115         if(!AudioFileSource::get_soundfile_info (filename, sf_info, error_msg)) {
116                 return false;
117         }
118
119         length.set_alignment (0.0f, 0.0f);
120         length.set_text (string_compose(_("Length: %1"), length2string(sf_info.length, sf_info.samplerate)));
121
122         format.set_alignment (0.0f, 0.0f);
123         format.set_text (sf_info.format_name);
124
125         channels.set_alignment (0.0f, 0.0f);
126         channels.set_text (string_compose(_("Channels: %1"), sf_info.channels));
127
128         samplerate.set_alignment (0.0f, 0.0f);
129         samplerate.set_text (string_compose(_("Samplerate: %1"), sf_info.samplerate));
130
131         timecode.set_alignment (0.0f, 0.0f);
132         timecode.set_text (string_compose (_("Timecode: %1"), length2string(sf_info.timecode, sf_info.samplerate)));
133
134         vector<string> tags = Library->get_tags (filename);
135         
136         stringstream tag_string;
137         for (vector<string>::iterator i = tags.begin(); i != tags.end(); ++i) {
138                 if (i != tags.begin()) {
139                         tag_string << ", ";
140                 }
141                 tag_string << *i;
142         }
143         tags_entry.set_text (tag_string.str());
144         
145         return true;
146 }
147
148 bool
149 SoundFileBox::tags_entry_left (GdkEventFocus* event)
150 {       
151         apply_btn_clicked ();
152         
153         return true;
154 }
155
156 void
157 SoundFileBox::play_btn_clicked ()
158 {
159         if (!_session) {
160                 return;
161         }
162
163         _session->cancel_audition();
164
165         if (access(path.c_str(), R_OK)) {
166                 warning << string_compose(_("Could not read file: %1 (%2)."), path, strerror(errno)) << endmsg;
167                 return;
168         }
169
170         typedef std::map<string, boost::shared_ptr<AudioRegion> > RegionCache; 
171         static  RegionCache region_cache;
172         RegionCache::iterator the_region;
173
174         if ((the_region = region_cache.find (path)) == region_cache.end()) {
175                 SourceList srclist;
176                 boost::shared_ptr<AudioFileSource> afs;
177                 
178                 for (int n = 0; n < sf_info.channels; ++n) {
179                         try {
180                                 afs = boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createReadable (*_session, path+":"+string_compose("%1", n), AudioFileSource::Flag (0)));
181                                 srclist.push_back(afs);
182
183                         } catch (failed_constructor& err) {
184                                 error << _("Could not access soundfile: ") << path << endmsg;
185                                 return;
186                         }
187                 }
188
189                 if (srclist.empty()) {
190                         return;
191                 }
192
193                 string rname;
194
195                 _session->region_name (rname, Glib::path_get_basename(srclist[0]->name()), false);
196
197                 pair<string,boost::shared_ptr<AudioRegion> > newpair;
198                 pair<RegionCache::iterator,bool> res;
199
200                 newpair.first = path;
201                 newpair.second = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (srclist, 0, srclist[0]->length(), rname, 0, Region::DefaultFlags, false));
202
203                 res = region_cache.insert (newpair);
204                 the_region = res.first;
205         }
206
207         play_btn.hide();
208         stop_btn.show();
209
210         boost::shared_ptr<Region> r = boost::static_pointer_cast<Region> (the_region->second);
211
212         _session->audition_region(r);
213 }
214
215 void
216 SoundFileBox::stop_btn_clicked ()
217 {
218         if (_session) {
219                 _session->cancel_audition();
220                 play_btn.show();
221                 stop_btn.hide();
222         }
223 }
224
225 void
226 SoundFileBox::apply_btn_clicked ()
227 {
228         string tag_string = tags_entry.get_text ();
229
230         vector<string> tags;
231
232         static const string DELIMITERS = ",";
233         
234         // Skip delimiters at beginning.
235     string::size_type last_pos = tag_string.find_first_not_of(DELIMITERS, 0);
236     // Find first "non-delimiter".
237     string::size_type pos = tag_string.find_first_of(DELIMITERS, last_pos);
238
239     while (string::npos != pos || string::npos != last_pos) {
240                 string x = tag_string.substr(last_pos, pos - last_pos);
241                 strip_whitespace_edges (x);
242                 if (x.length()) {
243                         tags.push_back (x);
244                 }
245         // Skip delimiters.  Note the "not_of"
246         last_pos = tag_string.find_first_not_of(DELIMITERS, pos);
247         // Find next "non-delimiter"
248         pos = tag_string.find_first_of(DELIMITERS, last_pos);
249     }
250     
251         Library->set_tags (path, tags);
252         Library->save_changes ();
253 }
254
255 void
256 SoundFileBox::audition_status_changed (bool active)
257 {
258         ENSURE_GUI_THREAD(bind (mem_fun (*this, &SoundFileBox::audition_status_changed), active));
259         
260         if (!active) {
261                 stop_btn_clicked ();
262         }
263 }
264
265 // this needs to be kept in sync with the ImportMode enum defined in editing.h and editing_syms.h.
266 static const char *import_mode_strings[] = {
267         N_("Add to Region list"),
268         N_("Add to selected Track(s)"),
269         N_("Add as new Track(s)"),
270         N_("Add as new Tape Track(s)"),
271         0
272 };
273
274 SoundFileBrowser::SoundFileBrowser (string title, ARDOUR::Session* s)
275         : ArdourDialog (title, false),
276           chooser (Gtk::FILE_CHOOSER_ACTION_OPEN)
277 {
278         set_default_size (700, 500);
279         get_vbox()->pack_start(chooser);
280         chooser.set_preview_widget(preview);
281         chooser.set_select_multiple (true);
282
283         chooser.signal_update_preview().connect(mem_fun(*this, &SoundFileBrowser::update_preview));
284
285         set_session (s);
286 }
287
288 void
289 SoundFileBrowser::set_session (Session* s)
290 {
291         preview.set_session(s);
292 }
293
294 void
295 SoundFileBrowser::update_preview ()
296 {
297         chooser.set_preview_widget_active(preview.setup_labels(chooser.get_filename()));
298 }
299
300 SoundFileChooser::SoundFileChooser (string title, ARDOUR::Session* s)
301         :
302         SoundFileBrowser(title, s)
303 {
304         add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
305         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
306         show_all ();
307 }
308
309 vector<string> SoundFileOmega::mode_strings;
310
311 SoundFileOmega::SoundFileOmega (string title, ARDOUR::Session* s)
312         : SoundFileBrowser (title, s),
313           split_check (_("Split Channels"))
314 {
315         if (mode_strings.empty()) {
316                 mode_strings = I18N (import_mode_strings);
317         }
318
319         ARDOUR_UI::instance()->tooltips().set_tip(split_check, 
320                         _("Create a region for each channel"));
321
322         Gtk::Button* btn = add_button (_("Embed"), ResponseEmbed);
323         ARDOUR_UI::instance()->tooltips().set_tip(*btn, 
324                         _("Link to an external file"));
325
326         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
327
328         btn = add_button (_("Import"), ResponseImport);
329         ARDOUR_UI::instance()->tooltips().set_tip(*btn, 
330                         _("Copy a file to the session folder"));
331
332         Gtk::HBox *box = manage (new Gtk::HBox());
333
334         Gtkmm2ext::set_popdown_strings (mode_combo, mode_strings);
335
336         set_mode (Editing::ImportAsRegion);
337
338         box->pack_start (split_check);
339         box->pack_start (mode_combo);
340
341         mode_combo.signal_changed().connect (mem_fun (*this, &SoundFileOmega::mode_changed));
342
343         chooser.set_extra_widget (*box);
344         
345         show_all ();
346 }
347
348 bool
349 SoundFileOmega::get_split ()
350 {
351         return split_check.get_active();
352 }
353
354 vector<Glib::ustring>
355 SoundFileOmega::get_paths ()
356 {
357         return chooser.get_filenames();
358 }
359
360 void
361 SoundFileOmega::set_mode (Editing::ImportMode mode)
362 {
363         mode_combo.set_active_text (mode_strings[(int)mode]);
364
365         switch (mode) {
366         case Editing::ImportAsRegion:
367                 split_check.set_sensitive (true);
368                 break;
369         case Editing::ImportAsTrack:
370                 split_check.set_sensitive (true);
371                 break;
372         case Editing::ImportToTrack:
373                 split_check.set_sensitive (false);
374                 break;
375         case Editing::ImportAsTapeTrack:
376                 split_check.set_sensitive (true);
377                 break;
378         }
379 }
380
381 Editing::ImportMode
382 SoundFileOmega::get_mode ()
383 {
384         vector<string>::iterator i;
385         uint32_t n;
386         string str = mode_combo.get_active_text ();
387
388         for (n = 0, i = mode_strings.begin (); i != mode_strings.end(); ++i, ++n) {
389                 if (str == (*i)) {
390                         break;
391                 }
392         }
393
394         if (i == mode_strings.end()) {
395                 fatal << string_compose (_("programming error: %1"), X_("unknown import mode string")) << endmsg;
396                 /*NOTREACHED*/
397         }
398
399         return (Editing::ImportMode) (n);
400 }
401
402 void
403 SoundFileOmega::mode_changed ()
404 {
405         Editing::ImportMode mode = get_mode();
406
407         switch (mode) {
408         case Editing::ImportAsRegion:
409                 split_check.set_sensitive (true);
410                 break;
411         case Editing::ImportAsTrack:
412                 split_check.set_sensitive (true);
413                 break;
414         case Editing::ImportToTrack:
415                 split_check.set_sensitive (false);
416                 break;
417         case Editing::ImportAsTapeTrack:
418                 split_check.set_sensitive (true);
419                 break;
420         }
421 }