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