ab12356989034651e14c462acbd54fcd72dc54b8
[ardour.git] / gtk2_ardour / sfdb_ui.cc
1 /*
2     Copyright (C) 2005 Paul Davis 
3     Written by Taybin Rutkin
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19
20 */
21
22 #include <map>
23 #include <cerrno>
24
25 #include <gtkmm/box.h>
26 #include <gtkmm/stock.h>
27
28 #include <pbd/convert.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         fields(Gtk::ListStore::create(label_columns)),
56         main_box (false, 3),
57         top_box (true, 4),
58         bottom_box (true, 4),
59         play_btn(_("Play")),
60         stop_btn(_("Stop")),
61         add_field_btn(_("Add Field...")),
62         remove_field_btn(_("Remove Field"))
63 {
64         set_name (X_("SoundFileBox"));
65         border_frame.set_label (_("Soundfile Info"));
66         border_frame.add (main_box);
67
68         pack_start (border_frame);
69         set_border_width (4);
70
71         main_box.set_border_width (4);
72
73         main_box.pack_start(length, false, false);
74         main_box.pack_start(format, false, false);
75         main_box.pack_start(channels, false, false);
76         main_box.pack_start(samplerate, false, false);
77         main_box.pack_start(field_view, true, true);
78         main_box.pack_start(top_box, false, false);
79         main_box.pack_start(bottom_box, false, false);
80
81         field_view.set_model (fields);
82         field_view.set_size_request(200, 150);
83         field_view.append_column (_("Field"), label_columns.field);
84         field_view.append_column_editable (_("Value"), label_columns.data);
85
86         top_box.set_homogeneous(true);
87         top_box.pack_start(add_field_btn);
88         top_box.pack_start(remove_field_btn);
89
90         remove_field_btn.set_sensitive(false);
91
92         bottom_box.set_homogeneous(true);
93         bottom_box.pack_start(play_btn);
94         bottom_box.pack_start(stop_btn);
95
96         play_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::play_btn_clicked));
97         stop_btn.signal_clicked().connect (mem_fun (*this, &SoundFileBox::stop_btn_clicked));
98
99         add_field_btn.signal_clicked().connect
100                         (mem_fun (*this, &SoundFileBox::add_field_clicked));
101         remove_field_btn.signal_clicked().connect
102                         (mem_fun (*this, &SoundFileBox::remove_field_clicked));
103         
104         Gtk::CellRendererText* cell(dynamic_cast<Gtk::CellRendererText*>(field_view.get_column_cell_renderer(1)));
105         cell->signal_edited().connect (mem_fun (*this, &SoundFileBox::field_edited));
106
107         field_view.get_selection()->signal_changed().connect (mem_fun (*this, &SoundFileBox::field_selected));
108         Library->fields_changed.connect (mem_fun (*this, &SoundFileBox::setup_fields));
109
110         show_all();
111         stop_btn.hide();
112 }
113
114 void
115 SoundFileBox::set_session(Session* s)
116 {
117         _session = s;
118
119         if (!_session) {
120                 play_btn.set_sensitive(false);
121         } else {
122                 _session->AuditionActive.connect(mem_fun (*this, &SoundFileBox::audition_status_changed));
123         }
124 }
125
126 bool
127 SoundFileBox::setup_labels (string filename) 
128 {
129         path = filename;
130
131         string error_msg;
132         if(!AudioFileSource::get_soundfile_info (filename, sf_info, error_msg)) {
133                 return false;
134         }
135
136         length.set_alignment (0.0f, 0.0f);
137         length.set_text (string_compose("Length: %1", PBD::length2string(sf_info.length, sf_info.samplerate)));
138
139         format.set_alignment (0.0f, 0.0f);
140         format.set_text (sf_info.format_name);
141
142         channels.set_alignment (0.0f, 0.0f);
143         channels.set_text (string_compose("Channels: %1", sf_info.channels));
144
145         samplerate.set_alignment (0.0f, 0.0f);
146         samplerate.set_text (string_compose("Samplerate: %1", sf_info.samplerate));
147
148         setup_fields ();
149
150         return true;
151 }
152
153 void
154 SoundFileBox::setup_fields ()
155 {
156         ENSURE_GUI_THREAD(mem_fun (*this, &SoundFileBox::setup_fields));
157
158         fields->clear ();
159
160         vector<string> field_list;
161         Library->get_fields(field_list);
162
163         vector<string>::iterator i;
164         Gtk::TreeModel::iterator iter;
165         Gtk::TreeModel::Row row;
166         for (i = field_list.begin(); i != field_list.end(); ++i) {
167                 if (!(*i == _("channels") || *i == _("samplerate") ||
168                         *i == _("resolution") || *i == _("format"))) {
169                         iter = fields->append();
170                         row = *iter;
171
172                         string value = Library->get_field(path, *i);
173                         row[label_columns.field] = *i;
174                         row[label_columns.data]  = value;
175                 }
176         }
177 }
178
179 void
180 SoundFileBox::play_btn_clicked ()
181 {
182         if (!_session) {
183                 return;
184         }
185
186         _session->cancel_audition();
187
188         if (access(path.c_str(), R_OK)) {
189                 warning << string_compose(_("Could not read file: %1 (%2)."), path, strerror(errno)) << endmsg;
190                 return;
191         }
192
193         typedef std::map<string, boost::shared_ptr<AudioRegion> > RegionCache; 
194         static  RegionCache region_cache;
195         RegionCache::iterator the_region;
196
197         if ((the_region = region_cache.find (path)) == region_cache.end()) {
198                 SourceList srclist;
199                 boost::shared_ptr<AudioFileSource> afs;
200                 
201                 for (int n = 0; n < sf_info.channels; ++n) {
202                         try {
203                                 afs = boost::dynamic_pointer_cast<AudioFileSource> (SourceFactory::createReadable (*_session, path+":"+string_compose("%1", n), AudioFileSource::Flag (0)));
204                                 srclist.push_back(afs);
205
206                         } catch (failed_constructor& err) {
207                                 error << _("Could not access soundfile: ") << path << endmsg;
208                                 return;
209                         }
210                 }
211
212                 if (srclist.empty()) {
213                         return;
214                 }
215
216                 string rname;
217
218                 _session->region_name (rname, Glib::path_get_basename(srclist[0]->name()), false);
219
220                 pair<string,boost::shared_ptr<AudioRegion> > newpair;
221                 pair<RegionCache::iterator,bool> res;
222
223                 newpair.first = path;
224                 newpair.second = boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (srclist, 0, srclist[0]->length(), rname, 0, Region::DefaultFlags, false));
225
226
227                 res = region_cache.insert (newpair);
228                 the_region = res.first;
229         }
230
231         play_btn.hide();
232         stop_btn.show();
233
234         boost::shared_ptr<Region> r = boost::static_pointer_cast<Region> (the_region->second);
235
236         _session->audition_region(r);
237 }
238
239 void
240 SoundFileBox::stop_btn_clicked ()
241 {
242         if (_session) {
243                 _session->cancel_audition();
244                 play_btn.show();
245                 stop_btn.hide();
246         }
247 }
248
249 void
250 SoundFileBox::add_field_clicked ()
251 {
252     ArdourPrompter prompter (true);
253     string name;
254
255     prompter.set_prompt (_("Name for Field"));
256     prompter.add_button (Gtk::Stock::ADD, Gtk::RESPONSE_ACCEPT);
257     prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
258
259     switch (prompter.run ()) {
260                 case Gtk::RESPONSE_ACCEPT:
261                 prompter.get_result (name);
262                         if (name.length()) {
263                                 Library->add_field (name);
264                                 Library->save_changes ();
265                         }
266                 break;
267
268             default:
269                 break;
270     }
271 }
272
273 void
274 SoundFileBox::remove_field_clicked ()
275 {
276         field_view.get_selection()->selected_foreach_iter(mem_fun(*this, &SoundFileBox::delete_row));
277
278         Library->save_changes ();
279 }
280
281 void
282 SoundFileBox::field_edited (const Glib::ustring& str1, const Glib::ustring& str2)
283 {
284         Gtk::TreeModel::Children rows(fields->children());
285         Gtk::TreeModel::Row row(rows[atoi(str1.c_str())]);
286         
287         Library->set_field (path, row[label_columns.field], str2);
288         
289         Library->save_changes ();
290 }
291
292 void
293 SoundFileBox::delete_row (const Gtk::TreeModel::iterator& iter)
294 {
295         Gtk::TreeModel::Row row = *iter;
296
297         Library->remove_field(row[label_columns.field]);
298 }
299
300 void
301 SoundFileBox::audition_status_changed (bool active)
302 {
303         ENSURE_GUI_THREAD(bind (mem_fun (*this, &SoundFileBox::audition_status_changed), active));
304         
305         if (!active) {
306                 stop_btn_clicked ();
307         }
308 }
309
310 void
311 SoundFileBox::field_selected ()
312 {
313         if (field_view.get_selection()->count_selected_rows()) {
314                 remove_field_btn.set_sensitive(true);
315         } else {
316                 remove_field_btn.set_sensitive(false);
317         }
318 }
319
320 // this needs to be kept in sync with the ImportMode enum defined in editing.h and editing_syms.h.
321 static const char *import_mode_strings[] = {
322         N_("Add to Region list"),
323         N_("Add to selected Track(s)"),
324         N_("Add as new Track(s)"),
325         N_("Add as new Tape Track(s)"),
326         0
327 };
328
329 SoundFileBrowser::SoundFileBrowser (string title, ARDOUR::Session* s)
330         : ArdourDialog (title, false),
331           chooser (Gtk::FILE_CHOOSER_ACTION_OPEN)
332 {
333         get_vbox()->pack_start(chooser);
334         chooser.set_preview_widget(preview);
335         chooser.set_select_multiple (true);
336
337         chooser.signal_update_preview().connect(mem_fun(*this, &SoundFileBrowser::update_preview));
338
339         set_session (s);
340 }
341
342 void
343 SoundFileBrowser::set_session (Session* s)
344 {
345         preview.set_session(s);
346 }
347
348 void
349 SoundFileBrowser::update_preview ()
350 {
351         chooser.set_preview_widget_active(preview.setup_labels(chooser.get_filename()));
352 }
353
354 SoundFileChooser::SoundFileChooser (string title, ARDOUR::Session* s)
355         :
356         SoundFileBrowser(title, s)
357 {
358         add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
359         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
360         show_all ();
361 }
362
363 vector<string> SoundFileOmega::mode_strings;
364
365 SoundFileOmega::SoundFileOmega (string title, ARDOUR::Session* s)
366         : SoundFileBrowser (title, s),
367           split_check (_("Split Channels"))
368 {
369         if (mode_strings.empty()) {
370                 mode_strings = I18N (import_mode_strings);
371         }
372
373         ARDOUR_UI::instance()->tooltips().set_tip(split_check, 
374                         _("Create a region for each channel"));
375
376         Gtk::Button* btn = add_button (_("Embed"), ResponseEmbed);
377         ARDOUR_UI::instance()->tooltips().set_tip(*btn, 
378                         _("Link to an external file"));
379
380         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
381
382         btn = add_button (_("Import"), ResponseImport);
383         ARDOUR_UI::instance()->tooltips().set_tip(*btn, 
384                         _("Copy a file to the session folder"));
385
386         Gtk::HBox *box = manage (new Gtk::HBox());
387
388         Gtkmm2ext::set_popdown_strings (mode_combo, mode_strings);
389
390         set_mode (Editing::ImportAsRegion);
391
392         box->pack_start (split_check);
393         box->pack_start (mode_combo);
394
395         mode_combo.signal_changed().connect (mem_fun (*this, &SoundFileOmega::mode_changed));
396
397         chooser.set_extra_widget (*box);
398         
399         show_all ();
400 }
401
402 bool
403 SoundFileOmega::get_split ()
404 {
405         return split_check.get_active();
406 }
407
408 vector<Glib::ustring>
409 SoundFileOmega::get_paths ()
410 {
411         return chooser.get_filenames();
412 }
413
414 void
415 SoundFileOmega::set_mode (Editing::ImportMode mode)
416 {
417         mode_combo.set_active_text (mode_strings[(int)mode]);
418
419         switch (mode) {
420         case Editing::ImportAsRegion:
421                 split_check.set_sensitive (true);
422                 break;
423         case Editing::ImportAsTrack:
424                 split_check.set_sensitive (true);
425                 break;
426         case Editing::ImportToTrack:
427                 split_check.set_sensitive (false);
428                 break;
429         case Editing::ImportAsTapeTrack:
430                 split_check.set_sensitive (true);
431                 break;
432         }
433 }
434
435 Editing::ImportMode
436 SoundFileOmega::get_mode ()
437 {
438         vector<string>::iterator i;
439         uint32_t n;
440         string str = mode_combo.get_active_text ();
441
442         for (n = 0, i = mode_strings.begin (); i != mode_strings.end(); ++i, ++n) {
443                 if (str == (*i)) {
444                         break;
445                 }
446         }
447
448         if (i == mode_strings.end()) {
449                 fatal << string_compose (_("programming error: %1"), X_("unknown import mode string")) << endmsg;
450                 /*NOTREACHED*/
451         }
452
453         return (Editing::ImportMode) (n);
454 }
455
456 void
457 SoundFileOmega::mode_changed ()
458 {
459         Editing::ImportMode mode = get_mode();
460
461         switch (mode) {
462         case Editing::ImportAsRegion:
463                 split_check.set_sensitive (true);
464                 break;
465         case Editing::ImportAsTrack:
466                 split_check.set_sensitive (true);
467                 break;
468         case Editing::ImportToTrack:
469                 split_check.set_sensitive (false);
470                 break;
471         case Editing::ImportAsTapeTrack:
472                 split_check.set_sensitive (true);
473                 break;
474         }
475 }