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