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