2 Copyright (C) 2020-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 #include "full_language_tag_dialog.h"
23 #include "lib/dcpomatic_assert.h"
24 #include <dcp/language_tag.h>
25 #include <dcp/warnings.h>
26 LIBDCP_DISABLE_WARNINGS
27 #include <wx/listctrl.h>
28 #include <wx/srchctrl.h>
30 LIBDCP_ENABLE_WARNINGS
31 #include <boost/algorithm/string.hpp>
32 #include <boost/bind/bind.hpp>
33 #include <boost/optional.hpp>
34 #include <boost/signals2.hpp>
42 using std::shared_ptr;
46 using boost::optional;
47 #if BOOST_VERSION >= 106100
48 using namespace boost::placeholders;
52 class SubtagListCtrl : public wxListCtrl
55 SubtagListCtrl (wxWindow* parent)
56 : wxListCtrl (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxLC_VIRTUAL)
58 AppendColumn ("", wxLIST_FORMAT_LEFT, 80);
59 AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
62 void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
64 _all_subtags = dcp::LanguageTag::get_all(type);
67 auto i = find(_matching_subtags.begin(), _matching_subtags.end(), *subtag);
68 if (i != _matching_subtags.end()) {
69 auto item = std::distance(_matching_subtags.begin(), i);
70 SetItemState (item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
74 if (GetItemCount() > 0) {
75 /* The new list sometimes isn't visible without this */
81 void set_search (string search)
84 _matching_subtags = _all_subtags;
86 _matching_subtags.clear ();
88 boost::algorithm::to_lower(search);
89 for (auto const& i: _all_subtags) {
91 (boost::algorithm::to_lower_copy(i.subtag).find(search) != string::npos) ||
92 (boost::algorithm::to_lower_copy(i.description).find(search) != string::npos)) {
93 _matching_subtags.push_back (i);
98 SetItemCount (_matching_subtags.size());
99 if (GetItemCount() > 0) {
100 RefreshItems (0, GetItemCount() - 1);
104 optional<dcp::LanguageTag::SubtagData> selected_subtag () const
106 auto selected = GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
107 if (selected == -1) {
111 DCPOMATIC_ASSERT (static_cast<size_t>(selected) < _matching_subtags.size());
112 return _matching_subtags[selected];
116 wxString OnGetItemText (long item, long column) const override
119 return _matching_subtags[item].subtag;
121 return _matching_subtags[item].description;
125 std::vector<dcp::LanguageTag::SubtagData> _all_subtags;
126 std::vector<dcp::LanguageTag::SubtagData> _matching_subtags;
130 class LanguageSubtagPanel : public wxPanel
133 LanguageSubtagPanel (wxWindow* parent)
134 : wxPanel (parent, wxID_ANY)
137 int const height = 30;
139 int const height = -1;
142 _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, height));
143 _list = new SubtagListCtrl (this);
145 auto sizer = new wxBoxSizer (wxVERTICAL);
146 sizer->Add (_search, 0, wxALL, 8);
147 sizer->Add (_list, 1, wxALL, 8);
150 _search->Bind (wxEVT_TEXT, boost::bind(&LanguageSubtagPanel::search_changed, this));
151 _list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
152 _list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
155 void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
157 _list->set (type, search, subtag);
158 _search->SetValue (wxString(search));
161 optional<dcp::LanguageTag::RegionSubtag> get () const
163 if (!_list->selected_subtag()) {
167 return dcp::LanguageTag::RegionSubtag(_list->selected_subtag()->subtag);
170 boost::signals2::signal<void (optional<dcp::LanguageTag::SubtagData>)> SelectionChanged;
171 boost::signals2::signal<void (string)> SearchChanged;
174 void search_changed ()
176 auto search = _search->GetValue();
177 _list->set_search (search.ToStdString());
178 if (search.Length() > 0 && _list->GetItemCount() > 0) {
179 _list->EnsureVisible (0);
181 SearchChanged (_search->GetValue().ToStdString());
184 void selection_changed ()
186 SelectionChanged (_list->selected_subtag());
189 wxSearchCtrl* _search;
190 SubtagListCtrl* _list;
194 FullLanguageTagDialog::FullLanguageTagDialog (wxWindow* parent, dcp::LanguageTag tag)
195 : wxDialog (parent, wxID_ANY, _("Language Tag"), wxDefaultPosition, wxSize(-1, 500))
197 _current_tag_list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER);
198 _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 200);
199 _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
201 auto button_sizer = new wxBoxSizer (wxVERTICAL);
202 _add_script = new wxButton(this, wxID_ANY, "Add script");
203 button_sizer->Add (_add_script, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
204 _add_region = new wxButton(this, wxID_ANY, "Add region");
205 button_sizer->Add (_add_region, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
206 _add_variant = new wxButton(this, wxID_ANY, "Add variant");
207 button_sizer->Add (_add_variant, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
208 _add_external = new wxButton(this, wxID_ANY, "Add external");
209 button_sizer->Add (_add_external, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
210 _remove = new wxButton(this, wxID_ANY, "Remove");
211 button_sizer->Add (_remove, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
213 _choose_subtag_panel = new LanguageSubtagPanel (this);
214 _choose_subtag_panel->set (dcp::LanguageTag::SubtagType::LANGUAGE, "");
216 auto ltor_sizer = new wxBoxSizer (wxHORIZONTAL);
217 ltor_sizer->Add (_current_tag_list, 1, wxALL, 8);
218 ltor_sizer->Add (button_sizer, 0, wxALL, 8);
219 ltor_sizer->Add (_choose_subtag_panel, 1, wxALL, 8);
221 auto overall_sizer = new wxBoxSizer (wxVERTICAL);
222 overall_sizer->Add (ltor_sizer, 0);
224 auto buttons = CreateSeparatedButtonSizer (wxOK);
226 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
229 SetSizerAndFit (overall_sizer);
233 _add_script->Bind (wxEVT_BUTTON, boost::bind(&FullLanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SubtagType::SCRIPT, boost::optional<dcp::LanguageTag::SubtagData>()));
234 _add_region->Bind (wxEVT_BUTTON, boost::bind(&FullLanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SubtagType::REGION, boost::optional<dcp::LanguageTag::SubtagData>()));
235 _add_variant->Bind (wxEVT_BUTTON, boost::bind(&FullLanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SubtagType::VARIANT, boost::optional<dcp::LanguageTag::SubtagData>()));
236 _add_external->Bind (wxEVT_BUTTON, boost::bind(&FullLanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SubtagType::EXTLANG, boost::optional<dcp::LanguageTag::SubtagData>()));
237 _remove->Bind (wxEVT_BUTTON, boost::bind(&FullLanguageTagDialog::remove_from_current_tag, this));
238 _choose_subtag_panel->SelectionChanged.connect(bind(&FullLanguageTagDialog::chosen_subtag_changed, this, _1));
239 _choose_subtag_panel->SearchChanged.connect(bind(&FullLanguageTagDialog::search_changed, this, _1));
240 _current_tag_list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&FullLanguageTagDialog::current_tag_selection_changed, this));
241 _current_tag_list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&FullLanguageTagDialog::current_tag_selection_changed, this));
246 FullLanguageTagDialog::remove_from_current_tag ()
248 auto selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
253 _current_tag_subtags.erase (_current_tag_subtags.begin() + selected);
254 _current_tag_list->DeleteItem (selected);
256 _current_tag_list->SetItemState (min(selected, _current_tag_list->GetItemCount() - 1L), wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
258 setup_sensitivity ();
259 current_tag_selection_changed ();
263 dcp::LanguageTag FullLanguageTagDialog::get () const
265 dcp::LanguageTag tag;
267 vector<dcp::LanguageTag::VariantSubtag> variants;
268 vector<dcp::LanguageTag::ExtlangSubtag> extlangs;
270 for (auto i: _current_tag_subtags) {
275 case dcp::LanguageTag::SubtagType::LANGUAGE:
276 tag.set_language (i.subtag->subtag);
278 case dcp::LanguageTag::SubtagType::SCRIPT:
279 tag.set_script (i.subtag->subtag);
281 case dcp::LanguageTag::SubtagType::REGION:
282 tag.set_region (i.subtag->subtag);
284 case dcp::LanguageTag::SubtagType::VARIANT:
285 variants.push_back (i.subtag->subtag);
287 case dcp::LanguageTag::SubtagType::EXTLANG:
288 extlangs.push_back (i.subtag->subtag);
293 tag.set_variants (variants);
294 tag.set_extlangs (extlangs);
300 FullLanguageTagDialog::set (dcp::LanguageTag tag)
302 _current_tag_subtags.clear ();
303 _current_tag_list->DeleteAllItems ();
305 bool have_language = false;
306 for (auto const& i: tag.subtags()) {
307 add_to_current_tag (i.first, i.second);
308 if (i.first == dcp::LanguageTag::SubtagType::LANGUAGE) {
309 have_language = true;
313 if (!have_language) {
314 add_to_current_tag (dcp::LanguageTag::SubtagType::LANGUAGE, dcp::LanguageTag::SubtagData("en", "English"));
319 string FullLanguageTagDialog::subtag_type_name (dcp::LanguageTag::SubtagType type)
322 case dcp::LanguageTag::SubtagType::LANGUAGE:
324 case dcp::LanguageTag::SubtagType::SCRIPT:
326 case dcp::LanguageTag::SubtagType::REGION:
328 case dcp::LanguageTag::SubtagType::VARIANT:
330 case dcp::LanguageTag::SubtagType::EXTLANG:
339 FullLanguageTagDialog::search_changed (string search)
341 long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
343 _current_tag_subtags[selected].last_search = search;
349 FullLanguageTagDialog::add_to_current_tag (dcp::LanguageTag::SubtagType type, optional<dcp::LanguageTag::SubtagData> subtag)
351 _current_tag_subtags.push_back (Subtag(type, subtag));
353 it.SetId (_current_tag_list->GetItemCount());
355 it.SetText (subtag_type_name(type));
356 _current_tag_list->InsertItem (it);
359 it.SetText (subtag->description);
361 it.SetText ("Select...");
363 _current_tag_list->SetItem (it);
364 _current_tag_list->SetItemState (_current_tag_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
365 _choose_subtag_panel->set (type, "");
366 setup_sensitivity ();
367 current_tag_selection_changed ();
372 FullLanguageTagDialog::current_tag_selection_changed ()
374 auto selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
376 _choose_subtag_panel->Enable (true);
377 _choose_subtag_panel->set (_current_tag_subtags[selected].type, _current_tag_subtags[selected].last_search, _current_tag_subtags[selected].subtag);
379 _choose_subtag_panel->Enable (false);
385 FullLanguageTagDialog::chosen_subtag_changed (optional<dcp::LanguageTag::SubtagData> selection)
391 auto selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
393 _current_tag_subtags[selected].subtag = *selection;
394 _current_tag_list->SetItem (selected, 0, subtag_type_name(_current_tag_subtags[selected].type));
395 _current_tag_list->SetItem (selected, 1, selection->description);
398 setup_sensitivity ();
402 FullLanguageTagDialog::setup_sensitivity ()
404 _add_script->Enable ();
405 _add_region->Enable ();
406 _add_variant->Enable ();
407 _add_external->Enable ();
408 for (auto const& i: _current_tag_subtags) {
410 case dcp::LanguageTag::SubtagType::SCRIPT:
411 _add_script->Enable (false);
413 case dcp::LanguageTag::SubtagType::REGION:
414 _add_region->Enable (false);
416 case dcp::LanguageTag::SubtagType::VARIANT:
417 _add_variant->Enable (false);
419 case dcp::LanguageTag::SubtagType::EXTLANG:
420 _add_external->Enable (false);
426 auto selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
427 _remove->Enable (selected > 0);
431 RegionSubtagDialog::RegionSubtagDialog (wxWindow* parent, dcp::LanguageTag::RegionSubtag region)
432 : wxDialog (parent, wxID_ANY, _("Region"), wxDefaultPosition, wxSize(-1, 500))
433 , _panel (new LanguageSubtagPanel (this))
435 auto sizer = new wxBoxSizer (wxVERTICAL);
436 sizer->Add (_panel, 1);
438 auto buttons = CreateSeparatedButtonSizer (wxOK);
440 sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
445 _panel->set (dcp::LanguageTag::SubtagType::REGION, "", *dcp::LanguageTag::get_subtag_data(region));
449 optional<dcp::LanguageTag::RegionSubtag>
450 RegionSubtagDialog::get () const
452 return _panel->get ();