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