C++11 tidying.
[dcpomatic.git] / src / wx / content_widget.h
1 /*
2     Copyright (C) 2013-2016 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 /** @file  src/wx/content_widget.h
22  *  @brief ContentWidget class.
23  */
24
25 #ifndef DCPOMATIC_CONTENT_WIDGET_H
26 #define DCPOMATIC_CONTENT_WIDGET_H
27
28 #include "wx_util.h"
29 #include "lib/content.h"
30 #include <wx/wx.h>
31 #include <wx/gbsizer.h>
32 #include <wx/spinctrl.h>
33 #include <vector>
34
35 /** @class ContentWidget
36  *  @brief A widget which represents some Content state and which can be used
37  *  when multiple pieces of content are selected.
38  *
39  *  @param S Type of ContentPart being manipulated (e.g. VideoContent)
40  *  @param T Type of the widget (e.g. wxSpinCtrl)
41  *  @param U Data type of state as used by the model.
42  *  @param V Data type of state as used by the view.
43  */
44 template <class S, class T, typename U, typename V>
45 class ContentWidget
46 {
47 public:
48         /** @param parent Parent window.
49          *  @param wrapped Control widget that we are wrapping.
50          *  @param property ContentProperty that the widget is handling.
51          *  @param part Part of Content that the property is in (e.g. &Content::video)
52          *  @param model_getter Function on the Content to get the value.
53          *  @param model_setter Function on the Content to set the value.
54          *  @param view_changed Function called when the view has changed; useful for linking controls.
55          *  @param view_to_model Function to convert a view value to a model value.
56          *  @param model_to_view Function to convert a model value to a view value.
57          */
58         ContentWidget (
59                 wxWindow* parent,
60                 T* wrapped,
61                 int property,
62                 std::function<std::shared_ptr<S> (Content*)> part,
63                 std::function<U (S*)> model_getter,
64                 std::function<void (S*, U)> model_setter,
65                 std::function<void ()> view_changed,
66                 std::function<U (V)> view_to_model,
67                 std::function<V (U)> model_to_view
68                 )
69                 : _wrapped (wrapped)
70                 , _sizer (0)
71                 , _button (new wxButton (parent, wxID_ANY, _("Multiple values")))
72                 , _property (property)
73                 , _part (part)
74                 , _model_getter (model_getter)
75                 , _model_setter (model_setter)
76                 , _view_changed (view_changed)
77                 , _view_to_model (view_to_model)
78                 , _model_to_view (model_to_view)
79                 , _ignore_model_changes (false)
80         {
81                 _button->SetToolTip (_("Click the button to set all selected content to the same value."));
82                 _button->Hide ();
83                 _button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentWidget::button_clicked, this));
84         }
85
86         ContentWidget (ContentWidget const&) = delete;
87         ContentWidget& operator= (ContentWidget const&) = delete;
88
89         /** @return the widget that we are wrapping */
90         T* wrapped () const
91         {
92                 return _wrapped;
93         }
94
95         typedef std::vector<std::shared_ptr<Content> > List;
96
97         /** Set the content that this control is working on (i.e. the selected content) */
98         void set_content (List content)
99         {
100                 for (typename std::list<boost::signals2::connection>::iterator i = _connections.begin(); i != _connections.end(); ++i) {
101                         i->disconnect ();
102                 }
103
104                 _connections.clear ();
105
106                 _content = content;
107
108                 _wrapped->Enable (!_content.empty ());
109
110                 update_from_model ();
111
112                 for (typename List::iterator i = _content.begin(); i != _content.end(); ++i) {
113 #if BOOST_VERSION >= 106100
114                         _connections.push_back ((*i)->Change.connect (boost::bind (&ContentWidget::model_changed, this, boost::placeholders::_1, boost::placeholders::_3)));
115 #else
116                         _connections.push_back ((*i)->Change.connect (boost::bind (&ContentWidget::model_changed, this, _1, _3)));
117 #endif
118                 }
119         }
120
121         /** Add this widget to a wxGridBagSizer */
122         void add (wxGridBagSizer* sizer, wxGBPosition position, wxGBSpan span = wxDefaultSpan, int flag = 0)
123         {
124                 _sizer = sizer;
125                 _position = position;
126                 _span = span;
127                 _sizer->Add (_wrapped, _position, _span, flag);
128         }
129
130         /** Update the view from the model */
131         void update_from_model ()
132         {
133                 if (_content.empty ()) {
134                         set_single ();
135                         return;
136                 }
137
138                 typename List::iterator i = _content.begin ();
139                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
140                 while (i != _content.end() && boost::bind (_model_getter, _part(i->get()).get())() == v) {
141                         ++i;
142                 }
143
144                 if (i == _content.end ()) {
145                         set_single ();
146                         checked_set (_wrapped, _model_to_view (v));
147                 } else {
148                         set_multiple ();
149                 }
150         }
151
152         void view_changed ()
153         {
154                 _ignore_model_changes = true;
155                 for (size_t i = 0; i < _content.size(); ++i) {
156                         boost::bind (_model_setter, _part (_content[i].get()).get(), _view_to_model (wx_get (_wrapped))) ();
157                 }
158                 if (_view_changed) {
159                         _view_changed ();
160                 }
161                 _ignore_model_changes = false;
162         }
163
164         void show (bool s)
165         {
166                 _wrapped->Show (s);
167         }
168
169 private:
170
171         void set_single ()
172         {
173                 if (_wrapped->IsShown() || !_sizer) {
174                         return;
175                 }
176
177                 _sizer->Detach (_button);
178                 _button->Hide ();
179                 _sizer->Add (_wrapped, _position, _span);
180                 _wrapped->Show ();
181                 _sizer->Layout ();
182         }
183
184         void set_multiple ()
185         {
186                 if (_button->IsShown() || !_sizer) {
187                         return;
188                 }
189
190                 _wrapped->Hide ();
191                 _sizer->Detach (_wrapped);
192                 _button->Show ();
193                 _sizer->Add (_button, _position, _span);
194                 _sizer->Layout ();
195         }
196
197         void button_clicked ()
198         {
199                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
200                 for (auto const& i: _content) {
201                         boost::bind (_model_setter, _part(i.get()).get(), v)();
202                 }
203         }
204
205         void model_changed (ChangeType type, int property)
206         {
207                 if (type == ChangeType::DONE && property == _property && !_ignore_model_changes) {
208                         update_from_model ();
209                 }
210         }
211
212         T* _wrapped;
213         wxGridBagSizer* _sizer;
214         wxGBPosition _position;
215         wxGBSpan _span;
216         wxButton* _button;
217         List _content;
218         int _property;
219         std::function<std::shared_ptr<S> (Content *)> _part;
220         std::function<U (S*)> _model_getter;
221         std::function<void (S*, U)> _model_setter;
222         std::function<void ()> _view_changed;
223         std::function<U (V)> _view_to_model;
224         std::function<V (U)> _model_to_view;
225         std::list<boost::signals2::connection> _connections;
226         bool _ignore_model_changes;
227 };
228
229 template <typename U, typename V>
230 V caster (U x)
231 {
232         return static_cast<V> (x);
233 }
234
235 template <class S>
236 class ContentSpinCtrl : public ContentWidget<S, wxSpinCtrl, int, int>
237 {
238 public:
239         ContentSpinCtrl (
240                 wxWindow* parent,
241                 wxSpinCtrl* wrapped,
242                 int property,
243                 std::function<std::shared_ptr<S> (Content *)> part,
244                 std::function<int (S*)> getter,
245                 std::function<void (S*, int)> setter,
246                 std::function<void ()> view_changed = std::function<void ()>()
247                 )
248                 : ContentWidget<S, wxSpinCtrl, int, int> (
249                         parent,
250                         wrapped,
251                         property,
252                         part,
253                         getter, setter,
254                         view_changed,
255                         &caster<int, int>,
256                         &caster<int, int>
257                         )
258         {
259                 wrapped->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrl, int, int>::view_changed, this));
260         }
261 };
262
263 template <class S>
264 class ContentSpinCtrlDouble : public ContentWidget<S, wxSpinCtrlDouble, double, double>
265 {
266 public:
267         ContentSpinCtrlDouble (
268                 wxWindow* parent,
269                 wxSpinCtrlDouble* wrapped,
270                 int property,
271                 std::function<std::shared_ptr<S> (Content *)> part,
272                 std::function<double (S*)> getter,
273                 std::function<void (S*, double)> setter,
274                 std::function<void ()> view_changed = std::function<void ()>()
275                 )
276                 : ContentWidget<S, wxSpinCtrlDouble, double, double> (
277                         parent,
278                         wrapped,
279                         property,
280                         part,
281                         getter, setter,
282                         view_changed,
283                         &caster<double, double>,
284                         &caster<double, double>
285                         )
286         {
287                 wrapped->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrlDouble, double, double>::view_changed, this));
288         }
289 };
290
291 template <class S, class U>
292 class ContentChoice : public ContentWidget<S, wxChoice, U, int>
293 {
294 public:
295         ContentChoice (
296                 wxWindow* parent,
297                 wxChoice* wrapped,
298                 int property,
299                 std::function<std::shared_ptr<S> (Content *)> part,
300                 std::function<U (S*)> getter,
301                 std::function<void (S*, U)> setter,
302                 std::function<U (int)> view_to_model,
303                 std::function<int (U)> model_to_view,
304                 std::function<void ()> view_changed = std::function<void()>()
305                 )
306                 : ContentWidget<S, wxChoice, U, int> (
307                         parent,
308                         wrapped,
309                         property,
310                         part,
311                         getter,
312                         setter,
313                         view_changed,
314                         view_to_model,
315                         model_to_view
316                         )
317         {
318                 wrapped->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentWidget<S, wxChoice, U, int>::view_changed, this));
319         }
320
321 };
322
323 #endif