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