Merge branch '1.0' into 1.0-multiple-selection
[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<V (T*)> view_getter
54                 )
55                 : _wrapped (wrapped)
56                 , _sizer (0)
57                 , _button (new wxButton (parent, wxID_ANY, _("Multiple values")))
58                 , _property (property)
59                 , _model_getter (model_getter)
60                 , _model_setter (model_setter)
61                 , _view_getter (view_getter)
62                 , _ignore_model_changes (false)
63         {
64                 _button->SetToolTip (_("Click the button to set all selected content to the same value."));
65                 _button->Hide ();
66                 _button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentWidget::button_clicked, this));
67         }
68
69         T* wrapped () const {
70                 return _wrapped;
71         }
72
73         typedef std::vector<boost::shared_ptr<S> > List;
74
75         void set_content (List content)
76         {
77                 for (typename std::list<boost::signals2::connection>::iterator i = _connections.begin(); i != _connections.end(); ++i) {
78                         i->disconnect ();
79                 }
80
81                 _connections.clear ();
82                 
83                 _content = content;
84
85                 _wrapped->Enable (!_content.empty ());
86
87                 update_from_model ();
88
89                 for (typename List::iterator i = _content.begin(); i != _content.end(); ++i) {
90                         _connections.push_back ((*i)->Changed.connect (boost::bind (&ContentWidget::model_changed, this, _2)));
91                 }
92         }
93
94         void add (wxGridBagSizer* sizer, wxGBPosition position)
95         {
96                 _sizer = sizer;
97                 _position = position;
98                 _sizer->Add (_wrapped, _position);
99         }
100
101         void update_from_model ()
102         {
103                 if (_content.empty ()) {
104                         set_single ();
105                         return;
106                 }
107
108                 typename List::iterator i = _content.begin ();
109                 U const v = boost::bind (_model_getter, _content.front().get())();
110                 while (i != _content.end() && boost::bind (_model_getter, i->get())() == v) {
111                         ++i;
112                 }
113
114                 if (i == _content.end ()) {
115                         set_single ();
116                         checked_set (_wrapped, v);
117                 } else {
118                         set_multiple ();
119                 }
120         }
121
122         void view_changed ()
123         {
124                 for (size_t i = 0; i < _content.size(); ++i) {
125                         /* Only update our view on the last time round this loop */
126                         _ignore_model_changes = i < (_content.size() - 1);
127                         boost::bind (_model_setter, _content[i].get(), static_cast<U> (boost::bind (_view_getter, _wrapped)()))();
128                 }
129         }
130         
131 private:
132         
133         void set_single ()
134         {
135                 if (_wrapped->IsShown ()) {
136                         return;
137                 }
138
139                 _sizer->Detach (_button);
140                 _button->Hide ();
141                 _sizer->Add (_wrapped, _position);
142                 _wrapped->Show ();
143                 _sizer->Layout ();
144         }
145
146         void set_multiple ()
147         {
148                 if (_button->IsShown ()) {
149                         return;
150                 }
151                 
152                 _wrapped->Hide ();
153                 _sizer->Detach (_wrapped);
154                 _button->Show ();
155                 _sizer->Add (_button, _position);
156                 _sizer->Layout ();
157         }
158
159         void button_clicked ()
160         {
161                 U const v = boost::bind (_model_getter, _content.front().get())();
162                 for (typename List::iterator i = _content.begin (); i != _content.end(); ++i) {
163                         boost::bind (_model_setter, i->get(), v) ();
164                 }
165         }
166
167         void model_changed (int property)
168         {
169                 if (property == _property && !_ignore_model_changes) {
170                         update_from_model ();
171                 }
172         }
173         
174         T* _wrapped;
175         wxGridBagSizer* _sizer;
176         wxGBPosition _position;
177         wxButton* _button;
178         List _content;
179         int _property;
180         boost::function<U (S*)> _model_getter;
181         boost::function<void (S*, U)> _model_setter;
182         boost::function<V (T*)> _view_getter;
183         std::list<boost::signals2::connection> _connections;
184         bool _ignore_model_changes;
185 };
186
187 template <class S>
188 class ContentSpinCtrl : public ContentWidget<S, wxSpinCtrl, int, int>
189 {
190 public:
191         ContentSpinCtrl (wxWindow* parent, wxSpinCtrl* wrapped, int property, boost::function<int (S*)> getter, boost::function<void (S*, int)> setter)
192                 : ContentWidget<S, wxSpinCtrl, int, int> (parent, wrapped, property, getter, setter, boost::mem_fn (&wxSpinCtrl::GetValue))
193         {
194                 wrapped->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ContentWidget<S, wxSpinCtrl, int, int>::view_changed, this));
195         }
196
197 };
198
199 template <class S, class U>
200 class ContentChoice : public ContentWidget<S, wxChoice, U, int>
201 {
202 public:
203         ContentChoice (wxWindow* parent, wxChoice* wrapped, int property, boost::function<U (S*)> getter, boost::function<void (S*, U)> setter)
204                 : ContentWidget<S, wxChoice, U, int> (parent, wrapped, property, getter, setter, boost::mem_fn (&wxChoice::GetSelection))
205         {
206                 wrapped->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentWidget<S, wxChoice, U, int>::view_changed, this));
207         }
208
209 };
210
211 #endif