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