Extract all uses of DCP-o-matic name to allow branding.
[dcpomatic.git] / src / tools / dcpomatic_combiner.cc
1 /*
2     Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21
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"
33 #include <dcp/combine.h>
34 LIBDCP_DISABLE_WARNINGS
35 #include <wx/filepicker.h>
36 LIBDCP_ENABLE_WARNINGS
37 #include <wx/wx.h>
38 #include <boost/bind/bind.hpp>
39 #include <boost/filesystem.hpp>
40 #include <exception>
41
42
43 using std::dynamic_pointer_cast;
44 using std::exception;
45 using std::make_shared;
46 using std::shared_ptr;
47 using std::string;
48 using std::vector;
49 using boost::optional;
50 #if BOOST_VERSION >= 106100
51 using namespace boost::placeholders;
52 #endif
53
54
55 static string
56 display_string (boost::filesystem::path p, int)
57 {
58         return p.filename().string();
59 }
60
61
62 class DirDialogWrapper : public DirDialog
63 {
64 public:
65         DirDialogWrapper (wxWindow* parent)
66                 : DirDialog (parent, _("Choose a DCP folder"), wxDD_DIR_MUST_EXIST, "AddCombinerInputPath")
67         {
68
69         }
70
71         virtual int ShowModal() override
72         {
73                 return DirDialog::show() ? wxID_OK : wxID_CANCEL;
74         }
75
76         optional<boost::filesystem::path> get () const
77         {
78                 return path();
79         }
80
81         void set (boost::filesystem::path)
82         {
83                 /* Not used */
84         }
85 };
86
87
88 class DOMFrame : public wxFrame
89 {
90 public:
91         explicit DOMFrame (wxString const & title)
92                 : wxFrame (nullptr, -1, title)
93         {
94                 /* Use a panel as the only child of the Frame so that we avoid
95                    the dark-grey background on Windows.
96                 */
97                 auto overall_panel = new wxPanel (this);
98                 auto s = new wxBoxSizer (wxHORIZONTAL);
99                 s->Add (overall_panel, 1, wxEXPAND);
100                 SetSizer (s);
101
102                 vector<EditableListColumn> columns;
103                 columns.push_back(EditableListColumn(_("Input DCP"), 600, true));
104
105                 _input = new EditableList<boost::filesystem::path, DirDialogWrapper>(
106                         overall_panel,
107                         columns,
108                         boost::bind(&DOMFrame::inputs, this),
109                         boost::bind(&DOMFrame::set_inputs, this, _1),
110                         &display_string,
111                         EditableListTitle::VISIBLE,
112                         EditableListButton::NEW | EditableListButton::REMOVE
113                         );
114
115                 auto output = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
116                 output->AddGrowableCol (1, 1);
117
118                 add_label_to_sizer (output, overall_panel, _("Annotation text"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
119                 _annotation_text = new wxTextCtrl (overall_panel, wxID_ANY, wxT(""));
120                 output->Add (_annotation_text, 1, wxEXPAND);
121
122                 add_label_to_sizer (output, overall_panel, _("Output DCP folder"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
123                 _output = new DirPickerCtrl (overall_panel);
124                 output->Add (_output, 1, wxEXPAND);
125
126                 _combine = new Button (overall_panel, _("Combine"));
127
128                 auto sizer = new wxBoxSizer (wxVERTICAL);
129                 sizer->Add (_input, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
130                 sizer->Add (output, 0, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
131                 sizer->Add (_combine, 0, wxALL | wxALIGN_RIGHT, DCPOMATIC_DIALOG_BORDER);
132                 overall_panel->SetSizer (sizer);
133                 Fit ();
134                 SetSize (768, GetSize().GetHeight() + 32);
135
136                 _combine->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::combine, this));
137                 _output->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind(&DOMFrame::setup_sensitivity, this));
138
139                 setup_sensitivity ();
140         }
141
142 private:
143         void set_inputs (vector<boost::filesystem::path> inputs)
144         {
145                 _inputs = inputs;
146         }
147
148         vector<boost::filesystem::path> inputs () const
149         {
150                 return _inputs;
151         }
152
153         void combine ()
154         {
155                 using namespace boost::filesystem;
156
157                 path const output = wx_to_std(_output->GetPath());
158
159                 if (is_directory(output) && !is_empty(output)) {
160                         if (!confirm_dialog (
161                                     this,
162                                     std_to_wx (
163                                             String::compose(wx_to_std(_("The directory %1 already exists and is not empty.  "
164                                                                         "Are you sure you want to use it?")),
165                                                             output.string())
166                                             )
167                                     )) {
168                                 return;
169                         }
170                 } else if (is_regular_file(output)) {
171                         error_dialog (
172                                 this,
173                                 String::compose (wx_to_std(_("%1 already exists as a file, so you cannot use it for a DCP.")), output.string())
174                                 );
175                         return;
176                 }
177
178                 auto jm = JobManager::instance ();
179                 jm->add (make_shared<CombineDCPJob>(_inputs, output, wx_to_std(_annotation_text->GetValue())));
180                 bool const ok = display_progress(variant::wx::dcpomatic_combiner(), _("Combining DCPs"));
181                 if (!ok) {
182                         return;
183                 }
184
185                 DCPOMATIC_ASSERT (!jm->get().empty());
186                 auto last = dynamic_pointer_cast<CombineDCPJob> (jm->get().back());
187                 DCPOMATIC_ASSERT (last);
188
189                 if (last->finished_ok()) {
190                         message_dialog (this, _("DCPs combined successfully."));
191                 } else {
192                         auto m = std_to_wx(last->error_summary());
193                         if (!last->error_details().empty()) {
194                                 m += wxString::Format(" (%s)", std_to_wx(last->error_details()));
195                         }
196                         error_dialog (this, m);
197                 }
198         }
199
200         void setup_sensitivity ()
201         {
202                 _combine->Enable (!_output->GetPath().IsEmpty());
203         }
204
205         EditableList<boost::filesystem::path, DirDialogWrapper>* _input;
206         wxTextCtrl* _annotation_text = nullptr;
207         DirPickerCtrl* _output;
208         vector<boost::filesystem::path> _inputs;
209         Button* _combine;
210 };
211
212
213 class App : public wxApp
214 {
215 public:
216         App () {}
217
218         bool OnInit () override
219         {
220                 try {
221                         Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1));
222                         Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
223
224                         SetAppName(variant::wx::dcpomatic_combiner());
225
226                         if (!wxApp::OnInit()) {
227                                 return false;
228                         }
229
230 #ifdef DCPOMATIC_LINUX
231                         unsetenv ("UBUNTU_MENUPROXY");
232 #endif
233
234 #ifdef DCPOMATIC_OSX
235                         make_foreground_application ();
236 #endif
237
238                         dcpomatic_setup_path_encoding ();
239
240                         /* Enable i18n; this will create a Config object
241                            to look for a force-configured language.  This Config
242                            object will be wrong, however, because dcpomatic_setup
243                            hasn't yet been called and there aren't any filters etc.
244                            set up yet.
245                         */
246                         dcpomatic_setup_i18n ();
247
248                         /* Set things up, including filters etc.
249                            which will now be internationalised correctly.
250                         */
251                         dcpomatic_setup ();
252
253                         /* Force the configuration to be re-loaded correctly next
254                            time it is needed.
255                         */
256                         Config::drop ();
257
258                         _frame = new DOMFrame(variant::wx::dcpomatic_combiner());
259                         SetTopWindow (_frame);
260
261                         _frame->Show ();
262
263                         signal_manager = new wxSignalManager (this);
264                         Bind (wxEVT_IDLE, boost::bind(&App::idle, this, _1));
265                 }
266                 catch (exception& e)
267                 {
268                         error_dialog(nullptr, wxString::Format(_("%s could not start (%s)"), variant::wx::dcpomatic_combiner()), std_to_wx(e.what()));
269                         return false;
270                 }
271
272                 return true;
273         }
274
275         void config_failed_to_load(Config::LoadFailure what)
276         {
277                 report_config_load_failure(_frame, what);
278         }
279
280         void config_warning (string m)
281         {
282                 message_dialog (_frame, std_to_wx(m));
283         }
284
285         void idle (wxIdleEvent& ev)
286         {
287                 signal_manager->ui_idle ();
288                 ev.Skip ();
289         }
290
291         void report_exception ()
292         {
293                 try {
294                         throw;
295                 } catch (FileError& e) {
296                         error_dialog (
297                                 0,
298                                 wxString::Format(
299                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
300                                         std_to_wx (e.what()),
301                                         std_to_wx (e.file().string().c_str ())
302                                         )
303                                 );
304                 } catch (exception& e) {
305                         error_dialog (
306                                 0,
307                                 wxString::Format(
308                                         _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
309                                         std_to_wx (e.what ())
310                                         )
311                                 );
312                 } catch (...) {
313                         error_dialog (nullptr, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
314                 }
315         }
316
317         bool OnExceptionInMainLoop () override
318         {
319                 report_exception ();
320                 /* This will terminate the program */
321                 return false;
322         }
323
324         void OnUnhandledException () override
325         {
326                 report_exception ();
327         }
328
329         DOMFrame* _frame = nullptr;
330 };
331
332 IMPLEMENT_APP (App)