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