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