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