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