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