/* Copyright (C) 2020-2021 Carl Hetherington This file is part of DCP-o-matic. DCP-o-matic is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DCP-o-matic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DCP-o-matic. If not, see . */ #include "wx/dir_dialog.h" #include "wx/dir_picker_ctrl.h" #include "wx/editable_list.h" #include "wx/i18n_setup.h" #include "wx/wx_signal_manager.h" #include "wx/wx_util.h" #include "wx/wx_variant.h" #include "lib/combine_dcp_job.h" #include "lib/config.h" #include "lib/constants.h" #include "lib/cross.h" #include "lib/job_manager.h" #include "lib/util.h" #include LIBDCP_DISABLE_WARNINGS #include LIBDCP_ENABLE_WARNINGS #include #include #include #include using std::dynamic_pointer_cast; using std::exception; using std::make_shared; using std::shared_ptr; using std::string; using std::vector; using boost::optional; #if BOOST_VERSION >= 106100 using namespace boost::placeholders; #endif static string display_string (boost::filesystem::path p, int) { return p.filename().string(); } class DirDialogWrapper : public DirDialog { public: DirDialogWrapper (wxWindow* parent) : DirDialog (parent, _("Choose a DCP folder"), wxDD_DIR_MUST_EXIST, "AddCombinerInputPath") { } virtual int ShowModal() override { return DirDialog::show() ? wxID_OK : wxID_CANCEL; } vector get() const { return { path() }; } void set (boost::filesystem::path) { /* Not used */ } }; class DOMFrame : public wxFrame { public: explicit DOMFrame (wxString const & title) : wxFrame (nullptr, -1, title) { /* Use a panel as the only child of the Frame so that we avoid the dark-grey background on Windows. */ auto overall_panel = new wxPanel (this); auto s = new wxBoxSizer (wxHORIZONTAL); s->Add (overall_panel, 1, wxEXPAND); SetSizer (s); vector columns; columns.push_back(EditableListColumn(_("Input DCP"), 600, true)); _input = new EditableList( overall_panel, columns, boost::bind(&DOMFrame::inputs, this), boost::bind(&DOMFrame::set_inputs, this, _1), &display_string, EditableListTitle::VISIBLE, EditableListButton::NEW | EditableListButton::REMOVE ); auto output = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP); output->AddGrowableCol (1, 1); add_label_to_sizer (output, overall_panel, _("Annotation text"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL); _annotation_text = new wxTextCtrl(overall_panel, wxID_ANY, {}); output->Add (_annotation_text, 1, wxEXPAND); add_label_to_sizer (output, overall_panel, _("Output DCP folder"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL); _output = new DirPickerCtrl (overall_panel); output->Add (_output, 1, wxEXPAND); _combine = new Button (overall_panel, _("Combine")); auto sizer = new wxBoxSizer (wxVERTICAL); sizer->Add (_input, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER); sizer->Add (output, 0, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER); sizer->Add (_combine, 0, wxALL | wxALIGN_RIGHT, DCPOMATIC_DIALOG_BORDER); overall_panel->SetSizer (sizer); Fit (); SetSize(768, GetSize().GetHeight() + 96); _combine->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::combine, this)); _output->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind(&DOMFrame::setup_sensitivity, this)); setup_sensitivity (); } private: void set_inputs (vector inputs) { _inputs = inputs; } vector inputs () const { return _inputs; } void combine () { using namespace boost::filesystem; path const output = wx_to_std(_output->GetPath()); if (is_directory(output) && !is_empty(output)) { if (!confirm_dialog ( this, std_to_wx ( fmt::format(wx_to_std(_("The directory {} already exists and is not empty. " "Are you sure you want to use it?")), output.string()) ) )) { return; } } else if (is_regular_file(output)) { error_dialog ( this, wxString::Format(_("%s already exists as a file, so you cannot use it for a DCP."), std_to_wx(output.string())) ); return; } auto jm = JobManager::instance (); jm->add (make_shared(_inputs, output, wx_to_std(_annotation_text->GetValue()))); bool const ok = display_progress(variant::wx::dcpomatic_combiner(), _("Combining DCPs")); if (!ok) { return; } DCPOMATIC_ASSERT (!jm->get().empty()); auto last = dynamic_pointer_cast (jm->get().back()); DCPOMATIC_ASSERT (last); if (last->finished_ok()) { message_dialog (this, _("DCPs combined successfully.")); } else { auto m = std_to_wx(last->error_summary()); if (!last->error_details().empty()) { m += wxString::Format(char_to_wx(" (%s)"), std_to_wx(last->error_details())); } error_dialog (this, m); } } void setup_sensitivity () { _combine->Enable (!_output->GetPath().IsEmpty()); } EditableList* _input; wxTextCtrl* _annotation_text = nullptr; DirPickerCtrl* _output; vector _inputs; Button* _combine; }; class App : public wxApp { public: App () {} bool OnInit () override { try { Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1)); Config::Warning.connect (boost::bind (&App::config_warning, this, _1)); SetAppName(variant::wx::dcpomatic_combiner()); if (!wxApp::OnInit()) { return false; } #ifdef DCPOMATIC_LINUX unsetenv ("UBUNTU_MENUPROXY"); #endif #ifdef DCPOMATIC_OSX make_foreground_application (); #endif dcpomatic_setup_path_encoding (); /* Enable i18n; this will create a Config object to look for a force-configured language. This Config object will be wrong, however, because dcpomatic_setup hasn't yet been called and there aren't any filters etc. set up yet. */ dcpomatic::wx::setup_i18n(); /* Set things up, including filters etc. which will now be internationalised correctly. */ dcpomatic_setup (); /* Force the configuration to be re-loaded correctly next time it is needed. */ Config::drop (); _frame = new DOMFrame(variant::wx::dcpomatic_combiner()); SetTopWindow (_frame); _frame->Show (); signal_manager = new wxSignalManager (this); Bind (wxEVT_IDLE, boost::bind(&App::idle, this, _1)); } catch (exception& e) { error_dialog(nullptr, wxString::Format(_("%s could not start (%s)"), variant::wx::dcpomatic_combiner()), std_to_wx(e.what())); return false; } return true; } void config_failed_to_load(Config::LoadFailure what) { report_config_load_failure(_frame, what); } void config_warning (string m) { message_dialog (_frame, std_to_wx(m)); } void idle (wxIdleEvent& ev) { signal_manager->ui_idle (); ev.Skip (); } void report_exception () { try { throw; } catch (FileError& e) { error_dialog ( 0, wxString::Format( _("An exception occurred: %s (%s)\n\n%s"), std_to_wx(e.what()), std_to_wx(e.file().string().c_str()), dcpomatic::wx::report_problem() ) ); } catch (exception& e) { error_dialog ( 0, wxString::Format( _("An exception occurred: %s\n\n%s"), std_to_wx(e.what()), dcpomatic::wx::report_problem() ) ); } catch (...) { error_dialog(nullptr, wxString::Format(_("An unknown exception occurred. %s"), dcpomatic::wx::report_problem())); } } bool OnExceptionInMainLoop () override { report_exception (); /* This will terminate the program */ return false; } void OnUnhandledException () override { report_exception (); } DOMFrame* _frame = nullptr; }; IMPLEMENT_APP (App)