significant changes in code to handle import/embedding - much cleaner and less code...
[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
30 #include <gtkmm2ext/utils.h>
31
32 #include <ardour/audio_library.h>
33 #include <ardour/audioregion.h>
34 #include <ardour/externalsource.h>
35
36 #include "gui_thread.h"
37 #include "prompter.h"
38 #include "sfdb_ui.h"
39 #include "utils.h"
40
41 #include "i18n.h"
42
43 using namespace ARDOUR;
44 using namespace std;
45
46 string length2string (const int32_t frames, const float sample_rate);
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", 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
238     switch (prompter.run ()) {
239                 case Gtk::RESPONSE_ACCEPT:
240                 prompter.get_result (name);
241                         if (name.length()) {
242                                 Library->add_field (name);
243                                 Library->save_changes ();
244                         }
245                 break;
246
247             default:
248                 break;
249     }
250 }
251
252 void
253 SoundFileBox::remove_field_clicked ()
254 {
255         field_view.get_selection()->selected_foreach_iter(mem_fun(*this, &SoundFileBox::delete_row));
256
257         Library->save_changes ();
258 }
259
260 void
261 SoundFileBox::delete_row (const Gtk::TreeModel::iterator& iter)
262 {
263         Gtk::TreeModel::Row row = *iter;
264
265         Library->remove_field(row[label_columns.field]);
266 }
267
268 void
269 SoundFileBox::audition_status_changed (bool active)
270 {
271         ENSURE_GUI_THREAD(bind (mem_fun (*this, &SoundFileBox::audition_status_changed), active));
272         
273         if (!active) {
274                 stop_btn_clicked ();
275         }
276 }
277
278 void
279 SoundFileBox::field_selected ()
280 {
281         if (field_view.get_selection()->count_selected_rows()) {
282                 remove_field_btn.set_sensitive(true);
283         } else {
284                 remove_field_btn.set_sensitive(false);
285         }
286 }
287
288 SoundFileBrowser::SoundFileBrowser (string title)
289         : ArdourDialog (title, false),
290           chooser (Gtk::FILE_CHOOSER_ACTION_OPEN)
291 {
292         get_vbox()->pack_start(chooser);
293         chooser.set_preview_widget(preview);
294         chooser.set_select_multiple (true);
295
296         chooser.signal_update_preview().connect(mem_fun(*this, &SoundFileBrowser::update_preview));
297 }
298
299 void
300 SoundFileBrowser::set_session (Session* s)
301 {
302         preview.set_session(s);
303 }
304
305 void
306 SoundFileBrowser::update_preview ()
307 {
308         chooser.set_preview_widget_active(preview.setup_labels(chooser.get_filename()));
309 }
310
311 SoundFileChooser::SoundFileChooser (string title)
312         :
313         SoundFileBrowser(title)
314 {
315         add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
316         add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
317         show_all ();
318 }
319
320 static const char *import_mode_strings[] = {
321         X_("Add to Region list"),
322         X_("Add as new Track(s)"),
323         X_("Add to selected Track(s)"),
324         0
325 };
326
327 vector<string> SoundFileOmega::mode_strings;
328
329 SoundFileOmega::SoundFileOmega (string title)
330         : SoundFileBrowser (title),
331           split_check (_("Split Channels"))
332 {
333         if (mode_strings.empty()) {
334                 mode_strings = internationalize (import_mode_strings);
335         }
336
337         add_button (_("Embed"), ResponseEmbed);
338         add_button (_("Import"), ResponseImport);
339         add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
340
341         Gtk::HBox *box = manage (new Gtk::HBox());
342
343         Gtkmm2ext::set_popdown_strings (mode_combo, mode_strings);
344
345         set_mode (Editing::ImportAsRegion);
346
347         box->pack_start (split_check);
348         box->pack_start (mode_combo);
349
350         mode_combo.signal_changed().connect (mem_fun (*this, &SoundFileOmega::mode_changed));
351
352         chooser.set_extra_widget (*box);
353         
354         show_all ();
355 }
356
357 bool
358 SoundFileOmega::get_split ()
359 {
360         return split_check.get_active();
361 }
362
363 vector<Glib::ustring>
364 SoundFileOmega::get_paths ()
365 {
366         return chooser.get_filenames();
367 }
368
369 void
370 SoundFileOmega::set_mode (Editing::ImportMode mode)
371 {
372         mode_combo.set_active_text (mode_strings[(int)mode]);
373
374         switch (mode) {
375         case Editing::ImportAsRegion:
376                 split_check.set_sensitive (true);
377                 break;
378         case Editing::ImportAsTrack:
379                 split_check.set_sensitive (true);
380                 break;
381         case Editing::ImportToTrack:
382                 split_check.set_sensitive (false);
383                 break;
384         }
385 }
386
387 Editing::ImportMode
388 SoundFileOmega::get_mode ()
389 {
390         vector<string>::iterator i;
391         uint32_t n;
392         string str = mode_combo.get_active_text ();
393
394         for (n = 0, i = mode_strings.begin (); i != mode_strings.end(); ++i, ++n) {
395                 if (str == (*i)) {
396                         break;
397                 }
398         }
399
400         if (i == mode_strings.end()) {
401                 fatal << string_compose (_("programming error: %1"), X_("unknown import mode string")) << endmsg;
402                 /*NOTREACHED*/
403         }
404
405         return (Editing::ImportMode) (n);
406 }
407
408 void
409 SoundFileOmega::mode_changed ()
410 {
411         Editing::ImportMode mode = get_mode();
412
413         switch (mode) {
414         case Editing::ImportAsRegion:
415                 split_check.set_sensitive (true);
416                 break;
417         case Editing::ImportAsTrack:
418                 split_check.set_sensitive (true);
419                 break;
420         case Editing::ImportToTrack:
421                 split_check.set_sensitive (false);
422                 break;
423         }
424 }