2 Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
22 #include "wx/dir_dialog.h"
23 #include "wx/dir_picker_ctrl.h"
24 #include "wx/editable_list.h"
25 #include "wx/wx_signal_manager.h"
26 #include "wx/wx_util.h"
27 #include "wx/wx_variant.h"
28 #include "lib/combine_dcp_job.h"
29 #include "lib/config.h"
30 #include "lib/constants.h"
31 #include "lib/cross.h"
32 #include "lib/job_manager.h"
34 #include <dcp/combine.h>
35 LIBDCP_DISABLE_WARNINGS
36 #include <wx/filepicker.h>
37 LIBDCP_ENABLE_WARNINGS
39 #include <boost/bind/bind.hpp>
40 #include <boost/filesystem.hpp>
44 using std::dynamic_pointer_cast;
46 using std::make_shared;
47 using std::shared_ptr;
50 using boost::optional;
51 #if BOOST_VERSION >= 106100
52 using namespace boost::placeholders;
57 display_string (boost::filesystem::path p, int)
59 return p.filename().string();
63 class DirDialogWrapper : public DirDialog
66 DirDialogWrapper (wxWindow* parent)
67 : DirDialog (parent, _("Choose a DCP folder"), wxDD_DIR_MUST_EXIST, "AddCombinerInputPath")
72 virtual int ShowModal() override
74 return DirDialog::show() ? wxID_OK : wxID_CANCEL;
77 optional<boost::filesystem::path> get () const
82 void set (boost::filesystem::path)
89 class DOMFrame : public wxFrame
92 explicit DOMFrame (wxString const & title)
93 : wxFrame (nullptr, -1, title)
95 /* Use a panel as the only child of the Frame so that we avoid
96 the dark-grey background on Windows.
98 auto overall_panel = new wxPanel (this);
99 auto s = new wxBoxSizer (wxHORIZONTAL);
100 s->Add (overall_panel, 1, wxEXPAND);
103 vector<EditableListColumn> columns;
104 columns.push_back(EditableListColumn(_("Input DCP"), 600, true));
106 _input = new EditableList<boost::filesystem::path, DirDialogWrapper>(
109 boost::bind(&DOMFrame::inputs, this),
110 boost::bind(&DOMFrame::set_inputs, this, _1),
112 EditableListTitle::VISIBLE,
113 EditableListButton::NEW | EditableListButton::REMOVE
116 auto output = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
117 output->AddGrowableCol (1, 1);
119 add_label_to_sizer (output, overall_panel, _("Annotation text"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
120 _annotation_text = new wxTextCtrl (overall_panel, wxID_ANY, wxT(""));
121 output->Add (_annotation_text, 1, wxEXPAND);
123 add_label_to_sizer (output, overall_panel, _("Output DCP folder"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
124 _output = new DirPickerCtrl (overall_panel);
125 output->Add (_output, 1, wxEXPAND);
127 _combine = new Button (overall_panel, _("Combine"));
129 auto sizer = new wxBoxSizer (wxVERTICAL);
130 sizer->Add (_input, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
131 sizer->Add (output, 0, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
132 sizer->Add (_combine, 0, wxALL | wxALIGN_RIGHT, DCPOMATIC_DIALOG_BORDER);
133 overall_panel->SetSizer (sizer);
135 SetSize (768, GetSize().GetHeight() + 32);
137 _combine->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::combine, this));
138 _output->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind(&DOMFrame::setup_sensitivity, this));
140 setup_sensitivity ();
144 void set_inputs (vector<boost::filesystem::path> inputs)
149 vector<boost::filesystem::path> inputs () const
156 using namespace boost::filesystem;
158 path const output = wx_to_std(_output->GetPath());
160 if (is_directory(output) && !is_empty(output)) {
161 if (!confirm_dialog (
164 String::compose(wx_to_std(_("The directory %1 already exists and is not empty. "
165 "Are you sure you want to use it?")),
171 } else if (is_regular_file(output)) {
174 String::compose (wx_to_std(_("%1 already exists as a file, so you cannot use it for a DCP.")), output.string())
179 auto jm = JobManager::instance ();
180 jm->add (make_shared<CombineDCPJob>(_inputs, output, wx_to_std(_annotation_text->GetValue())));
181 bool const ok = display_progress(variant::wx::dcpomatic_combiner(), _("Combining DCPs"));
186 DCPOMATIC_ASSERT (!jm->get().empty());
187 auto last = dynamic_pointer_cast<CombineDCPJob> (jm->get().back());
188 DCPOMATIC_ASSERT (last);
190 if (last->finished_ok()) {
191 message_dialog (this, _("DCPs combined successfully."));
193 auto m = std_to_wx(last->error_summary());
194 if (!last->error_details().empty()) {
195 m += wxString::Format(" (%s)", std_to_wx(last->error_details()));
197 error_dialog (this, m);
201 void setup_sensitivity ()
203 _combine->Enable (!_output->GetPath().IsEmpty());
206 EditableList<boost::filesystem::path, DirDialogWrapper>* _input;
207 wxTextCtrl* _annotation_text = nullptr;
208 DirPickerCtrl* _output;
209 vector<boost::filesystem::path> _inputs;
214 class App : public wxApp
219 bool OnInit () override
222 Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1));
223 Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
225 SetAppName(variant::wx::dcpomatic_combiner());
227 if (!wxApp::OnInit()) {
231 #ifdef DCPOMATIC_LINUX
232 unsetenv ("UBUNTU_MENUPROXY");
236 make_foreground_application ();
239 dcpomatic_setup_path_encoding ();
241 /* Enable i18n; this will create a Config object
242 to look for a force-configured language. This Config
243 object will be wrong, however, because dcpomatic_setup
244 hasn't yet been called and there aren't any filters etc.
247 dcpomatic_setup_i18n ();
249 /* Set things up, including filters etc.
250 which will now be internationalised correctly.
254 /* Force the configuration to be re-loaded correctly next
259 _frame = new DOMFrame(variant::wx::dcpomatic_combiner());
260 SetTopWindow (_frame);
264 signal_manager = new wxSignalManager (this);
265 Bind (wxEVT_IDLE, boost::bind(&App::idle, this, _1));
269 error_dialog(nullptr, wxString::Format(_("%s could not start (%s)"), variant::wx::dcpomatic_combiner()), std_to_wx(e.what()));
276 void config_failed_to_load(Config::LoadFailure what)
278 report_config_load_failure(_frame, what);
281 void config_warning (string m)
283 message_dialog (_frame, std_to_wx(m));
286 void idle (wxIdleEvent& ev)
288 signal_manager->ui_idle ();
292 void report_exception ()
296 } catch (FileError& e) {
300 _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
301 std_to_wx (e.what()),
302 std_to_wx (e.file().string().c_str ())
305 } catch (exception& e) {
309 _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
310 std_to_wx (e.what ())
314 error_dialog (nullptr, _("An unknown exception occurred.") + " " + REPORT_PROBLEM);
318 bool OnExceptionInMainLoop () override
321 /* This will terminate the program */
325 void OnUnhandledException () override
330 DOMFrame* _frame = nullptr;