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