Split audio; builds.
[dcpomatic.git] / src / wx / content_widget.h
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file  src/wx/content_widget.h
21  *  @brief ContentWidget class.
22  */
23
24 #ifndef DCPOMATIC_CONTENT_WIDGET_H
25 #define DCPOMATIC_CONTENT_WIDGET_H
26
27 #include "wx_util.h"
28 #include "lib/content.h"
29 #include <wx/wx.h>
30 #include <wx/gbsizer.h>
31 #include <wx/spinctrl.h>
32 #include <boost/function.hpp>
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 : public boost::noncopyable
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 model_getter Function on the Content to get the value.
52          *  @param model_setter Function on the Content to set the value.
53          */
54         ContentWidget (
55                 wxWindow* parent,
56                 T* wrapped,
57                 int property,
58                 boost::function<boost::shared_ptr<S> (Content*)> part,
59                 boost::function<U (S*)> model_getter,
60                 boost::function<void (S*, U)> model_setter,
61                 boost::function<U (V)> view_to_model,
62                 boost::function<V (U)> model_to_view
63                 )
64                 : _wrapped (wrapped)
65                 , _sizer (0)
66                 , _button (new wxButton (parent, wxID_ANY, _("Multiple values")))
67                 , _property (property)
68                 , _part (part)
69                 , _model_getter (model_getter)
70                 , _model_setter (model_setter)
71                 , _view_to_model (view_to_model)
72                 , _model_to_view (model_to_view)
73                 , _ignore_model_changes (false)
74         {
75                 _button->SetToolTip (_("Click the button to set all selected content to the same value."));
76                 _button->Hide ();
77                 _button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentWidget::button_clicked, this));
78         }
79
80         /** @return the widget that we are wrapping */
81         T* wrapped () const
82         {
83                 return _wrapped;
84         }
85
86         typedef std::vector<boost::shared_ptr<Content> > List;
87
88         /** Set the content that this control is working on (i.e. the selected content) */
89         void set_content (List content)
90         {
91                 for (typename std::list<boost::signals2::connection>::iterator i = _connections.begin(); i != _connections.end(); ++i) {
92                         i->disconnect ();
93                 }
94
95                 _connections.clear ();
96
97                 _content = content;
98
99                 _wrapped->Enable (!_content.empty ());
100
101                 update_from_model ();
102
103                 for (typename List::iterator i = _content.begin(); i != _content.end(); ++i) {
104                         _connections.push_back ((*i)->Changed.connect (boost::bind (&ContentWidget::model_changed, this, _2)));
105                 }
106         }
107
108         /** Add this widget to a wxGridBagSizer */
109         void add (wxGridBagSizer* sizer, wxGBPosition position, wxGBSpan span = wxDefaultSpan)
110         {
111                 _sizer = sizer;
112                 _position = position;
113                 _span = span;
114                 _sizer->Add (_wrapped, _position, _span);
115         }
116
117         /** Update the view from the model */
118         void update_from_model ()
119         {
120                 if (_content.empty ()) {
121                         set_single ();
122                         return;
123                 }
124
125                 typename List::iterator i = _content.begin ();
126                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
127                 while (i != _content.end() && boost::bind (_model_getter, _part(i->get()).get())() == v) {
128                         ++i;
129                 }
130
131                 if (i == _content.end ()) {
132                         set_single ();
133                         checked_set (_wrapped, _model_to_view (v));
134                 } else {
135                         set_multiple ();
136                 }
137         }
138
139         void view_changed ()
140         {
141                 _ignore_model_changes = true;
142                 for (size_t i = 0; i < _content.size(); ++i) {
143                         boost::bind (_model_setter, _part (_content[i].get()).get(), _view_to_model (wx_get (_wrapped))) ();
144                 }
145                 _ignore_model_changes = false;
146         }
147
148 private:
149
150         void set_single ()
151         {
152                 if (_wrapped->IsShown ()) {
153                         return;
154                 }
155
156                 _sizer->Detach (_button);
157                 _button->Hide ();
158                 _sizer->Add (_wrapped, _position, _span);
159                 _wrapped->Show ();
160                 _sizer->Layout ();
161         }
162
163         void set_multiple ()
164         {
165                 if (_button->IsShown ()) {
166                         return;
167                 }
168
169                 _wrapped->Hide ();
170                 _sizer->Detach (_wrapped);
171                 _button->Show ();
172                 _sizer->Add (_button, _position, _span);
173                 _sizer->Layout ();
174         }
175
176         void button_clicked ()
177         {
178                 U const v = boost::bind (_model_getter, _part(_content.front().get()).get())();
179                 for (typename List::iterator i = _content.begin (); i != _content.end(); ++i) {
180                         boost::bind (_model_setter, _part(i->get()).get(), v) ();
181                 }
182         }
183
184         void model_changed (int property)
185         {
186                 if (property == _property && !_ignore_model_changes) {
187                         update_from_model ();
188                 }
189         }
190
191         T* _wrapped;
192         wxGridBagSizer* _sizer;
193         wxGBPosition _position;
194         wxGBSpan _span;
195         wxButton* _button;
196         List _content;
197         int _property;
198         boost::function<boost::shared_ptr<S> (Content *)> _part;
199         boost::function<U (S*)> _model_getter;
200         boost::function<void (S*, U)> _model_setter;
201         boost::function<U (V)> _view_to_model;
202         boost::function<V (U)> _model_to_view;
203         std::list<boost::signals2::connection> _connections;
204         bool _ignore_model_changes;
205 };
206
207 template <typename U, typename V>
208 V caster (U x)
209 {
210         return static_cast<V> (x);
211 }
212
213 template <class S>
214 class ContentSpinCtrl : public ContentWidget<S, wxSpinCtrl, int, int>
215 {
216 public:
217         ContentSpinCtrl (
218                 wxWindow* parent,
219                 wxSpinCtrl* wrapped,
220                 int property,
221                 boost::function<boost::shared_ptr<S> (Content *)> part,
222                 boost::function<int (S*)> getter,
223                 boost::function<void (S*, int)> setter
224                 )
225                 : ContentWidget<S, wxSpinCtrl, int, int> (
226                         parent,
227                         wrapped,
228                         property,
229                         part,
230                         getter, setter,
231                         &caster<int, int>,
232                         &caster<int, int>
233                         )
234         {
235                 wrapped->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrl, int, int>::view_changed, this));
236         }
237 };
238
239 template <class S>
240 class ContentSpinCtrlDouble : public ContentWidget<S, wxSpinCtrlDouble, double, double>
241 {
242 public:
243         ContentSpinCtrlDouble (
244                 wxWindow* parent,
245                 wxSpinCtrlDouble* wrapped,
246                 int property,
247                 boost::function<boost::shared_ptr<S> (Content *)> part,
248                 boost::function<double (S*)> getter,
249                 boost::function<void (S*, double)> setter
250                 )
251                 : ContentWidget<S, wxSpinCtrlDouble, double, double> (
252                         parent,
253                         wrapped,
254                         property,
255                         part,
256                         getter, setter,
257                         &caster<double, double>,
258                         &caster<double, double>
259                         )
260         {
261                 wrapped->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrlDouble, double, double>::view_changed, this));
262         }
263 };
264
265 template <class S, class U>
266 class ContentChoice : public ContentWidget<S, wxChoice, U, int>
267 {
268 public:
269         ContentChoice (
270                 wxWindow* parent,
271                 wxChoice* wrapped,
272                 int property,
273                 boost::function<boost::shared_ptr<S> (Content *)> part,
274                 boost::function<U (S*)> getter,
275                 boost::function<void (S*, U)> setter,
276                 boost::function<U (int)> view_to_model,
277                 boost::function<int (U)> model_to_view
278                 )
279                 : ContentWidget<S, wxChoice, U, int> (
280                         parent,
281                         wrapped,
282                         property,
283                         part,
284                         getter,
285                         setter,
286                         view_to_model,
287                         model_to_view
288                         )
289         {
290                 wrapped->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentWidget<S, wxChoice, U, int>::view_changed, this));
291         }
292
293 };
294
295 #endif