Always show splash even if we already have a config.xml.
[dcpomatic.git] / src / wx / wx_util.cc
1 /*
2     Copyright (C) 2012-2018 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 /** @file src/wx/wx_util.cc
22  *  @brief Some utility functions and classes.
23  */
24
25 #include "wx_util.h"
26 #include "file_picker_ctrl.h"
27 #include "static_text.h"
28 #include "lib/config.h"
29 #include "lib/job_manager.h"
30 #include "lib/util.h"
31 #include "lib/cross.h"
32 #include "lib/job.h"
33 #include <dcp/locale_convert.h>
34 #include <wx/spinctrl.h>
35 #include <wx/splash.h>
36 #include <wx/progdlg.h>
37 #include <wx/filepicker.h>
38 #include <boost/thread.hpp>
39
40 using std::string;
41 using std::vector;
42 using std::pair;
43 using boost::shared_ptr;
44 using boost::optional;
45 using dcp::locale_convert;
46
47 wxStaticText *
48 #ifdef __WXOSX__
49 create_label (wxWindow* p, wxString t, bool left)
50 #else
51 create_label (wxWindow* p, wxString t, bool)
52 #endif
53 {
54 #ifdef __WXOSX__
55         if (left) {
56                 t += wxT (":");
57         }
58 #endif
59         return new StaticText (p, t);
60 }
61
62 /** Add a wxStaticText to a wxSizer, aligning it at vertical centre.
63  *  @param s Sizer to add to.
64  *  @param p Parent window for the wxStaticText.
65  *  @param t Text for the wxStaticText.
66  *  @param left true if this label is a `left label'; ie the sort
67  *  of label which should be right-aligned on OS X.
68  *  @param prop Proportion to pass when calling Add() on the wxSizer.
69  */
70 wxStaticText *
71 add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, bool left, int prop, int flags)
72 {
73 #ifdef __WXOSX__
74         if (left) {
75                 flags |= wxALIGN_RIGHT;
76         }
77 #endif
78         wxStaticText* m = create_label (p, t, left);
79         s->Add (m, prop, flags, 6);
80         return m;
81 }
82
83 wxStaticText *
84 #ifdef __WXOSX__
85 add_label_to_sizer (wxSizer* s, wxStaticText* t, bool left, int prop, int flags)
86 #else
87 add_label_to_sizer (wxSizer* s, wxStaticText* t, bool, int prop, int flags)
88 #endif
89 {
90 #ifdef __WXOSX__
91         if (left) {
92                 flags |= wxALIGN_RIGHT;
93         }
94 #endif
95         s->Add (t, prop, flags, 6);
96         return t;
97 }
98
99 wxStaticText *
100 add_label_to_sizer (wxGridBagSizer* s, wxWindow* p, wxString t, bool left, wxGBPosition pos, wxGBSpan span)
101 {
102         int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
103 #ifdef __WXOSX__
104         if (left) {
105                 flags |= wxALIGN_RIGHT;
106         }
107 #endif
108         wxStaticText* m = create_label (p, t, left);
109         s->Add (m, pos, span, flags);
110         return m;
111 }
112
113 wxStaticText *
114 #ifdef __WXOSX__
115 add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool left, wxGBPosition pos, wxGBSpan span)
116 #else
117 add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool, wxGBPosition pos, wxGBSpan span)
118 #endif
119 {
120         int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
121 #ifdef __WXOSX__
122         if (left) {
123                 flags |= wxALIGN_RIGHT;
124         }
125 #endif
126         s->Add (t, pos, span, flags);
127         return t;
128 }
129
130 /** Pop up an error dialogue box.
131  *  @param parent Parent.
132  *  @param m Message.
133  *  @param e Extended message.
134  */
135 void
136 error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
137 {
138         wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK | wxICON_ERROR);
139         if (e) {
140                 wxString em = *e;
141                 em[0] = wxToupper (em[0]);
142                 d->SetExtendedMessage (em);
143         }
144         d->ShowModal ();
145         d->Destroy ();
146 }
147
148 /** Pop up an error dialogue box.
149  *  @param parent Parent.
150  *  @param m Message.
151  */
152 void
153 message_dialog (wxWindow* parent, wxString m)
154 {
155         wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxOK | wxICON_INFORMATION);
156         d->ShowModal ();
157         d->Destroy ();
158 }
159
160 bool
161 confirm_dialog (wxWindow* parent, wxString m)
162 {
163         wxMessageDialog* d = new wxMessageDialog (parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
164         int const r = d->ShowModal ();
165         d->Destroy ();
166         return r == wxID_YES;
167 }
168
169
170 /** @param s wxWidgets string.
171  *  @return Corresponding STL string.
172  */
173 string
174 wx_to_std (wxString s)
175 {
176         return string (s.ToUTF8 ());
177 }
178
179 /** @param s STL string.
180  *  @return Corresponding wxWidgets string.
181  */
182 wxString
183 std_to_wx (string s)
184 {
185         return wxString (s.c_str(), wxConvUTF8);
186 }
187
188 string
189 string_client_data (wxClientData* o)
190 {
191         return wx_to_std (dynamic_cast<wxStringClientData*>(o)->GetData());
192 }
193
194 void
195 checked_set (FilePickerCtrl* widget, boost::filesystem::path value)
196 {
197         if (widget->GetPath() != std_to_wx (value.string())) {
198                 if (value.empty()) {
199                         /* Hack to make wxWidgets clear the control when we are passed
200                            an empty value.
201                         */
202                         value = " ";
203                 }
204                 widget->SetPath (std_to_wx (value.string()));
205         }
206 }
207
208 void
209 checked_set (wxDirPickerCtrl* widget, boost::filesystem::path value)
210 {
211         if (widget->GetPath() != std_to_wx (value.string())) {
212                 if (value.empty()) {
213                         /* Hack to make wxWidgets clear the control when we are passed
214                            an empty value.
215                         */
216                         value = " ";
217                 }
218                 widget->SetPath (std_to_wx (value.string()));
219         }
220 }
221
222 void
223 checked_set (wxSpinCtrl* widget, int value)
224 {
225         if (widget->GetValue() != value) {
226                 widget->SetValue (value);
227         }
228 }
229
230 void
231 checked_set (wxSpinCtrlDouble* widget, double value)
232 {
233         /* XXX: completely arbitrary epsilon */
234         if (fabs (widget->GetValue() - value) > 1e-16) {
235                 widget->SetValue (value);
236         }
237 }
238
239 void
240 checked_set (wxChoice* widget, int value)
241 {
242         if (widget->GetSelection() != value) {
243                 widget->SetSelection (value);
244         }
245 }
246
247 void
248 checked_set (wxChoice* widget, string value)
249 {
250         wxClientData* o = 0;
251         if (widget->GetSelection() != -1) {
252                 o = widget->GetClientObject (widget->GetSelection ());
253         }
254
255         if (!o || string_client_data(o) != value) {
256                 for (unsigned int i = 0; i < widget->GetCount(); ++i) {
257                         if (string_client_data (widget->GetClientObject (i)) == value) {
258                                 widget->SetSelection (i);
259                         }
260                 }
261         }
262 }
263
264 void
265 checked_set (wxChoice* widget, vector<pair<string, string> > items)
266 {
267        vector<pair<string, string> > current;
268        for (unsigned int i = 0; i < widget->GetCount(); ++i) {
269                current.push_back (
270                        make_pair (
271                                wx_to_std (widget->GetString (i)),
272                                string_client_data (widget->GetClientObject (i))
273                                )
274                        );
275        }
276
277        if (current == items) {
278                return;
279        }
280
281        widget->Clear ();
282        for (vector<pair<string, string> >::const_iterator i = items.begin(); i != items.end(); ++i) {
283                widget->Append (std_to_wx (i->first), new wxStringClientData (std_to_wx (i->second)));
284        }
285 }
286
287 void
288 checked_set (wxTextCtrl* widget, string value)
289 {
290         if (widget->GetValue() != std_to_wx (value)) {
291                 widget->ChangeValue (std_to_wx (value));
292         }
293 }
294
295 void
296 checked_set (wxTextCtrl* widget, wxString value)
297 {
298         if (widget->GetValue() != value) {
299                 widget->ChangeValue (value);
300         }
301 }
302
303 void
304 checked_set (wxStaticText* widget, string value)
305 {
306         if (widget->GetLabel() != std_to_wx (value)) {
307                 widget->SetLabel (std_to_wx (value));
308         }
309 }
310
311 void
312 checked_set (wxStaticText* widget, wxString value)
313 {
314         if (widget->GetLabel() != value) {
315                 widget->SetLabel (value);
316         }
317 }
318
319 void
320 checked_set (wxCheckBox* widget, bool value)
321 {
322         if (widget->GetValue() != value) {
323                 widget->SetValue (value);
324         }
325 }
326
327 void
328 checked_set (wxRadioButton* widget, bool value)
329 {
330         if (widget->GetValue() != value) {
331                 widget->SetValue (value);
332         }
333 }
334
335 void
336 dcpomatic_setup_i18n ()
337 {
338         int language = wxLANGUAGE_DEFAULT;
339
340         boost::optional<string> config_lang = Config::instance()->language ();
341         if (config_lang && !config_lang->empty ()) {
342                 wxLanguageInfo const * li = wxLocale::FindLanguageInfo (std_to_wx (config_lang.get ()));
343                 if (li) {
344                         language = li->Language;
345                 }
346         }
347
348         wxLocale* locale = 0;
349         if (wxLocale::IsAvailable (language)) {
350                 locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
351
352 #ifdef DCPOMATIC_WINDOWS
353                 locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
354 #endif
355
356 #ifdef DCPOMATIC_LINUX
357                 locale->AddCatalogLookupPathPrefix (LINUX_LOCALE_PREFIX);
358
359                 /* We have to include the wxWidgets .mo in our distribution,
360                    so we rename it to avoid clashes with any other installation
361                    of wxWidgets.
362                 */
363                 locale->AddCatalog (wxT ("dcpomatic2-wxstd"));
364 #endif
365
366                 locale->AddCatalog (wxT ("libdcpomatic2-wx"));
367                 locale->AddCatalog (wxT ("dcpomatic2"));
368
369                 if (!locale->IsOk()) {
370                         delete locale;
371                         locale = new wxLocale (wxLANGUAGE_ENGLISH);
372                 }
373         }
374
375         if (locale) {
376                 dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
377         }
378 }
379
380 int
381 wx_get (wxSpinCtrl* w)
382 {
383         return w->GetValue ();
384 }
385
386 int
387 wx_get (wxChoice* w)
388 {
389         return w->GetSelection ();
390 }
391
392 double
393 wx_get (wxSpinCtrlDouble* w)
394 {
395         return w->GetValue ();
396 }
397
398 /** @param s String of the form Context|String
399  *  @return translation, or String if no translation is available.
400  */
401 wxString
402 context_translation (wxString s)
403 {
404         wxString t = wxGetTranslation (s);
405         if (t == s) {
406                 /* No translation; strip the context */
407                 int c = t.Find (wxT ("|"));
408                 if (c != wxNOT_FOUND) {
409                         t = t.Mid (c + 1);
410                 }
411         }
412
413         return t;
414 }
415
416 wxString
417 time_to_timecode (DCPTime t, double fps)
418 {
419         double w = t.seconds ();
420         int const h = (w / 3600);
421         w -= h * 3600;
422         int const m = (w / 60);
423         w -= m * 60;
424         int const s = floor (w);
425         w -= s;
426         int const f = lrint (w * fps);
427         return wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f);
428 }
429
430 void
431 setup_audio_channels_choice (wxChoice* choice, int minimum)
432 {
433         vector<pair<string, string> > items;
434         for (int i = minimum; i <= 16; i += 2) {
435                 if (i == 2) {
436                         items.push_back (make_pair (wx_to_std (_("2 - stereo")), locale_convert<string> (i)));
437                 } else if (i == 4) {
438                         items.push_back (make_pair (wx_to_std (_("4 - L/C/R/Lfe")), locale_convert<string> (i)));
439                 } else if (i == 6) {
440                         items.push_back (make_pair (wx_to_std (_("6 - 5.1")), locale_convert<string> (i)));
441                 } else if (i == 8) {
442                         items.push_back (make_pair (wx_to_std (_("8 - 5.1/HI/VI")), locale_convert<string> (i)));
443                 } else if (i == 12) {
444                         items.push_back (make_pair (wx_to_std (_("12 - 7.1/HI/VI")), locale_convert<string> (i)));
445                 } else {
446                         items.push_back (make_pair (locale_convert<string> (i), locale_convert<string> (i)));
447                 }
448         }
449
450         checked_set (choice, items);
451 }
452
453 wxSplashScreen *
454 maybe_show_splash ()
455 {
456         wxSplashScreen* splash = 0;
457         try {
458                 wxBitmap bitmap;
459                 boost::filesystem::path p = shared_path () / "splash.png";
460                 if (bitmap.LoadFile (std_to_wx (p.string ()), wxBITMAP_TYPE_PNG)) {
461                         splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1);
462                         wxYield ();
463                 }
464         } catch (boost::filesystem::filesystem_error& e) {
465                 /* Maybe we couldn't find the splash image; never mind */
466         }
467
468         return splash;
469 }
470
471 double
472 calculate_mark_interval (double mark_interval)
473 {
474         if (mark_interval > 5) {
475                 mark_interval -= lrint (mark_interval) % 5;
476         }
477         if (mark_interval > 10) {
478                 mark_interval -= lrint (mark_interval) % 10;
479         }
480         if (mark_interval > 60) {
481                 mark_interval -= lrint (mark_interval) % 60;
482         }
483         if (mark_interval > 3600) {
484                 mark_interval -= lrint (mark_interval) % 3600;
485         }
486
487         if (mark_interval < 1) {
488                 mark_interval = 1;
489         }
490
491         return mark_interval;
492 }
493
494
495 /** @return false if the task was cancelled */
496 bool
497 display_progress (wxString title, wxString task)
498 {
499         JobManager* jm = JobManager::instance ();
500
501         wxProgressDialog progress (title, task, 100, 0, wxPD_CAN_ABORT);
502
503         bool ok = true;
504
505         while (jm->work_to_do()) {
506                 dcpomatic_sleep (1);
507                 if (!progress.Pulse()) {
508                         /* user pressed cancel */
509                         BOOST_FOREACH (shared_ptr<Job> i, jm->get()) {
510                                 i->cancel();
511                         }
512                         ok = false;
513                         break;
514                 }
515         }
516
517         return ok;
518 }
519
520 bool
521 report_errors_from_last_job (wxWindow* parent)
522 {
523         JobManager* jm = JobManager::instance ();
524
525         DCPOMATIC_ASSERT (!jm->get().empty());
526
527         shared_ptr<Job> last = jm->get().back();
528         if (last->finished_in_error()) {
529                 error_dialog(parent, std_to_wx(last->error_summary()) + ".\n", std_to_wx(last->error_details()));
530                 return false;
531         }
532
533         return true;
534 }