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