Tidying.
[dcpomatic.git] / src / wx / editable_list.h
1 /*
2     Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #ifndef DCPOMATIC_EDITABLE_LIST_H
23 #define DCPOMATIC_EDITABLE_LIST_H
24
25
26 #include "dcpomatic_button.h"
27 #include "wx_util.h"
28 #include <wx/listctrl.h>
29 #include <wx/wx.h>
30 #include <vector>
31
32
33 class EditableListColumn
34 {
35 public:
36         EditableListColumn (wxString name_)
37                 : name (name_)
38                 , growable (false)
39         {}
40
41         EditableListColumn (wxString name_, boost::optional<int> width_, bool growable_)
42                 : name (name_)
43                 , width (width_)
44                 , growable (growable_)
45         {}
46
47         wxString name;
48         boost::optional<int> width;
49         bool growable;
50 };
51
52 /** @param T type of things being edited.
53  *  @param S dialog to edit a thing.
54  *  @param get Function to get a std::vector of the things being edited.
55  *  @param set Function set the things from a a std::vector.
56  *  @param column Function to get the display string for a given column in a given item.
57  */
58 template<class T, class S>
59 class EditableList : public wxPanel
60 {
61 public:
62         EditableList (
63                 wxWindow* parent,
64                 std::vector<EditableListColumn> columns,
65                 std::function<std::vector<T> ()> get,
66                 std::function<void (std::vector<T>)> set,
67                 std::function<std::string (T, int)> column,
68                 bool can_edit = true,
69                 bool title = true
70                 )
71                 : wxPanel (parent)
72                 , _get (get)
73                 , _set (set)
74                 , _columns (columns)
75                 , _column (column)
76                 , _edit (0)
77                 , _default_width (200)
78         {
79                 _sizer = new wxBoxSizer (wxHORIZONTAL);
80                 SetSizer (_sizer);
81
82                 long style = wxLC_REPORT | wxLC_SINGLE_SEL;
83                 if (!title) {
84                         style |= wxLC_NO_HEADER;
85                 }
86
87                 int total_width = 0;
88                 for (auto i: _columns) {
89                         total_width += i.width.get_value_or (_default_width);
90                 }
91
92 #ifdef __WXGTK3__
93                 /* With the GTK3 backend wxListCtrls are hard to pick out from the background of the
94                  * window, so put a border in to help.
95                  */
96                 wxPanel* border = new wxPanel (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_THEME);
97                 _list = new wxListCtrl (border, wxID_ANY, wxDefaultPosition, wxSize(total_width, 100), style);
98                 wxBoxSizer* border_sizer = new wxBoxSizer (wxHORIZONTAL);
99                 border_sizer->Add (_list, 1, wxALL | wxEXPAND, 2);
100                 border->SetSizer (border_sizer);
101 #else
102                 _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize(total_width, 100), style);
103 #endif
104
105                 int j = 0;
106                 for (auto i: _columns) {
107                         wxListItem ip;
108                         ip.SetId (j);
109                         ip.SetText (i.name);
110                         _list->InsertColumn (j, ip);
111                         ++j;
112                 }
113
114 #ifdef __WXGTK3__
115                 _sizer->Add (border, 1, wxEXPAND);
116 #else
117                 _sizer->Add (_list, 1, wxEXPAND);
118 #endif
119
120                 {
121                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
122                         _add = new Button (this, _("Add..."));
123                         s->Add (_add, 1, wxEXPAND | wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
124                         if (can_edit) {
125                                 _edit = new Button (this, _("Edit..."));
126                                 s->Add (_edit, 1, wxEXPAND | wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
127                         }
128                         _remove = new Button (this, _("Remove"));
129                         s->Add (_remove, 1, wxEXPAND | wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
130                         _sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
131                 }
132
133                 _add->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::add_clicked, this));
134                 if (_edit) {
135                         _edit->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::edit_clicked, this));
136                 }
137                 _remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::remove_clicked, this));
138
139                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&EditableList::selection_changed, this));
140                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&EditableList::selection_changed, this));
141 #if BOOST_VERSION >= 106100
142                 _list->Bind (wxEVT_SIZE, boost::bind (&EditableList::resized, this, boost::placeholders::_1));
143 #else
144                 _list->Bind (wxEVT_SIZE, boost::bind (&EditableList::resized, this, _1));
145 #endif
146
147                 refresh ();
148                 selection_changed ();
149         }
150
151         void refresh ()
152         {
153                 _list->DeleteAllItems ();
154
155                 std::vector<T> current = _get ();
156                 for (typename std::vector<T>::iterator i = current.begin (); i != current.end(); ++i) {
157                         add_to_control (*i);
158                 }
159         }
160
161         boost::optional<T> selection () const
162         {
163                 int item = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
164                 if (item == -1) {
165                         return boost::optional<T> ();
166                 }
167
168                 std::vector<T> all = _get ();
169                 DCPOMATIC_ASSERT (item >= 0 && item < int (all.size ()));
170                 return all[item];
171         }
172
173         void layout ()
174         {
175                 _sizer->Layout ();
176         }
177
178         boost::signals2::signal<void ()> SelectionChanged;
179
180 private:
181
182         void add_to_control (T item)
183         {
184                 wxListItem list_item;
185                 int const n = _list->GetItemCount ();
186                 list_item.SetId (n);
187                 _list->InsertItem (list_item);
188
189                 for (size_t i = 0; i < _columns.size(); ++i) {
190                         _list->SetItem (n, i, std_to_wx (_column (item, i)));
191                 }
192         }
193
194         void selection_changed ()
195         {
196                 int const i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
197                 if (_edit) {
198                         _edit->Enable (i >= 0);
199                 }
200                 _remove->Enable (i >= 0);
201
202                 SelectionChanged ();
203         }
204
205         void add_clicked ()
206         {
207                 S* dialog = new S (this);
208
209                 if (dialog->ShowModal() == wxID_OK) {
210                         boost::optional<T> const v = dialog->get ();
211                         if (v) {
212                                 add_to_control (v.get ());
213                                 std::vector<T> all = _get ();
214                                 all.push_back (v.get ());
215                                 _set (all);
216                         }
217                 }
218
219                 dialog->Destroy ();
220         }
221
222         void edit_clicked ()
223         {
224                 int item = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
225                 if (item == -1) {
226                         return;
227                 }
228
229                 std::vector<T> all = _get ();
230                 DCPOMATIC_ASSERT (item >= 0 && item < int (all.size ()));
231
232                 S* dialog = new S (this);
233                 dialog->set (all[item]);
234                 if (dialog->ShowModal() == wxID_OK) {
235                         boost::optional<T> const v = dialog->get ();
236                         if (!v) {
237                                 return;
238                         }
239
240                         all[item] = v.get ();
241                 }
242                 dialog->Destroy ();
243
244                 for (size_t i = 0; i < _columns.size(); ++i) {
245                         _list->SetItem (item, i, std_to_wx (_column (all[item], i)));
246                 }
247
248                 _set (all);
249         }
250
251         void remove_clicked ()
252         {
253                 int i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
254                 if (i == -1) {
255                         return;
256                 }
257
258                 _list->DeleteItem (i);
259                 std::vector<T> all = _get ();
260                 all.erase (all.begin() + i);
261                 _set (all);
262
263                 selection_changed ();
264         }
265
266         void resized (wxSizeEvent& ev)
267         {
268                 int const w = _list->GetSize().GetWidth() - 2;
269
270                 int fixed_width = 0;
271                 int growable = 0;
272                 int j = 0;
273                 for (auto i: _columns) {
274                         fixed_width += i.width.get_value_or (_default_width);
275                         if (!i.growable) {
276                                 _list->SetColumnWidth (j, i.width.get_value_or(_default_width));
277                         } else {
278                                 ++growable;
279                         }
280                         ++j;
281                 }
282
283                 j = 0;
284                 for (auto i: _columns) {
285                         if (i.growable) {
286                                 _list->SetColumnWidth (j, i.width.get_value_or(_default_width) + (w - fixed_width) / growable);
287                         }
288                         ++j;
289                 }
290
291                 ev.Skip ();
292         }
293
294         std::function <std::vector<T> ()> _get;
295         std::function <void (std::vector<T>)> _set;
296         std::vector<EditableListColumn> _columns;
297         std::function<std::string (T, int)> _column;
298
299         wxButton* _add;
300         wxButton* _edit;
301         wxButton* _remove;
302         wxListCtrl* _list;
303         wxBoxSizer* _sizer;
304         int _default_width;
305 };
306
307 #endif