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