macOS / new boost build fixes.
[dcpomatic.git] / src / wx / language_tag_dialog.cc
1 /*
2     Copyright (C) 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
22 #include "lib/dcpomatic_assert.h"
23 #include "language_tag_dialog.h"
24 #include <dcp/language_tag.h>
25 #include <wx/listctrl.h>
26 #include <wx/srchctrl.h>
27 #include <wx/wx.h>
28 #include <boost/algorithm/string.hpp>
29 #include <boost/bind.hpp>
30 #include <boost/foreach.hpp>
31 #include <boost/optional.hpp>
32 #include <boost/shared_ptr.hpp>
33 #include <boost/signals2.hpp>
34 #include <iostream>
35 #include <iterator>
36 #include <string>
37 #include <vector>
38
39
40 using std::pair;
41 using std::string;
42 using std::vector;
43 using boost::optional;
44 using boost::shared_ptr;
45 using boost::weak_ptr;
46 #if BOOST_VERSION >= 106100
47 using namespace boost::placeholders;
48 #endif
49
50
51 class SubtagListCtrl : public wxListCtrl
52 {
53 public:
54         SubtagListCtrl (wxWindow* parent)
55                 : wxListCtrl (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxLC_VIRTUAL)
56         {
57                 AppendColumn ("", wxLIST_FORMAT_LEFT, 80);
58                 AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
59         }
60
61         void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
62         {
63                 _all_subtags = dcp::LanguageTag::get_all(type);
64                 set_search (search);
65                 if (subtag) {
66                         vector<dcp::LanguageTag::SubtagData>::iterator i = find(_matching_subtags.begin(), _matching_subtags.end(), *subtag);
67                         if (i != _matching_subtags.end()) {
68                                 long item = std::distance(_matching_subtags.begin(), i);
69                                 SetItemState (item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
70                                 EnsureVisible (item);
71                         }
72                 }
73         }
74
75         void set_search (string search)
76         {
77                 if (search == "") {
78                         _matching_subtags = _all_subtags;
79                 } else {
80                         _matching_subtags.clear ();
81
82                         boost::algorithm::to_lower(search);
83                         BOOST_FOREACH (dcp::LanguageTag::SubtagData const& i, _all_subtags) {
84                                 if (
85                                         (boost::algorithm::to_lower_copy(i.subtag).find(search) != string::npos) ||
86                                         (boost::algorithm::to_lower_copy(i.description).find(search) != string::npos)) {
87                                         _matching_subtags.push_back (i);
88                                 }
89                         }
90                 }
91
92                 SetItemCount (_matching_subtags.size());
93                 if (GetItemCount() > 0) {
94                         RefreshItems (0, GetItemCount() - 1);
95                 }
96         }
97
98         optional<dcp::LanguageTag::SubtagData> selected_subtag () const
99         {
100                 long int selected = GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
101                 if (selected == -1) {
102                         return optional<dcp::LanguageTag::SubtagData>();
103                 }
104
105                 DCPOMATIC_ASSERT (static_cast<size_t>(selected) < _matching_subtags.size());
106                 return _matching_subtags[selected];
107         }
108
109 private:
110         wxString OnGetItemText (long item, long column) const
111         {
112                 if (column == 0) {
113                         return _matching_subtags[item].subtag;
114                 } else {
115                         return _matching_subtags[item].description;
116                 }
117         }
118
119         std::vector<dcp::LanguageTag::SubtagData> _all_subtags;
120         std::vector<dcp::LanguageTag::SubtagData> _matching_subtags;
121 };
122
123
124 class LanguageSubtagPanel : public wxPanel
125 {
126 public:
127         LanguageSubtagPanel (wxWindow* parent)
128                 : wxPanel (parent, wxID_ANY)
129         {
130 #ifdef __WXGTK3__
131                 int const height = 30;
132 #else
133                 int const height = -1;
134 #endif
135
136                 _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, height));
137                 _list = new SubtagListCtrl (this);
138
139                 wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
140                 sizer->Add (_search, 0, wxALL, 8);
141                 sizer->Add (_list, 1, wxALL, 8);
142                 SetSizer (sizer);
143
144                 _search->Bind (wxEVT_TEXT, boost::bind(&LanguageSubtagPanel::search_changed, this));
145                 _list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
146                 _list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
147         }
148
149         void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
150         {
151                 _list->set (type, search, subtag);
152                 _search->SetValue (wxString(search));
153         }
154
155         optional<dcp::LanguageTag::RegionSubtag> get () const
156         {
157                 if (!_list->selected_subtag()) {
158                         return optional<dcp::LanguageTag::RegionSubtag>();
159                 }
160
161                 return dcp::LanguageTag::RegionSubtag(_list->selected_subtag()->subtag);
162         }
163
164         boost::signals2::signal<void (optional<dcp::LanguageTag::SubtagData>)> SelectionChanged;
165         boost::signals2::signal<void (string)> SearchChanged;
166
167 private:
168         void search_changed ()
169         {
170                 _list->set_search (_search->GetValue().ToStdString());
171                 SearchChanged (_search->GetValue().ToStdString());
172         }
173
174         void selection_changed ()
175         {
176                 SelectionChanged (_list->selected_subtag());
177         }
178
179         wxSearchCtrl* _search;
180         SubtagListCtrl* _list;
181 };
182
183
184 LanguageTagDialog::LanguageTagDialog (wxWindow* parent, dcp::LanguageTag tag)
185         : wxDialog (parent, wxID_ANY, "Language Tag", wxDefaultPosition, wxSize(-1, 500))
186 {
187         _current_tag_list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER);
188         _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 200);
189         _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
190
191         wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL);
192         _add_script = new wxButton(this, wxID_ANY, "Add script");
193         button_sizer->Add (_add_script, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
194         _add_region = new wxButton(this, wxID_ANY, "Add region");
195         button_sizer->Add (_add_region, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
196         _add_variant = new wxButton(this, wxID_ANY, "Add variant");
197         button_sizer->Add (_add_variant, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
198         _add_external = new wxButton(this, wxID_ANY, "Add external");
199         button_sizer->Add (_add_external, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
200
201         _choose_subtag_panel = new LanguageSubtagPanel (this);
202         _choose_subtag_panel->set (dcp::LanguageTag::LANGUAGE, "");
203
204         wxBoxSizer* ltor_sizer = new wxBoxSizer (wxHORIZONTAL);
205         ltor_sizer->Add (_current_tag_list, 1, wxALL, 8);
206         ltor_sizer->Add (button_sizer, 0, wxALL, 8);
207         ltor_sizer->Add (_choose_subtag_panel, 1, wxALL, 8);
208
209         wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
210         overall_sizer->Add (ltor_sizer, 0);
211
212         wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
213         if (buttons) {
214                 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
215         }
216
217         SetSizerAndFit (overall_sizer);
218
219         bool have_language = false;
220         vector<pair<dcp::LanguageTag::SubtagType, dcp::LanguageTag::SubtagData> > subtags = tag.subtags();
221         for (vector<pair<dcp::LanguageTag::SubtagType, dcp::LanguageTag::SubtagData> >::const_iterator i = subtags.begin(); i != subtags.end(); ++i) {
222                 add_to_current_tag (i->first, i->second);
223                 if (i->first == dcp::LanguageTag::LANGUAGE) {
224                         have_language = true;
225                 }
226         }
227
228         if (!have_language) {
229                 add_to_current_tag (dcp::LanguageTag::LANGUAGE, dcp::LanguageTag::SubtagData("en", "English"));
230         }
231
232         _add_script->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SCRIPT, boost::optional<dcp::LanguageTag::SubtagData>()));
233         _add_region->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::REGION, boost::optional<dcp::LanguageTag::SubtagData>()));
234         _add_variant->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::VARIANT, boost::optional<dcp::LanguageTag::SubtagData>()));
235         _add_external->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::EXTLANG, boost::optional<dcp::LanguageTag::SubtagData>()));
236         _choose_subtag_panel->SelectionChanged.connect(bind(&LanguageTagDialog::chosen_subtag_changed, this, _1));
237         _choose_subtag_panel->SearchChanged.connect(bind(&LanguageTagDialog::search_changed, this, _1));
238         _current_tag_list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&LanguageTagDialog::current_tag_selection_changed, this));
239         _current_tag_list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&LanguageTagDialog::current_tag_selection_changed, this));
240 }
241
242
243 dcp::LanguageTag LanguageTagDialog::get () const
244 {
245         dcp::LanguageTag tag;
246
247         vector<dcp::LanguageTag::VariantSubtag> variants;
248         vector<dcp::LanguageTag::ExtlangSubtag> extlangs;
249
250         BOOST_FOREACH (Subtag i, _current_tag_subtags) {
251                 if (!i.subtag) {
252                         continue;
253                 }
254                 switch (i.type) {
255                         case dcp::LanguageTag::LANGUAGE:
256                                 tag.set_language (i.subtag->subtag);
257                                 break;
258                         case dcp::LanguageTag::SCRIPT:
259                                 tag.set_script (i.subtag->subtag);
260                                 break;
261                         case dcp::LanguageTag::REGION:
262                                 tag.set_region (i.subtag->subtag);
263                                 break;
264                         case dcp::LanguageTag::VARIANT:
265                                 variants.push_back (i.subtag->subtag);
266                                 break;
267                         case dcp::LanguageTag::EXTLANG:
268                                 extlangs.push_back (i.subtag->subtag);
269                                 break;
270                 }
271         }
272
273         tag.set_variants (variants);
274         tag.set_extlangs (extlangs);
275         return tag;
276 }
277
278
279 string LanguageTagDialog::subtag_type_name (dcp::LanguageTag::SubtagType type)
280 {
281         switch (type) {
282                 case dcp::LanguageTag::LANGUAGE:
283                         return "Language";
284                 case dcp::LanguageTag::SCRIPT:
285                         return "Script";
286                 case dcp::LanguageTag::REGION:
287                         return "Region";
288                 case dcp::LanguageTag::VARIANT:
289                         return "Variant";
290                 case dcp::LanguageTag::EXTLANG:
291                         return "External";
292         }
293
294         return "";
295 }
296
297
298 void
299 LanguageTagDialog::search_changed (string search)
300 {
301         long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
302         if (selected >= 0) {
303                 _current_tag_subtags[selected].last_search = search;
304         }
305 }
306
307
308 void
309 LanguageTagDialog::add_to_current_tag (dcp::LanguageTag::SubtagType type, optional<dcp::LanguageTag::SubtagData> subtag)
310 {
311         _current_tag_subtags.push_back (Subtag(type, subtag));
312         wxListItem it;
313         it.SetId (_current_tag_list->GetItemCount());
314         it.SetColumn (0);
315         it.SetText (subtag_type_name(type));
316         _current_tag_list->InsertItem (it);
317         it.SetColumn (1);
318         if (subtag) {
319                 it.SetText (subtag->description);
320         } else {
321                 it.SetText ("Select...");
322         }
323         _current_tag_list->SetItem (it);
324         _current_tag_list->SetItemState (_current_tag_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
325         _choose_subtag_panel->set (type, "");
326         setup_sensitivity ();
327         current_tag_selection_changed ();
328 }
329
330
331 void
332 LanguageTagDialog::current_tag_selection_changed ()
333 {
334         long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
335         if (selected >= 0) {
336                 _choose_subtag_panel->Enable (true);
337                 _choose_subtag_panel->set (_current_tag_subtags[selected].type, _current_tag_subtags[selected].last_search, _current_tag_subtags[selected].subtag);
338         } else {
339                 _choose_subtag_panel->Enable (false);
340         }
341 }
342
343
344 void
345 LanguageTagDialog::chosen_subtag_changed (optional<dcp::LanguageTag::SubtagData> selection)
346 {
347         if (!selection) {
348                 return;
349         }
350
351         long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
352         if (selected >= 0) {
353                 _current_tag_subtags[selected].subtag = *selection;
354                 _current_tag_list->SetItem (selected, 0, subtag_type_name(_current_tag_subtags[selected].type));
355                 _current_tag_list->SetItem (selected, 1, selection->description);
356         }
357 }
358
359 void
360 LanguageTagDialog::setup_sensitivity ()
361 {
362         _add_script->Enable ();
363         _add_region->Enable ();
364         _add_variant->Enable ();
365         _add_external->Enable ();
366         BOOST_FOREACH (Subtag const& i, _current_tag_subtags) {
367                 switch (i.type) {
368                         case dcp::LanguageTag::SCRIPT:
369                                 _add_script->Enable (false);
370                                 break;
371                         case dcp::LanguageTag::REGION:
372                                 _add_region->Enable (false);
373                                 break;
374                         case dcp::LanguageTag::VARIANT:
375                                 _add_variant->Enable (false);
376                                 break;
377                         case dcp::LanguageTag::EXTLANG:
378                                 _add_external->Enable (false);
379                                 break;
380                         default:
381                                 break;
382                 }
383         }
384 }
385
386
387 RegionSubtagDialog::RegionSubtagDialog (wxWindow* parent, dcp::LanguageTag::RegionSubtag region)
388         : wxDialog (parent, wxID_ANY, _("Region"), wxDefaultPosition, wxSize(-1, 500))
389         , _panel (new LanguageSubtagPanel (this))
390 {
391         wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
392         sizer->Add (_panel, 1);
393
394         wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
395         if (buttons) {
396                 sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
397         }
398
399         SetSizer (sizer);
400
401         _panel->set (dcp::LanguageTag::REGION, "", *dcp::LanguageTag::get_subtag_data(region));
402 }
403
404
405 optional<dcp::LanguageTag::RegionSubtag>
406 RegionSubtagDialog::get () const
407 {
408         return _panel->get ();
409 }
410
411