9c75f0d4cacf8d53bcdd6c408f1583332391891b
[dcpomatic.git] / src / wx / rating_dialog.cc
1 /*
2     Copyright (C) 2019-2022 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 "dcpomatic_spin_ctrl.h"
23 #include "rating_dialog.h"
24 #include "wx_util.h"
25 #include <dcp/warnings.h>
26 #include <unicode/unistr.h>
27 LIBDCP_DISABLE_WARNINGS
28 #include <wx/listctrl.h>
29 #include <wx/notebook.h>
30 #include <wx/srchctrl.h>
31 LIBDCP_ENABLE_WARNINGS
32
33
34 using std::string;
35 using std::vector;
36 using boost::optional;
37 #if BOOST_VERSION >= 106100
38 using namespace boost::placeholders;
39 #endif
40
41
42 RatingDialog::RatingDialog (wxWindow* parent)
43         : wxDialog (parent, wxID_ANY, _("Rating"))
44 {
45         _notebook = new wxNotebook (this, wxID_ANY);
46
47         _standard_page = new StandardRatingDialogPage (_notebook);
48         _custom_page = new CustomRatingDialogPage (_notebook);
49
50         _notebook->AddPage (_standard_page, _("Standard"));
51         _notebook->AddPage (_custom_page, _("Custom"));
52
53         _active_page = _standard_page;
54
55         auto overall_sizer = new wxBoxSizer (wxVERTICAL);
56         overall_sizer->Add (_notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
57
58         auto buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
59         if (buttons) {
60                 overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
61         }
62
63         SetSizerAndFit (overall_sizer);
64
65         _notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, boost::bind(&RatingDialog::page_changed, this));
66
67         _standard_page->Changed.connect(boost::bind(&RatingDialog::setup_sensitivity, this, _1));
68         _custom_page->Changed.connect(boost::bind(&RatingDialog::setup_sensitivity, this, _1));
69 }
70
71
72 void
73 RatingDialog::page_changed ()
74 {
75         if (_notebook->GetSelection() == 0) {
76                 _active_page = _standard_page;
77         } else {
78                 _active_page = _custom_page;
79         }
80 }
81
82
83 void
84 RatingDialog::set (dcp::Rating rating)
85 {
86         if (_standard_page->set(rating)) {
87                 _notebook->SetSelection(0);
88         } else {
89                 _custom_page->set(rating);
90                 _notebook->SetSelection(1);
91         }
92 }
93
94
95 dcp::Rating
96 RatingDialog::get () const
97 {
98         return _active_page->get();
99 }
100
101
102 void
103 RatingDialog::setup_sensitivity (bool ok_valid)
104 {
105         auto ok = dynamic_cast<wxButton *>(FindWindowById(wxID_OK, this));
106         if (ok) {
107                 ok->Enable (ok_valid);
108         }
109 }
110
111
112 RatingDialogPage::RatingDialogPage (wxNotebook* notebook)
113         : wxPanel (notebook, wxID_ANY)
114 {
115
116 }
117
118
119 StandardRatingDialogPage::StandardRatingDialogPage (wxNotebook* notebook)
120         : RatingDialogPage (notebook)
121 {
122         _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, search_ctrl_height()));
123 #ifndef __WXGTK3__
124         /* The cancel button seems to be strangely broken in GTK3; clicking on it twice sometimes works */
125         _search->ShowCancelButton (true);
126 #endif
127
128         _found_systems_view = new wxListView (this, wxID_ANY, wxDefaultPosition, wxSize(600, 400), wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER);
129         _found_systems_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 150);
130         _found_systems_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 50);
131         _found_systems_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 400);
132         _rating = new wxChoice (this, wxID_ANY);
133
134         auto sizer = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
135
136         add_label_to_sizer (sizer, this, _("Agency"), true, 0, wxALIGN_CENTER_VERTICAL);
137         sizer->Add (_search, 0, wxEXPAND, DCPOMATIC_SIZER_Y_GAP);
138
139         sizer->AddSpacer (0);
140         sizer->Add (_found_systems_view, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_SIZER_Y_GAP);
141
142         add_label_to_sizer (sizer, this, _("Rating"), true, 0, wxALIGN_CENTER_VERTICAL);
143         sizer->Add (_rating, 1, wxEXPAND);
144
145         auto pad_sizer = new wxBoxSizer (wxVERTICAL);
146         pad_sizer->Add (sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
147
148         SetSizerAndFit (pad_sizer);
149
150         _search->Bind (wxEVT_TEXT, boost::bind(&StandardRatingDialogPage::search_changed, this));
151         _found_systems_view->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&StandardRatingDialogPage::found_systems_view_selection_changed, this));
152         _found_systems_view->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&StandardRatingDialogPage::found_systems_view_selection_changed, this));
153
154         search_changed ();
155 }
156
157
158 /** The user clicked something different in the list of systems found by the search */
159 void
160 StandardRatingDialogPage::found_systems_view_selection_changed ()
161 {
162         auto selected_index = _found_systems_view->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
163         if (selected_index < 0 || selected_index >= static_cast<int>(_found_systems.size())) {
164                 _selected_system = boost::none;
165         } else {
166                 _selected_system = _found_systems[selected_index];
167         }
168
169         /* Update the ratings dropdown */
170         wxArrayString items;
171         if (_selected_system) {
172                 for (auto rating: _selected_system->ratings) {
173                         items.Add(std_to_wx(rating.label));
174                 }
175         }
176
177         _rating->Set(items);
178
179         if (!items.empty()) {
180                 _rating->SetSelection(0);
181         }
182
183         Changed (static_cast<bool>(_selected_system));
184 }
185
186
187 void
188 StandardRatingDialogPage::search_changed ()
189 {
190         _found_systems_view->DeleteAllItems();
191         _found_systems.clear();
192
193         icu::UnicodeString term(wx_to_std(_search->GetValue()).c_str(), "UTF-8");
194         term = term.toLower();
195
196         int N = 0;
197         for (auto const& system: dcp::rating_systems()) {
198                 icu::UnicodeString name(system.name.c_str(), "UTF-8");
199                 name = name.toLower();
200                 icu::UnicodeString country_and_region_names(system.country_and_region_names.c_str(), "UTF-8");
201                 country_and_region_names = country_and_region_names.toLower();
202                 icu::UnicodeString country_code(system.country_code.c_str(), "UTF-8");
203                 country_code = country_code.toLower();
204                 if (term.isEmpty() || name.indexOf(term) != -1 || country_and_region_names.indexOf(term) != -1 || country_code.indexOf(term) != -1) {
205                         wxListItem item;
206                         item.SetId(N);
207                         _found_systems_view->InsertItem(item);
208                         _found_systems_view->SetItem(N, 0, std_to_wx(system.name));
209                         _found_systems_view->SetItem(N, 1, std_to_wx(system.country_code));
210                         _found_systems_view->SetItem(N, 2, std_to_wx(system.country_and_region_names));
211                         _found_systems.push_back(system);
212                         ++N;
213                 }
214         }
215
216         update_found_system_selection ();
217 }
218
219
220 /** Reflect _selected_system in the current _found_systems_view */
221 void
222 StandardRatingDialogPage::update_found_system_selection ()
223 {
224         if (!_selected_system) {
225                 for (auto i = 0; i < _found_systems_view->GetItemCount(); ++i) {
226                         _found_systems_view->Select(i, false);
227                 }
228                 return;
229         }
230
231         int index = 0;
232         for (auto const& system: _found_systems) {
233                 bool const selected = system.agency == _selected_system->agency;
234                 _found_systems_view->Select(index, selected);
235                 if (selected) {
236                         _found_systems_view->EnsureVisible(index);
237                 }
238                 ++index;
239         }
240 }
241
242
243 bool
244 StandardRatingDialogPage::set (dcp::Rating rating)
245 {
246         _selected_system = boost::none;
247         for (auto const& system: dcp::rating_systems()) {
248                 if (system.agency == rating.agency) {
249                         _selected_system = system;
250                         break;
251                 }
252         }
253
254         if (!_selected_system) {
255                 return false;
256         }
257
258         update_found_system_selection ();
259
260         int rating_index = 0;
261         for (auto const& possible_rating: _selected_system->ratings) {
262                 if (possible_rating.label == rating.label) {
263                         _rating->SetSelection (rating_index);
264                         return true;
265                 }
266                 ++rating_index;
267         }
268
269         return false;
270 }
271
272
273 dcp::Rating
274 StandardRatingDialogPage::get () const
275 {
276         DCPOMATIC_ASSERT (_selected_system);
277         auto selected_rating = _rating->GetSelection();
278         DCPOMATIC_ASSERT (selected_rating >= 0);
279         DCPOMATIC_ASSERT (selected_rating < static_cast<int>(_selected_system->ratings.size()));
280         return dcp::Rating(_selected_system->agency, _selected_system->ratings[selected_rating].label);
281 }
282
283
284 CustomRatingDialogPage::CustomRatingDialogPage (wxNotebook* notebook)
285         : RatingDialogPage (notebook)
286 {
287         auto sizer = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
288
289         _agency = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1));
290         _rating = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1));
291
292         add_label_to_sizer (sizer, this, _("Agency"), true, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL);
293         sizer->Add (_agency, 1, wxEXPAND);
294         add_label_to_sizer (sizer, this, _("Rating"), true, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL);
295         sizer->Add (_rating, 1, wxEXPAND);
296
297         auto pad_sizer = new wxBoxSizer (wxVERTICAL);
298         pad_sizer->Add (sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
299
300         SetSizerAndFit (pad_sizer);
301
302         _agency->Bind(wxEVT_TEXT, boost::bind(&CustomRatingDialogPage::changed, this));
303         _rating->Bind(wxEVT_TEXT, boost::bind(&CustomRatingDialogPage::changed, this));
304 }
305
306
307 void
308 CustomRatingDialogPage::changed ()
309 {
310         Changed (!_agency->IsEmpty() && !_rating->IsEmpty());
311 }
312
313
314 dcp::Rating
315 CustomRatingDialogPage::get () const
316 {
317         return dcp::Rating(wx_to_std(_agency->GetValue()), wx_to_std(_rating->GetValue()));
318 }
319
320
321 bool
322 CustomRatingDialogPage::set (dcp::Rating rating)
323 {
324         _agency->SetValue(std_to_wx(rating.agency));
325         _rating->SetValue(std_to_wx(rating.label));
326         return true;
327 }
328