Restore time zone to Cinema and improve UI to use it (#2473).
[dcpomatic.git] / src / wx / wx_util.cc
1 /*
2     Copyright (C) 2012-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 /** @file src/wx/wx_util.cc
23  *  @brief Some utility functions and classes.
24  */
25
26
27 #include "file_picker_ctrl.h"
28 #include "language_tag_widget.h"
29 #include "password_entry.h"
30 #include "region_subtag_widget.h"
31 #include "static_text.h"
32 #include "wx_ptr.h"
33 #include "wx_util.h"
34 #include "wx_variant.h"
35 #include "lib/config.h"
36 #include "lib/cross.h"
37 #include "lib/job.h"
38 #include "lib/job_manager.h"
39 #include "lib/util.h"
40 #include "lib/variant.h"
41 #include "lib/version.h"
42 #include <dcp/locale_convert.h>
43 #include <dcp/warnings.h>
44 LIBDCP_DISABLE_WARNINGS
45 #include <wx/filepicker.h>
46 #include <wx/progdlg.h>
47 #include <wx/sizer.h>
48 #include <wx/spinctrl.h>
49 #include <wx/splash.h>
50 LIBDCP_ENABLE_WARNINGS
51 #include <boost/thread.hpp>
52
53
54 using std::string;
55 using std::vector;
56 using std::pair;
57 using std::shared_ptr;
58 using boost::optional;
59 using dcp::locale_convert;
60 using namespace dcpomatic;
61
62
63 wxStaticText *
64 #ifdef __WXOSX__
65 create_label (wxWindow* p, wxString t, bool left)
66 #else
67 create_label (wxWindow* p, wxString t, bool)
68 #endif
69 {
70 #ifdef __WXOSX__
71         if (left) {
72                 t += wxT (":");
73         }
74 #endif
75         return new StaticText (p, t);
76 }
77
78
79 #ifdef __WXOSX__
80 static
81 void
82 setup_osx_flags (wxSizer* s, bool left, int& flags)
83 {
84         if (left) {
85                 auto box = dynamic_cast<wxBoxSizer*>(s);
86                 if (!box || box->GetOrientation() != wxHORIZONTAL) {
87                         flags |= wxALIGN_RIGHT;
88                 }
89         }
90 }
91 #endif
92
93
94 /** Add a wxStaticText to a wxSizer.
95  *  @param s Sizer to add to.
96  *  @param p Parent window for the wxStaticText.
97  *  @param t Text for the wxStaticText.
98  *  @param left true if this label is a `left label'; ie the sort
99  *  of label which should be right-aligned on OS X.
100  *  @param prop Proportion to pass when calling Add() on the wxSizer.
101  */
102 wxStaticText *
103 add_label_to_sizer (wxSizer* s, wxWindow* p, wxString t, bool left, int prop, int flags)
104 {
105 #ifdef __WXOSX__
106         setup_osx_flags (s, left, flags);
107 #endif
108         auto m = create_label (p, t, left);
109         s->Add (m, prop, flags, DCPOMATIC_SIZER_GAP);
110         return m;
111 }
112
113
114 wxStaticText *
115 #ifdef __WXOSX__
116 add_label_to_sizer (wxSizer* s, wxStaticText* t, bool left, int prop, int flags)
117 #else
118 add_label_to_sizer (wxSizer* s, wxStaticText* t, bool, int prop, int flags)
119 #endif
120 {
121 #ifdef __WXOSX__
122         setup_osx_flags (s, left, flags);
123 #endif
124         s->Add (t, prop, flags, DCPOMATIC_SIZER_GAP);
125         return t;
126 }
127
128
129 wxStaticText *
130 add_label_to_sizer(wxGridBagSizer* s, wxWindow* p, wxString t, bool left, wxGBPosition pos, wxGBSpan span, bool indent)
131 {
132         int flags = wxALIGN_CENTER_VERTICAL | wxLEFT;
133 #ifdef __WXOSX__
134         setup_osx_flags (s, left, flags);
135 #endif
136         auto m = create_label (p, t, left);
137         s->Add(m, pos, span, flags, indent ? DCPOMATIC_SIZER_X_GAP : 0);
138         return m;
139 }
140
141
142 wxStaticText *
143 #ifdef __WXOSX__
144 add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool left, wxGBPosition pos, wxGBSpan span)
145 #else
146 add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool, wxGBPosition pos, wxGBSpan span)
147 #endif
148 {
149         int flags = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT;
150 #ifdef __WXOSX__
151         setup_osx_flags (s, left, flags);
152 #endif
153         s->Add (t, pos, span, flags);
154         return t;
155 }
156
157
158 /** Pop up an error dialogue box.
159  *  @param parent Parent.
160  *  @param m Message.
161  *  @param e Extended message.
162  */
163 void
164 error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
165 {
166         auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_ERROR);
167         if (e) {
168                 wxString em = *e;
169                 em[0] = wxToupper (em[0]);
170                 d->SetExtendedMessage (em);
171         }
172         d->ShowModal ();
173 }
174
175
176 /** Pop up an error dialogue box.
177  *  @param parent Parent.
178  *  @param m Message.
179  */
180 void
181 message_dialog (wxWindow* parent, wxString m)
182 {
183         auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_INFORMATION);
184         d->ShowModal ();
185 }
186
187
188 /** @return true if the user answered "yes" */
189 bool
190 confirm_dialog (wxWindow* parent, wxString m)
191 {
192         auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxYES_NO | wxICON_QUESTION);
193         return d->ShowModal() == wxID_YES;
194 }
195
196
197 /** @param s wxWidgets string.
198  *  @return Corresponding STL string.
199  */
200 string
201 wx_to_std (wxString s)
202 {
203         return string (s.ToUTF8());
204 }
205
206
207 /** @param s STL string.
208  *  @return Corresponding wxWidgets string.
209  */
210 wxString
211 std_to_wx (string s)
212 {
213         return wxString (s.c_str(), wxConvUTF8);
214 }
215
216
217 string
218 string_client_data (wxClientData* o)
219 {
220         return wx_to_std (dynamic_cast<wxStringClientData*>(o)->GetData());
221 }
222
223
224 void
225 checked_set (FilePickerCtrl* widget, boost::filesystem::path value)
226 {
227         if (widget->path() != value) {
228                 if (value.empty()) {
229                         /* Hack to make wxWidgets clear the control when we are passed
230                            an empty value.
231                         */
232                         value = " ";
233                 }
234                 widget->set_path(value);
235         }
236 }
237
238
239 void
240 checked_set (wxDirPickerCtrl* widget, boost::filesystem::path value)
241 {
242         if (widget->GetPath() != std_to_wx (value.string())) {
243                 if (value.empty()) {
244                         /* Hack to make wxWidgets clear the control when we are passed
245                            an empty value.
246                         */
247                         value = " ";
248                 }
249                 widget->SetPath (std_to_wx (value.string()));
250         }
251 }
252
253
254 void
255 checked_set (wxSpinCtrl* widget, int value)
256 {
257         if (widget->GetValue() != value) {
258                 widget->SetValue (value);
259         }
260 }
261
262
263 void
264 checked_set (wxSpinCtrlDouble* widget, double value)
265 {
266         /* XXX: completely arbitrary epsilon */
267         if (fabs (widget->GetValue() - value) > 1e-16) {
268                 widget->SetValue (value);
269         }
270 }
271
272
273 void
274 checked_set (wxChoice* widget, int value)
275 {
276         if (widget->GetSelection() != value) {
277                 widget->SetSelection (value);
278         }
279 }
280
281
282 void
283 checked_set (wxChoice* widget, string value)
284 {
285         wxClientData* o = nullptr;
286         if (widget->GetSelection() != -1) {
287                 o = widget->GetClientObject (widget->GetSelection ());
288         }
289
290         if (!o || string_client_data(o) != value) {
291                 for (unsigned int i = 0; i < widget->GetCount(); ++i) {
292                         if (string_client_data (widget->GetClientObject (i)) == value) {
293                                 widget->SetSelection (i);
294                         }
295                 }
296         }
297 }
298
299
300 void
301 checked_set (wxChoice* widget, vector<pair<string, string>> items)
302 {
303        vector<pair<string, string>> current;
304        for (unsigned int i = 0; i < widget->GetCount(); ++i) {
305                current.push_back (
306                        make_pair(
307                                wx_to_std(widget->GetString(i)),
308                                widget->GetClientData() ? string_client_data(widget->GetClientObject(i)) : ""
309                                )
310                        );
311        }
312
313        if (current == items) {
314                return;
315        }
316
317        widget->Clear ();
318        for (auto i: items) {
319                widget->Append (std_to_wx(i.first), new wxStringClientData(std_to_wx(i.second)));
320        }
321 }
322
323
324 void
325 checked_set (wxTextCtrl* widget, string value)
326 {
327         if (widget->GetValue() != std_to_wx (value)) {
328                 widget->ChangeValue (std_to_wx (value));
329         }
330 }
331
332
333 void
334 checked_set (PasswordEntry* entry, string value)
335 {
336         if (entry->get() != value) {
337                 entry->set(value);
338         }
339 }
340
341
342 void
343 checked_set (wxTextCtrl* widget, wxString value)
344 {
345         if (widget->GetValue() != value) {
346                 widget->ChangeValue (value);
347         }
348 }
349
350
351 void
352 checked_set (wxStaticText* widget, string value)
353 {
354         if (widget->GetLabel() != std_to_wx (value)) {
355                 widget->SetLabel (std_to_wx (value));
356         }
357 }
358
359
360 void
361 checked_set (wxStaticText* widget, wxString value)
362 {
363         if (widget->GetLabel() != value) {
364                 widget->SetLabel (value);
365         }
366 }
367
368
369 void
370 checked_set (wxCheckBox* widget, bool value)
371 {
372         if (widget->GetValue() != value) {
373                 widget->SetValue (value);
374         }
375 }
376
377
378 void
379 checked_set (wxRadioButton* widget, bool value)
380 {
381         if (widget->GetValue() != value) {
382                 widget->SetValue (value);
383         }
384 }
385
386
387 void
388 checked_set(LanguageTagWidget* widget, dcp::LanguageTag value)
389 {
390         if (widget->get() != value) {
391                 widget->set(value);
392         }
393 }
394
395
396 void
397 checked_set(LanguageTagWidget* widget, optional<dcp::LanguageTag> value)
398 {
399         if (widget->get() != value) {
400                 widget->set(value);
401         }
402 }
403
404
405 void
406 checked_set(RegionSubtagWidget* widget, optional<dcp::LanguageTag::RegionSubtag> value)
407 {
408         if (widget->get() != value) {
409                 widget->set(value);
410         }
411 }
412
413
414 #ifdef DCPOMATIC_OSX
415
416 void
417 dcpomatic_setup_i18n()
418 {
419         wxLog::EnableLogging();
420
421         auto get_locale_value = [](CFLocaleKey key) {
422                 CFLocaleRef cflocale = CFLocaleCopyCurrent();
423                 auto value = (CFStringRef) CFLocaleGetValue(cflocale, key);
424                 char buffer[64];
425                 CFStringGetCString(value, buffer, sizeof(buffer), kCFStringEncodingUTF8);
426                 CFRelease(cflocale);
427                 return string(buffer);
428         };
429
430         auto translations = new wxTranslations();
431
432         auto config_lang = Config::instance()->language();
433         if (config_lang && !config_lang->empty()) {
434                 translations->SetLanguage(std_to_wx(*config_lang));
435         } else {
436                 /* We want to use the user's preferred language.  It seems that if we use the wxWidgets default we will get the
437                  * language for the locale, which may not be what we want (e.g. for a machine in Germany, configured for DE locale,
438                  * but with the preferred language set to English).
439                  *
440                  * Instead, the the language code from macOS then get the corresponding canonical language string with region,
441                  * which wxTranslations::SetLanguage will accept.
442                  */
443                 auto const language_code = get_locale_value(kCFLocaleLanguageCode);
444                 /* Ideally this would be wxUILocale (as wxLocale is deprecated) but we want to keep this building
445                  * with the old wxWidgets we use for the older macOS builds.
446                  */
447                 auto const info = wxLocale::FindLanguageInfo(std_to_wx(language_code));
448                 if (info) {
449 #if wxCHECK_VERSION(3, 1, 6)
450                         translations->SetLanguage(info->GetCanonicalWithRegion());
451 #else
452                         translations->SetLanguage(info->CanonicalName);
453 #endif
454                 }
455         }
456
457 #ifdef DCPOMATIC_DEBUG
458         wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/wx/mo"));
459         wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/tools/mo"));
460 #endif
461
462         translations->AddStdCatalog();
463         translations->AddCatalog(wxT("libdcpomatic2-wx"));
464         translations->AddCatalog(wxT("dcpomatic2"));
465
466         wxTranslations::Set(translations);
467
468         dcpomatic_setup_gettext_i18n(config_lang.get_value_or(""));
469 }
470
471 #else
472
473 void
474 dcpomatic_setup_i18n ()
475 {
476         int language = wxLANGUAGE_DEFAULT;
477
478         auto config_lang = Config::instance()->language ();
479         if (config_lang && !config_lang->empty ()) {
480                 auto const li = wxLocale::FindLanguageInfo (std_to_wx (config_lang.get ()));
481                 if (li) {
482                         language = li->Language;
483                 }
484         }
485
486         wxLocale* locale = nullptr;
487         if (wxLocale::IsAvailable (language)) {
488                 locale = new wxLocale (language, wxLOCALE_LOAD_DEFAULT);
489
490 #ifdef DCPOMATIC_WINDOWS
491                 locale->AddCatalogLookupPathPrefix (std_to_wx (mo_path().string()));
492 #endif
493
494 #ifdef DCPOMATIC_LINUX
495                 locale->AddCatalogLookupPathPrefix (LINUX_LOCALE_PREFIX);
496
497                 /* We have to include the wxWidgets .mo in our distribution,
498                    so we rename it to avoid clashes with any other installation
499                    of wxWidgets.
500                 */
501                 locale->AddCatalog (wxT ("dcpomatic2-wxstd"));
502
503                 /* Fedora 29 (at least) installs wxstd3.mo instead of wxstd.mo */
504                 locale->AddCatalog (wxT ("wxstd3"));
505 #endif
506
507                 locale->AddCatalog(wxT("wxstd"));
508                 locale->AddCatalog(wxT("libdcpomatic2-wx"));
509                 locale->AddCatalog(wxT("dcpomatic2"));
510
511                 if (!locale->IsOk()) {
512                         delete locale;
513                         locale = new wxLocale (wxLANGUAGE_ENGLISH);
514                 }
515         }
516
517         if (locale) {
518                 dcpomatic_setup_gettext_i18n (wx_to_std (locale->GetCanonicalName ()));
519         }
520 }
521
522 #endif
523
524
525 int
526 wx_get (wxSpinCtrl* w)
527 {
528         return w->GetValue ();
529 }
530
531
532 int
533 wx_get (wxChoice* w)
534 {
535         return w->GetSelection ();
536 }
537
538
539 double
540 wx_get (wxSpinCtrlDouble* w)
541 {
542         return w->GetValue ();
543 }
544
545
546 /** @param s String of the form Context|String
547  *  @return translation, or String if no translation is available.
548  */
549 wxString
550 context_translation (wxString s)
551 {
552         auto t = wxGetTranslation (s);
553         if (t == s) {
554                 /* No translation; strip the context */
555                 int c = t.Find (wxT ("|"));
556                 if (c != wxNOT_FOUND) {
557                         t = t.Mid (c + 1);
558                 }
559         }
560
561         return t;
562 }
563
564
565 wxString
566 time_to_timecode (DCPTime t, double fps)
567 {
568         auto w = t.seconds ();
569         int const h = (w / 3600);
570         w -= h * 3600;
571         int const m = (w / 60);
572         w -= m * 60;
573         int const s = floor (w);
574         w -= s;
575         int const f = lrint (w * fps);
576         return wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f);
577 }
578
579
580 void
581 setup_audio_channels_choice (wxChoice* choice, int minimum)
582 {
583         vector<pair<string, string>> items;
584         for (int i = minimum; i <= 16; i += 2) {
585                 if (i == 2) {
586                         items.push_back (make_pair(wx_to_std(_("2 - stereo")), locale_convert<string>(i)));
587                 } else if (i == 4) {
588                         items.push_back (make_pair(wx_to_std(_("4 - L/C/R/Lfe")), locale_convert<string>(i)));
589                 } else if (i == 6) {
590                         items.push_back (make_pair(wx_to_std(_("6 - 5.1")), locale_convert<string>(i)));
591                 } else if (i == 8) {
592                         items.push_back (make_pair(wx_to_std(_("8 - 5.1/HI/VI")), locale_convert<string>(i)));
593                 } else if (i == 12) {
594                         items.push_back (make_pair(wx_to_std(_("12 - 7.1/HI/VI")), locale_convert<string>(i)));
595                 } else {
596                         items.push_back (make_pair(locale_convert<string> (i), locale_convert<string>(i)));
597                 }
598         }
599
600         checked_set (choice, items);
601 }
602
603
604 wxSplashScreen*
605 maybe_show_splash ()
606 {
607         if (!variant::show_splash()) {
608                 return nullptr;
609         }
610
611         wxSplashScreen* splash = nullptr;
612
613         try {
614                 wxBitmap bitmap;
615                 if (bitmap.LoadFile(bitmap_path("splash.png"), wxBITMAP_TYPE_PNG)) {
616                         {
617                                 /* This wxMemoryDC must be destroyed before bitmap can be used elsewhere */
618                                 wxMemoryDC dc(bitmap);
619                                 auto const version = wxString::Format("%s (%s)", dcpomatic_version, dcpomatic_git_commit);
620                                 auto screen_size = dc.GetSize();
621                                 auto text_size = dc.GetTextExtent(version);
622                                 dc.DrawText(version, (screen_size.GetWidth() - text_size.GetWidth()) / 2, 236);
623                         }
624 #ifdef DCPOMATIC_WINDOWS
625                         /* Having wxSTAY_ON_TOP means error dialogues hide behind the splash screen on Windows, no matter what I try */
626                         splash = new wxSplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, nullptr, -1, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE | wxFRAME_NO_TASKBAR);
627 #else
628                         splash = new wxSplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, nullptr, -1);
629 #endif
630                         wxYield ();
631                 }
632         } catch (boost::filesystem::filesystem_error& e) {
633                 /* Maybe we couldn't find the splash image; never mind */
634         }
635
636         return splash;
637 }
638
639
640 double
641 calculate_mark_interval (double mark_interval)
642 {
643         if (mark_interval > 5) {
644                 mark_interval -= lrint (mark_interval) % 5;
645         }
646         if (mark_interval > 10) {
647                 mark_interval -= lrint (mark_interval) % 10;
648         }
649         if (mark_interval > 60) {
650                 mark_interval -= lrint (mark_interval) % 60;
651         }
652         if (mark_interval > 3600) {
653                 mark_interval -= lrint (mark_interval) % 3600;
654         }
655
656         if (mark_interval < 1) {
657                 mark_interval = 1;
658         }
659
660         return mark_interval;
661 }
662
663
664 /** @return false if the task was cancelled */
665 bool
666 display_progress (wxString title, wxString task)
667 {
668         auto jm = JobManager::instance ();
669
670         wxProgressDialog progress (title, task, 100, 0, wxPD_CAN_ABORT);
671
672         bool ok = true;
673
674         while (jm->work_to_do()) {
675                 dcpomatic_sleep_seconds (1);
676                 if (!progress.Pulse()) {
677                         /* user pressed cancel */
678                         for (auto i: jm->get()) {
679                                 i->cancel();
680                         }
681                         ok = false;
682                         break;
683                 }
684         }
685
686         return ok;
687 }
688
689
690 int
691 get_offsets (vector<Offset>& offsets)
692 {
693         offsets.push_back({_("UTC-11"),  dcp::UTCOffset(-11,   0)});
694         offsets.push_back({_("UTC-10"),  dcp::UTCOffset(-10,   0)});
695         offsets.push_back({_("UTC-9"),   dcp::UTCOffset( -9,   0)});
696         offsets.push_back({_("UTC-8"),   dcp::UTCOffset( -8,   0)});
697         offsets.push_back({_("UTC-7"),   dcp::UTCOffset( -7,   0)});
698         offsets.push_back({_("UTC-6"),   dcp::UTCOffset( -6,   0)});
699         offsets.push_back({_("UTC-5"),   dcp::UTCOffset( -5,   0)});
700         offsets.push_back({_("UTC-4:30"),dcp::UTCOffset( -4, -30)});
701         offsets.push_back({_("UTC-4"),   dcp::UTCOffset( -4,   0)});
702         offsets.push_back({_("UTC-3:30"),dcp::UTCOffset( -3, -30)});
703         offsets.push_back({_("UTC-3"),   dcp::UTCOffset( -3,   0)});
704         offsets.push_back({_("UTC-2"),   dcp::UTCOffset( -2,   0)});
705         offsets.push_back({_("UTC-1"),   dcp::UTCOffset( -1,   0)});
706         int utc = offsets.size();
707         offsets.push_back({_("UTC")  ,   dcp::UTCOffset(  0,   0)});
708         offsets.push_back({_("UTC+1"),   dcp::UTCOffset(  1,   0)});
709         offsets.push_back({_("UTC+2"),   dcp::UTCOffset(  2,   0)});
710         offsets.push_back({_("UTC+3"),   dcp::UTCOffset(  3,   0)});
711         offsets.push_back({_("UTC+4"),   dcp::UTCOffset(  4,   0)});
712         offsets.push_back({_("UTC+5"),   dcp::UTCOffset(  5,   0)});
713         offsets.push_back({_("UTC+5:30"),dcp::UTCOffset(  5,  30)});
714         offsets.push_back({_("UTC+6"),   dcp::UTCOffset(  6,   0)});
715         offsets.push_back({_("UTC+7"),   dcp::UTCOffset(  7,   0)});
716         offsets.push_back({_("UTC+8"),   dcp::UTCOffset(  8,   0)});
717         offsets.push_back({_("UTC+9"),   dcp::UTCOffset(  9,   0)});
718         offsets.push_back({_("UTC+9:30"),dcp::UTCOffset(  9,  30)});
719         offsets.push_back({_("UTC+10"),  dcp::UTCOffset( 10,   0)});
720         offsets.push_back({_("UTC+11"),  dcp::UTCOffset( 11,   0)});
721         offsets.push_back({_("UTC+12"),  dcp::UTCOffset( 12,   0)});
722
723         return utc;
724 }
725
726
727 wxString
728 bitmap_path (string name)
729 {
730         boost::filesystem::path base;
731
732 #ifdef DCPOMATIC_DEBUG
733         /* Hack to allow Linux and OS X to find icons when running from the source tree */
734         char* path = getenv ("DCPOMATIC_GRAPHICS");
735         if (path) {
736                 base = path;
737         } else {
738                 base = resources_path();
739         }
740
741         if (!boost::filesystem::exists(base / name)) {
742                 base = path / boost::filesystem::path("osx/preferences");
743         }
744 #else
745         base = resources_path();
746 #endif
747
748         auto p = base / name;
749         return std_to_wx (p.string());
750 }
751
752
753 wxString
754 icon_path(string name)
755 {
756         return gui_is_dark() ? bitmap_path(String::compose("%1_white.png", name)) : bitmap_path(String::compose("%1_black.png", name));
757 }
758
759
760 wxSize
761 small_button_size (wxWindow* parent, wxString text)
762 {
763         wxClientDC dc (parent);
764         auto size = dc.GetTextExtent (text);
765         size.SetHeight (-1);
766         size.IncBy (32, 0);
767         return size;
768 }
769
770
771 bool
772 gui_is_dark ()
773 {
774 #if defined(DCPOMATIC_OSX) && wxCHECK_VERSION(3, 1, 0)
775         auto appearance = wxSystemSettings::GetAppearance();
776         return appearance.IsDark();
777 #else
778         return false;
779 #endif
780 }
781
782
783 #if wxCHECK_VERSION(3,1,0)
784 double
785 dpi_scale_factor (wxWindow* window)
786 {
787         return window->GetDPIScaleFactor();
788 }
789 #else
790 double
791 dpi_scale_factor (wxWindow*)
792 {
793         return 1;
794 }
795 #endif
796
797
798
799 int
800 search_ctrl_height ()
801 {
802 #ifdef __WXGTK3__
803         return 30;
804 #else
805         return -1;
806 #endif
807 }
808
809
810 void
811 report_config_load_failure(wxWindow* parent, Config::LoadFailure what)
812 {
813         switch (what) {
814         case Config::LoadFailure::CONFIG:
815                 message_dialog(parent, _("The existing configuration failed to load.  Default values will be used instead.  These may take a short time to create."));
816                 break;
817         case Config::LoadFailure::CINEMAS:
818                 message_dialog(
819                         parent,
820                         _(wxString::Format("The cinemas list for creating KDMs (cinemas.xml) failed to load.  Please check the numbered backup files in %s",
821                                            std_to_wx(Config::instance()->cinemas_file().parent_path().string())))
822                         );
823                 break;
824         case Config::LoadFailure::DKDM_RECIPIENTS:
825                 message_dialog(
826                         parent,
827                         _(wxString::Format("The recipients list for creating DKDMs (dkdm_recipients.xml) failed to load.  Please check the numbered backup files in %s",
828                                            std_to_wx(Config::instance()->dkdm_recipients_file().parent_path().string())))
829                         );
830                 break;
831         }
832 }
833