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