Add FilmViewer::time_until_next_frame.
[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 : public boost::noncopyable
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_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                 boost::function<boost::shared_ptr<S> (Content*)> part,
63                 boost::function<U (S*)> model_getter,
64                 boost::function<void (S*, U)> model_setter,
65                 boost::function<U (V)> view_to_model,
66                 boost::function<V (U)> model_to_view
67                 )
68                 : _wrapped (wrapped)
69                 , _sizer (0)
70                 , _button (new wxButton (parent, wxID_ANY, _("Multiple values")))
71                 , _property (property)
72                 , _part (part)
73                 , _model_getter (model_getter)
74                 , _model_setter (model_setter)
75                 , _view_to_model (view_to_model)
76                 , _model_to_view (model_to_view)
77                 , _ignore_model_changes (false)
78         {
79                 _button->SetToolTip (_("Click the button to set all selected content to the same value."));
80                 _button->Hide ();
81                 _button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentWidget::button_clicked, this));
82         }
83
84         /** @return the widget that we are wrapping */
85         T* wrapped () const
86         {
87                 return _wrapped;
88         }
89
90         typedef std::vector<boost::shared_ptr<Content> > List;
91
92         /** Set the content that this control is working on (i.e. the selected content) */
93         void set_content (List content)
94         {
95                 for (typename std::list<boost::signals2::connection>::iterator i = _connections.begin(); i != _connections.end(); ++i) {
96                         i->disconnect ();
97                 }
98
99                 _connections.clear ();
100
101                 _content = content;
102
103                 _wrapped->Enable (!_content.empty ());
104
105                 update_from_model ();
106
107                 for (typename List::iterator i = _content.begin(); i != _content.end(); ++i) {
108                         _connections.push_back ((*i)->Change.connect (boost::bind (&ContentWidget::model_changed, this, _1, _3)));
109                 }
110         }
111
112         /** Add this widget to a wxGridBagSizer */
113         void add (wxGridBagSizer* sizer, wxGBPosition position, wxGBSpan span = wxDefaultSpan)
114         {
115                 _sizer = sizer;
116                 _position = position;
117                 _span = span;
118                 _sizer->Add (_wrapped, _position, _span);
119         }
120
121         /** Update the view from the model */
122         void update_from_model ()
123         {
124                 if (_content.empty ()) {
125                         set_single ();
126                         return;
127                 }
128
129                 typename List::iterator i = _content.begin ();
130                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
131                 while (i != _content.end() && boost::bind (_model_getter, _part(i->get()).get())() == v) {
132                         ++i;
133                 }
134
135                 if (i == _content.end ()) {
136                         set_single ();
137                         checked_set (_wrapped, _model_to_view (v));
138                 } else {
139                         set_multiple ();
140                 }
141         }
142
143         void view_changed ()
144         {
145                 _ignore_model_changes = true;
146                 for (size_t i = 0; i < _content.size(); ++i) {
147                         boost::bind (_model_setter, _part (_content[i].get()).get(), _view_to_model (wx_get (_wrapped))) ();
148                 }
149                 _ignore_model_changes = false;
150         }
151
152         void show (bool s)
153         {
154                 _wrapped->Show (s);
155         }
156
157 private:
158
159         void set_single ()
160         {
161                 if (_wrapped->IsShown() || !_sizer) {
162                         return;
163                 }
164
165                 _sizer->Detach (_button);
166                 _button->Hide ();
167                 _sizer->Add (_wrapped, _position, _span);
168                 _wrapped->Show ();
169                 _sizer->Layout ();
170         }
171
172         void set_multiple ()
173         {
174                 if (_button->IsShown() || !_sizer) {
175                         return;
176                 }
177
178                 _wrapped->Hide ();
179                 _sizer->Detach (_wrapped);
180                 _button->Show ();
181                 _sizer->Add (_button, _position, _span);
182                 _sizer->Layout ();
183         }
184
185         void button_clicked ()
186         {
187                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
188                 for (typename List::iterator i = _content.begin (); i != _content.end(); ++i) {
189                         boost::bind (_model_setter, _part(i->get()).get(), v) ();
190                 }
191         }
192
193         void model_changed (ChangeType type, int property)
194         {
195                 if (type == CHANGE_TYPE_DONE && property == _property && !_ignore_model_changes) {
196                         update_from_model ();
197                 }
198         }
199
200         T* _wrapped;
201         wxGridBagSizer* _sizer;
202         wxGBPosition _position;
203         wxGBSpan _span;
204         wxButton* _button;
205         List _content;
206         int _property;
207         boost::function<boost::shared_ptr<S> (Content *)> _part;
208         boost::function<U (S*)> _model_getter;
209         boost::function<void (S*, U)> _model_setter;
210         boost::function<U (V)> _view_to_model;
211         boost::function<V (U)> _model_to_view;
212         std::list<boost::signals2::connection> _connections;
213         bool _ignore_model_changes;
214 };
215
216 template <typename U, typename V>
217 V caster (U x)
218 {
219         return static_cast<V> (x);
220 }
221
222 template <class S>
223 class ContentSpinCtrl : public ContentWidget<S, wxSpinCtrl, int, int>
224 {
225 public:
226         ContentSpinCtrl (
227                 wxWindow* parent,
228                 wxSpinCtrl* wrapped,
229                 int property,
230                 boost::function<boost::shared_ptr<S> (Content *)> part,
231                 boost::function<int (S*)> getter,
232                 boost::function<void (S*, int)> setter
233                 )
234                 : ContentWidget<S, wxSpinCtrl, int, int> (
235                         parent,
236                         wrapped,
237                         property,
238                         part,
239                         getter, setter,
240                         &caster<int, int>,
241                         &caster<int, int>
242                         )
243         {
244                 wrapped->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrl, int, int>::view_changed, this));
245         }
246 };
247
248 template <class S>
249 class ContentSpinCtrlDouble : public ContentWidget<S, wxSpinCtrlDouble, double, double>
250 {
251 public:
252         ContentSpinCtrlDouble (
253                 wxWindow* parent,
254                 wxSpinCtrlDouble* wrapped,
255                 int property,
256                 boost::function<boost::shared_ptr<S> (Content *)> part,
257                 boost::function<double (S*)> getter,
258                 boost::function<void (S*, double)> setter
259                 )
260                 : ContentWidget<S, wxSpinCtrlDouble, double, double> (
261                         parent,
262                         wrapped,
263                         property,
264                         part,
265                         getter, setter,
266                         &caster<double, double>,
267                         &caster<double, double>
268                         )
269         {
270                 wrapped->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrlDouble, double, double>::view_changed, this));
271         }
272 };
273
274 template <class S, class U>
275 class ContentChoice : public ContentWidget<S, wxChoice, U, int>
276 {
277 public:
278         ContentChoice (
279                 wxWindow* parent,
280                 wxChoice* wrapped,
281                 int property,
282                 boost::function<boost::shared_ptr<S> (Content *)> part,
283                 boost::function<U (S*)> getter,
284                 boost::function<void (S*, U)> setter,
285                 boost::function<U (int)> view_to_model,
286                 boost::function<int (U)> model_to_view
287                 )
288                 : ContentWidget<S, wxChoice, U, int> (
289                         parent,
290                         wrapped,
291                         property,
292                         part,
293                         getter,
294                         setter,
295                         view_to_model,
296                         model_to_view
297                         )
298         {
299                 wrapped->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentWidget<S, wxChoice, U, int>::view_changed, this));
300         }
301
302 };
303
304 #endif