e653a48bf0151c82f7a9266acd159ffba8b00a93
[ardour.git] / gtk2_ardour / export_main_dialog.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
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 #include "export_main_dialog.h"
22
23 #include <sigc++/signal.h>
24
25 #include <pbd/filesystem.h>
26
27 #include "utils.h"
28
29 #include <ardour/export_handler.h>
30 #include <ardour/export_filename.h>
31 #include <ardour/export_format_specification.h>
32 #include <ardour/export_channel_configuration.h>
33 #include <ardour/export_preset.h>
34
35 #include "i18n.h"
36
37 using namespace ARDOUR;
38 using namespace PBD;
39
40 ExportMainDialog::ExportMainDialog (PublicEditor & editor) :
41   ArdourDialog (_("Export")),
42   editor (editor),
43
44   preset_label (_("Preset:"), Gtk::ALIGN_LEFT),
45   preset_save_button (Gtk::Stock::SAVE),
46   preset_remove_button (Gtk::Stock::REMOVE),
47   preset_new_button (Gtk::Stock::NEW),
48
49   page_counter (1),
50
51   warn_label ("", Gtk::ALIGN_LEFT),
52   list_files_label (_("<span color=\"#ffa755\">Some already existing files will be overwritten.</span>"), Gtk::ALIGN_RIGHT),
53   list_files_button (_("List files")),
54
55   timespan_label (_("Time Span"), Gtk::ALIGN_LEFT),
56
57   channels_label (_("Channels"), Gtk::ALIGN_LEFT)
58 {
59         /* Main packing */
60
61         get_vbox()->pack_start (preset_align, false, false, 0);
62         get_vbox()->pack_start (timespan_label, false, false, 0);
63         get_vbox()->pack_start (timespan_align, false, false, 0);
64         get_vbox()->pack_start (channels_label, false, false, 0);
65         get_vbox()->pack_start (channels_align, false, false, 0);
66         get_vbox()->pack_start (file_notebook, false, false, 0);
67         get_vbox()->pack_start (warn_container, true, true, 0);
68         get_vbox()->pack_start (progress_container, true, true, 0);
69         
70         timespan_align.add (timespan_selector);
71         timespan_align.set_padding (0, 12, 18, 0);
72         
73         channels_align.add (channel_selector);
74         channels_align.set_padding (0, 12, 18, 0);
75         
76         /* Preset manipulation */
77         
78         preset_list = Gtk::ListStore::create (preset_cols);
79         preset_entry.set_model (preset_list);
80         preset_entry.set_text_column (preset_cols.label);
81         
82         preset_align.add (preset_hbox);
83         preset_align.set_padding (0, 12, 0, 0);
84         
85         preset_hbox.pack_start (preset_label, false, false, 0);
86         preset_hbox.pack_start (preset_entry, true, true, 6);
87         preset_hbox.pack_start (preset_save_button, false, false, 0);
88         preset_hbox.pack_start (preset_remove_button, false, false, 6);
89         preset_hbox.pack_start (preset_new_button, false, false, 0);
90         
91         preset_save_button.set_sensitive (false);
92         preset_remove_button.set_sensitive (false);
93         preset_new_button.set_sensitive (false);
94         
95         preset_select_connection = preset_entry.signal_changed().connect (sigc::mem_fun (*this, &ExportMainDialog::select_preset));
96         preset_save_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::save_current_preset));
97         preset_new_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::save_current_preset));
98         preset_remove_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::remove_current_preset));
99         
100         /* warnings */
101         
102         warn_container.pack_start (warn_hbox, true, true, 6);
103         warn_container.pack_end (list_files_hbox, false, false, 0);
104         
105         warn_hbox.pack_start (warn_label, true, true, 16);
106         warn_label.set_use_markup (true);
107         
108         list_files_hbox.pack_end (list_files_button, false, false, 6);
109         list_files_hbox.pack_end (list_files_label, false, false, 6);
110         list_files_label.set_use_markup (true);
111         
112         list_files_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::show_conflicting_files));
113         
114         /* Progress indicators */
115         
116         progress_container.pack_start (progress_label, false, false, 6);
117         progress_container.pack_start (progress_bar, false, false, 6);
118         
119         /* Buttons */
120         
121         cancel_button = add_button (Gtk::Stock::CANCEL, RESPONSE_CANCEL);
122         rt_export_button = add_button (_("Realtime export"), RESPONSE_RT);
123         fast_export_button = add_button (_("Fast Export"), RESPONSE_FAST);
124         
125         cancel_button->signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::close_dialog));
126         rt_export_button->signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::export_rt));
127         fast_export_button->signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::export_fw));
128         
129         /* Bolding for labels */
130         
131         Pango::AttrList bold;
132         Pango::Attribute b = Pango::Attribute::create_attr_weight (Pango::WEIGHT_BOLD);
133         bold.insert (b);
134         
135         timespan_label.set_attributes (bold);
136         channels_label.set_attributes (bold);
137         
138         /* Done! */
139         
140         show_all_children ();
141         progress_container.hide_all();
142 }
143
144 ExportMainDialog::~ExportMainDialog ()
145 {
146 }
147
148 void
149 ExportMainDialog::set_session (ARDOUR::Session* s)
150 {
151         session = s;
152         
153         /* Init handler and profile manager */
154         
155         handler = session->get_export_handler ();
156         status = session->get_export_status ();
157         profile_manager.reset (new ExportProfileManager (*session));
158         
159         /* Selection range  */
160         
161         TimeSelection const & time (editor.get_selection().time);
162         if (!time.empty()) {
163                 profile_manager->set_selection_range (time.front().start, time.front().end);
164         } else {
165                 profile_manager->set_selection_range ();
166         }
167         
168         /* Last notebook page */
169         
170         new_file_button.add (*Gtk::manage (new Gtk::Image (::get_icon("add"))));
171         new_file_button.set_alignment (0, 0.5);
172         new_file_button.set_relief (Gtk::RELIEF_NONE);
173         
174         new_file_hbox.pack_start (new_file_button, true, true);
175         file_notebook.append_page (new_file_dummy, new_file_hbox);
176         file_notebook.set_tab_label_packing (new_file_dummy, true, true, Gtk::PACK_START);
177         new_file_hbox.show_all_children ();
178         
179         page_change_connection = file_notebook.signal_switch_page().connect (sigc::mem_fun (*this, &ExportMainDialog::handle_page_change));
180         new_file_button.signal_clicked().connect (sigc::mem_fun (*this, &ExportMainDialog::add_new_file_page));
181         
182         /* Load states */
183         
184         profile_manager->load_profile ();
185         sync_with_manager ();
186         
187         /* Warnings */
188         
189         timespan_selector.CriticalSelectionChanged.connect (sigc::mem_fun (*this, &ExportMainDialog::update_warnings));
190         channel_selector.CriticalSelectionChanged.connect (sigc::mem_fun (*this, &ExportMainDialog::update_warnings));
191         status->Aborting.connect (sigc::mem_fun (*this, &ExportMainDialog::notify_errors));
192         
193         update_warnings ();
194 }
195
196 void
197 ExportMainDialog::select_timespan (Glib::ustring id)
198 {
199         set_title ("Export Range");
200         timespan_selector.select_one_range (id);
201 }
202
203 void
204 ExportMainDialog::notify_errors ()
205 {
206         if (status->errors()) {
207                 Glib::ustring txt = _("Export has been aborted due to an error!\nSee the Log for details.");
208                 Gtk::MessageDialog msg (txt, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
209                 msg.run();
210         }
211 }
212
213 void
214 ExportMainDialog::close_dialog ()
215 {
216         if (status->running) {
217                 status->abort();
218         }
219         
220         hide_all ();
221         set_modal (false);
222         
223 }
224
225 void
226 ExportMainDialog::sync_with_manager ()
227 {
228         /* Clear pages from notebook
229            The page switch handling has to be disabled during removing all pages due to a gtk bug
230          */
231
232         page_change_connection.block();
233         while (file_notebook.get_n_pages() > 1) {
234                 file_notebook.remove_page (0);
235         }
236         page_change_connection.block(false);
237         
238         page_counter = 1;
239         last_visible_page = 0;
240
241         /* Preset list */
242         
243         preset_list->clear();
244         
245         PresetList const & presets = profile_manager->get_presets();
246         Gtk::ListStore::iterator tree_it;
247         
248         for (PresetList::const_iterator it = presets.begin(); it != presets.end(); ++it) {
249                 tree_it = preset_list->append();
250                 tree_it->set_value (preset_cols.preset, *it);
251                 tree_it->set_value (preset_cols.label, Glib::ustring ((*it)->name()));
252                 
253                 if (*it == current_preset) {
254                         preset_select_connection.block (true);
255                         preset_entry.set_active (tree_it);
256                         preset_select_connection.block (false);
257                 }
258         }       
259
260         /* Timespan and channel config */
261
262         timespan_selector.set_state (profile_manager->get_timespans().front(), session);
263         channel_selector.set_state (profile_manager->get_channel_configs().front(), session);
264
265         /* File notebook */
266
267         ExportProfileManager::FormatStateList const & formats = profile_manager->get_formats ();
268         ExportProfileManager::FormatStateList::const_iterator format_it;
269         ExportProfileManager::FilenameStateList const & filenames = profile_manager->get_filenames ();
270         ExportProfileManager::FilenameStateList::const_iterator filename_it;
271         for (format_it = formats.begin(), filename_it = filenames.begin();
272              format_it != formats.end() && filename_it != filenames.end();
273              ++format_it, ++filename_it) {
274                 add_file_page (*format_it, *filename_it);
275         }
276         
277         file_notebook.set_current_page (0);
278         update_warnings ();
279 }
280
281 void
282 ExportMainDialog::update_warnings ()
283 {
284         /* Reset state */
285
286         warn_string = "";
287         warn_label.set_markup (warn_string);
288
289         list_files_hbox.hide ();
290         list_files_string = "";
291         
292         fast_export_button->set_sensitive (true);
293         rt_export_button->set_sensitive (true);
294
295         /* Add new warnings */
296
297         boost::shared_ptr<ExportProfileManager::Warnings> warnings = profile_manager->get_warnings();
298
299         for (std::list<Glib::ustring>::iterator it = warnings->errors.begin(); it != warnings->errors.end(); ++it) {
300                 add_error (*it);
301         }
302
303         for (std::list<Glib::ustring>::iterator it = warnings->warnings.begin(); it != warnings->warnings.end(); ++it) {
304                 add_warning (*it);
305         }
306
307         if (!warnings->conflicting_filenames.empty()) {
308                 list_files_hbox.show ();
309                 for (std::list<Glib::ustring>::iterator it = warnings->conflicting_filenames.begin(); it != warnings->conflicting_filenames.end(); ++it) {
310                         ustring::size_type pos = it->find_last_of ("/");
311                         list_files_string += "\n" + it->substr (0, pos + 1) + "<b>" + it->substr (pos + 1) + "</b>";
312                 }
313         }
314 }
315
316 void
317 ExportMainDialog::show_conflicting_files ()
318 {
319         ArdourDialog dialog (_("Files that will be overwritten"), true);
320         
321         Gtk::Label label ("", Gtk::ALIGN_LEFT);
322         label.set_use_markup (true);
323         label.set_markup (list_files_string);
324         
325         dialog.get_vbox()->pack_start (label);
326         dialog.add_button (Gtk::Stock::OK, 0);
327         dialog.show_all_children ();
328         
329         dialog.run();
330 }
331
332 void
333 ExportMainDialog::export_rt ()
334 {
335         profile_manager->prepare_for_export ();
336         handler->do_export (true);
337         show_progress ();
338 }
339
340 void
341 ExportMainDialog::export_fw ()
342 {
343         profile_manager->prepare_for_export ();
344         handler->do_export (false);
345         show_progress ();
346 }
347
348 void
349 ExportMainDialog::show_progress ()
350 {
351         status->running = true;
352
353         cancel_button->set_label (_("Stop Export"));
354         rt_export_button->set_sensitive (false);
355         fast_export_button->set_sensitive (false);
356
357         progress_bar.set_fraction (0.0);
358         warn_container.hide_all();
359         progress_container.show ();
360         progress_container.show_all_children ();
361         progress_connection = Glib::signal_timeout().connect (mem_fun(*this, &ExportMainDialog::progress_timeout), 100);
362         
363         gtk_main_iteration ();
364         while (status->running) {
365                 if (gtk_events_pending()) {
366                         gtk_main_iteration ();
367                 } else {
368                         usleep (10000);
369                 }
370         }
371 }
372
373 Glib::ustring
374 ExportMainDialog::get_nth_format_name (uint32_t n)
375 {
376         FilePage * page;
377         if ((page = dynamic_cast<FilePage *> (file_notebook.get_nth_page (n - 1)))) {
378                 return page->get_format_name();
379         }
380         return "";
381 }
382
383 gint
384 ExportMainDialog::progress_timeout ()
385 {
386         switch (status->stage) {
387           case export_None:
388                 progress_label.set_text ("");
389                 break;
390           case export_ReadTimespan:
391                 progress_label.set_text (string_compose (_("Reading timespan %1 of %2"), status->timespan, status->total_timespans));
392                 break;
393           case export_PostProcess:
394                 progress_label.set_text (string_compose (_("Processing file %2 of %3 (%1) from timespan %4 of %5"),
395                                                          get_nth_format_name (status->format),
396                                                          status->format, status->total_formats,
397                                                          status->timespan, status->total_timespans));
398                 break;
399           case export_Write:
400                 progress_label.set_text (string_compose (_("Encoding file %2 of %3 (%1) from timespan %4 of %5"),
401                                                          get_nth_format_name (status->format),
402                                                          status->format, status->total_formats,
403                                                          status->timespan, status->total_timespans));
404                 break;
405         }
406
407         progress_bar.set_fraction (status->progress);
408         return TRUE;
409 }
410
411 void
412 ExportMainDialog::select_preset ()
413 {
414         Gtk::ListStore::iterator it = preset_entry.get_active ();
415         Glib::ustring text = preset_entry.get_entry()->get_text();
416         bool preset_name_exists = false;
417         
418         for (PresetList::const_iterator it = profile_manager->get_presets().begin(); it != profile_manager->get_presets().end(); ++it) {
419                 if (!(*it)->name().compare (text)) { preset_name_exists = true; }
420         }
421         
422         if (preset_list->iter_is_valid (it)) {
423         
424                 previous_preset = current_preset = it->get_value (preset_cols.preset);
425                 if (!profile_manager->load_preset (current_preset)) {
426                         Gtk::MessageDialog dialog (_("The selected preset did not load successfully!\nPerhaps it references a format that has been removed?"),
427                                                    false, Gtk::MESSAGE_WARNING);
428                         dialog.run ();
429                 }
430                 sync_with_manager ();
431                 
432                 /* Make an edit, so that signal changed will be emitted on re-selection */
433                 
434                 preset_select_connection.block (true);
435                 preset_entry.get_entry()->set_text ("");
436                 preset_entry.get_entry()->set_text (text);
437                 preset_select_connection.block (false);
438         
439         } else { // Text has been edited 
440                 if (previous_preset && !text.compare (previous_preset->name())) {
441                         
442                         current_preset = previous_preset;
443                         
444                 } else {
445                         current_preset.reset ();
446                         profile_manager->load_preset (current_preset);
447                 }
448         }
449         
450         preset_save_button.set_sensitive (current_preset);
451         preset_remove_button.set_sensitive (current_preset);
452         preset_new_button.set_sensitive (!current_preset && !text.empty() && !preset_name_exists);
453 }
454
455 void
456 ExportMainDialog::save_current_preset ()
457 {
458         if (!profile_manager) { return; }
459         
460         previous_preset = current_preset = profile_manager->save_preset (preset_entry.get_entry()->get_text());
461         sync_with_manager ();
462         select_preset (); // Update preset widget states
463 }
464
465 void
466 ExportMainDialog::remove_current_preset ()
467 {
468         if (!profile_manager) { return; }
469         
470         profile_manager->remove_preset();
471         preset_entry.get_entry()->set_text ("");
472         sync_with_manager ();
473 }
474
475 ExportMainDialog::FilePage::FilePage (Session * s, ManagerPtr profile_manager, ExportMainDialog * parent, uint32_t number,
476                                       ExportProfileManager::FormatStatePtr format_state,
477                                       ExportProfileManager::FilenameStatePtr filename_state) :
478   format_state (format_state),
479   filename_state (filename_state),
480   profile_manager (profile_manager),
481
482   format_label (_("Format"), Gtk::ALIGN_LEFT),
483   filename_label (_("Location"), Gtk::ALIGN_LEFT),
484   tab_number (number)
485 {
486         set_border_width (12);
487
488         pack_start (format_label, false, false, 0);
489         pack_start (format_align, false, false, 0);
490         pack_start (filename_label, false, false, 0);
491         pack_start (filename_align, false, false, 0);
492         
493         format_align.add (format_selector);
494         format_align.set_padding (6, 12, 18, 0);
495         
496         filename_align.add (filename_selector);
497         filename_align.set_padding (0, 12, 18, 0);
498         
499         Pango::AttrList bold;
500         Pango::Attribute b = Pango::Attribute::create_attr_weight (Pango::WEIGHT_BOLD);
501         bold.insert (b);
502         
503         format_label.set_attributes (bold);
504         filename_label.set_attributes (bold);
505         tab_label.set_attributes (bold);
506         
507         /* Set states */
508         format_selector.set_state (format_state, s);
509         filename_selector.set_state (filename_state, s);
510         
511         /* Signals */
512         
513         tab_close_button.signal_clicked().connect (sigc::bind (sigc::mem_fun (*parent, &ExportMainDialog::remove_file_page), this));
514         
515         profile_manager->FormatListChanged.connect (sigc::mem_fun (format_selector, &ExportFormatSelector::update_format_list));
516         
517         format_selector.FormatEdited.connect (sigc::mem_fun (*this, &ExportMainDialog::FilePage::save_format_to_manager));
518         format_selector.FormatRemoved.connect (sigc::mem_fun (*profile_manager, &ExportProfileManager::remove_format_profile));
519         format_selector.NewFormat.connect (sigc::mem_fun (*profile_manager, &ExportProfileManager::get_new_format));
520         
521         format_selector.CriticalSelectionChanged.connect (sigc::mem_fun (*this, &ExportMainDialog::FilePage::update_tab_label));
522         filename_selector.CriticalSelectionChanged.connect (CriticalSelectionChanged.make_slot());
523         
524         /* Tab widget */
525         
526         tab_close_button.add (*Gtk::manage (new Gtk::Image (::get_icon("close"))));
527         tab_close_alignment.add (tab_close_button);
528         tab_close_alignment.set (0.5, 0.5, 0, 0);
529         
530         tab_widget.pack_start (tab_label, false, false, 3);
531         tab_widget.pack_end (tab_close_alignment, false, false, 0);
532         tab_widget.show_all_children ();
533         update_tab_label ();
534         
535         /* Done */
536         
537         show_all_children ();
538 }
539
540 ExportMainDialog::FilePage::~FilePage ()
541 {
542 }
543
544 void
545 ExportMainDialog::FilePage::set_remove_sensitive (bool value)
546 {
547         tab_close_button.set_sensitive (value);
548 }
549
550 Glib::ustring
551 ExportMainDialog::FilePage::get_format_name () const
552 {
553         if (format_state && format_state->format) {
554                 return format_state->format->name();
555         }
556         return "No format!";
557 }
558
559 void
560 ExportMainDialog::FilePage::save_format_to_manager (FormatPtr format)
561 {
562         profile_manager->save_format_to_disk (format);
563 }
564
565 void
566 ExportMainDialog::FilePage::update_tab_label ()
567 {
568         tab_label.set_text (string_compose ("%1 %2", tab_number, get_format_name()));
569         CriticalSelectionChanged();
570 }
571
572 void
573 ExportMainDialog::add_new_file_page ()
574 {
575         FilePage * page;
576         if ((page = dynamic_cast<FilePage *> (file_notebook.get_nth_page (file_notebook.get_current_page())))) {
577                 add_file_page (profile_manager->duplicate_format_state (page->get_format_state()),
578                                profile_manager->duplicate_filename_state (page->get_filename_state()));
579         }
580 }
581
582 void
583 ExportMainDialog::add_file_page (ExportProfileManager::FormatStatePtr format_state, ExportProfileManager::FilenameStatePtr filename_state)
584 {
585         FilePage * page = Gtk::manage (new FilePage (session, profile_manager, this, page_counter, format_state, filename_state));
586         page->CriticalSelectionChanged.connect (sigc::mem_fun (*this, &ExportMainDialog::update_warnings));
587         file_notebook.insert_page (*page, page->get_tab_widget(), file_notebook.get_n_pages() - 1);
588
589         update_remove_file_page_sensitivity ();
590         file_notebook.show_all_children();
591         ++page_counter;
592         
593         update_warnings ();
594 }
595
596 void
597 ExportMainDialog::remove_file_page (FilePage * page)
598 {
599         profile_manager->remove_format_state (page->get_format_state());
600         profile_manager->remove_filename_state (page->get_filename_state());
601
602         file_notebook.remove_page (*page);
603         update_remove_file_page_sensitivity ();
604         
605         update_warnings ();
606 }
607
608 void
609 ExportMainDialog::update_remove_file_page_sensitivity ()
610 {
611         FilePage * page;
612         if ((page = dynamic_cast<FilePage *> (file_notebook.get_nth_page (0)))) {
613                 if (file_notebook.get_n_pages() > 2) {
614                         page->set_remove_sensitive (true);
615                 } else {
616                         page->set_remove_sensitive (false);
617                 }
618         }
619 }
620
621 void
622 ExportMainDialog::handle_page_change (GtkNotebookPage*, uint page)
623 {
624         if (page + 1 == (uint32_t) file_notebook.get_n_pages()) {
625                 file_notebook.set_current_page (last_visible_page);
626         } else {
627                 last_visible_page = page;
628         }
629 }
630
631 void
632 ExportMainDialog::add_error (Glib::ustring const & text)
633 {
634         fast_export_button->set_sensitive (false);
635         rt_export_button->set_sensitive (false);
636         
637         if (warn_string.empty()) {
638                 warn_string = _("<span color=\"#ffa755\">Error: ") + text + "</span>";
639         } else {
640                 warn_string = _("<span color=\"#ffa755\">Error: ") + text + "</span>\n" + warn_string;
641         }
642         
643         warn_label.set_markup (warn_string);
644 }
645
646 void
647 ExportMainDialog::add_warning (Glib::ustring const & text)
648 {
649         if (warn_string.empty()) {
650                 warn_string = _("<span color=\"#ffa755\">Warning: ") + text + "</span>";
651         } else {
652                 warn_string = warn_string + _("\n<span color=\"#ffa755\">Warning: ") + text + "</span>";
653         }
654         
655         warn_label.set_markup (warn_string);
656 }