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