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