Ignore empty KDM email addresses (#818).
[dcpomatic.git] / src / wx / editable_list.h
1 /*
2     Copyright (C) 2012-2016 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_EDITABLE_LIST_H
21 #define DCPOMATIC_EDITABLE_LIST_H
22
23 #include "wx_util.h"
24 #include <wx/wx.h>
25 #include <wx/listctrl.h>
26 #include <boost/function.hpp>
27 #include <vector>
28
29 bool always_valid ();
30
31 /** @param T type of things being edited.
32  *  @param S dialog to edit a thing.
33  */
34 template<class T, class S>
35 class EditableList : public wxPanel
36 {
37 public:
38         EditableList (
39                 wxWindow* parent,
40                 std::vector<std::string> columns,
41                 boost::function<std::vector<T> ()> get,
42                 boost::function<void (std::vector<T>)> set,
43                 boost::function<bool (T)> valid,
44                 boost::function<std::string (T, int)> column,
45                 bool can_edit = true,
46                 bool title = true
47                 )
48                 : wxPanel (parent)
49                 , _get (get)
50                 , _set (set)
51                 , _valid (valid)
52                 , _columns (columns.size ())
53                 , _column (column)
54                 , _edit (0)
55         {
56                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
57                 SetSizer (s);
58
59                 _table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
60                 _table->AddGrowableCol (0, 1);
61                 s->Add (_table, 1, wxEXPAND);
62
63                 long style = wxLC_REPORT | wxLC_SINGLE_SEL;
64                 if (title) {
65                         style |= wxLC_NO_HEADER;
66                 }
67                 _list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (columns.size() * 200, 100), style);
68
69                 for (size_t i = 0; i < columns.size(); ++i) {
70                         wxListItem ip;
71                         ip.SetId (i);
72                         ip.SetText (std_to_wx (columns[i]));
73                         ip.SetWidth (200);
74                         _list->InsertColumn (i, ip);
75                 }
76
77                 _table->Add (_list, 1, wxEXPAND | wxALL);
78
79                 {
80                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
81                         _add = new wxButton (this, wxID_ANY, _("Add..."));
82                         s->Add (_add, 0, wxTOP | wxBOTTOM, 2);
83                         if (can_edit) {
84                                 _edit = new wxButton (this, wxID_ANY, _("Edit..."));
85                                 s->Add (_edit, 0, wxTOP | wxBOTTOM, 2);
86                         }
87                         _remove = new wxButton (this, wxID_ANY, _("Remove"));
88                         s->Add (_remove, 0, wxTOP | wxBOTTOM, 2);
89                         _table->Add (s, 0);
90                 }
91
92                 _add->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::add_clicked, this));
93                 if (_edit) {
94                         _edit->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::edit_clicked, this));
95                 }
96                 _remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&EditableList::remove_clicked, this));
97
98                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&EditableList::selection_changed, this));
99                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&EditableList::selection_changed, this));
100                 _list->Bind (wxEVT_SIZE, boost::bind (&EditableList::resized, this, _1));
101
102                 refresh ();
103                 selection_changed ();
104         }
105
106         void refresh ()
107         {
108                 _list->DeleteAllItems ();
109
110                 std::vector<T> current = _get ();
111                 for (typename std::vector<T>::iterator i = current.begin (); i != current.end(); ++i) {
112                         add_to_control (*i);
113                 }
114         }
115
116         boost::optional<T> selection () const
117         {
118                 int item = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
119                 if (item == -1) {
120                         return boost::optional<T> ();
121                 }
122
123                 std::vector<T> all = _get ();
124                 DCPOMATIC_ASSERT (item >= 0 && item < int (all.size ()));
125                 return all[item];
126         }
127
128         void layout ()
129         {
130                 _table->Layout ();
131         }
132
133         boost::signals2::signal<void ()> SelectionChanged;
134
135 private:
136
137         void add_to_control (T item)
138         {
139                 wxListItem list_item;
140                 int const n = _list->GetItemCount ();
141                 list_item.SetId (n);
142                 _list->InsertItem (list_item);
143
144                 for (int i = 0; i < _columns; ++i) {
145                         _list->SetItem (n, i, std_to_wx (_column (item, i)));
146                 }
147         }
148
149         void selection_changed ()
150         {
151                 int const i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
152                 if (_edit) {
153                         _edit->Enable (i >= 0);
154                 }
155                 _remove->Enable (i >= 0);
156
157                 SelectionChanged ();
158         }
159
160         void add_clicked ()
161         {
162                 S* dialog = new S (this);
163
164                 if (dialog->ShowModal() == wxID_OK) {
165                         T const v = dialog->get ();
166                         if (_valid (v)) {
167                                 add_to_control (v);
168                                 std::vector<T> all = _get ();
169                                 all.push_back (v);
170                                 _set (all);
171                         }
172                 }
173
174                 dialog->Destroy ();
175         }
176
177         void edit_clicked ()
178         {
179                 int item = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
180                 if (item == -1) {
181                         return;
182                 }
183
184                 std::vector<T> all = _get ();
185                 DCPOMATIC_ASSERT (item >= 0 && item < int (all.size ()));
186
187                 S* dialog = new S (this);
188                 dialog->set (all[item]);
189                 if (dialog->ShowModal() == wxID_OK) {
190                         T const v = dialog->get ();
191                         if (!_valid (v)) {
192                                 return;
193                         }
194
195                         all[item] = v;
196                 }
197                 dialog->Destroy ();
198
199                 for (int i = 0; i < _columns; ++i) {
200                         _list->SetItem (item, i, std_to_wx (_column (all[item], i)));
201                 }
202
203                 _set (all);
204         }
205
206         void remove_clicked ()
207         {
208                 int i = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
209                 if (i == -1) {
210                         return;
211                 }
212
213                 _list->DeleteItem (i);
214                 std::vector<T> all = _get ();
215                 all.erase (all.begin() + i);
216                 _set (all);
217
218                 selection_changed ();
219         }
220
221         void resized (wxSizeEvent& ev)
222         {
223                 int const w = GetSize().GetWidth() / _columns;
224                 for (int i = 0; i < _columns; ++i) {
225                         _list->SetColumnWidth (i, w);
226                 }
227                 ev.Skip ();
228         }
229
230         boost::function <std::vector<T> ()> _get;
231         boost::function <void (std::vector<T>)> _set;
232         boost::function <bool (T)> _valid;
233         int _columns;
234         boost::function<std::string (T, int)> _column;
235
236         wxButton* _add;
237         wxButton* _edit;
238         wxButton* _remove;
239         wxListCtrl* _list;
240         wxFlexGridSizer* _table;
241 };
242
243 #endif