Merge branch 'master' into 2.0
[dcpomatic.git] / src / wx / audio_mapping_view.cc
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 #include <wx/wx.h>
21 #include <wx/renderer.h>
22 #include <wx/grid.h>
23 #include <dcp/types.h>
24 #include "lib/audio_mapping.h"
25 #include "lib/util.h"
26 #include "audio_mapping_view.h"
27 #include "wx_util.h"
28 #include "audio_gain_dialog.h"
29
30 using std::cout;
31 using std::list;
32 using std::string;
33 using std::max;
34 using boost::shared_ptr;
35 using boost::lexical_cast;
36
37 #define INDICATOR_SIZE 16
38
39 enum {
40         ID_off = 1,
41         ID_full = 2,
42         ID_minus3dB = 3,
43         ID_edit = 4
44 };
45
46 class NoSelectionStringRenderer : public wxGridCellStringRenderer
47 {
48 public:
49         void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool)
50         {
51                 wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false);
52         }
53 };
54
55 class ValueRenderer : public wxGridCellRenderer
56 {
57 public:
58
59         void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
60         {
61                 LocaleGuard lg;
62         
63                 dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (255, 255, 255), 1, wxPENSTYLE_SOLID));
64                 dc.SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (255, 255, 255), wxBRUSHSTYLE_SOLID));
65                 dc.DrawRectangle (rect);
66
67                 int const xo = (rect.GetWidth() - INDICATOR_SIZE) / 2;
68                 int const yo = (rect.GetHeight() - INDICATOR_SIZE) / 2;
69
70                 dc.SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
71                 dc.SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (255, 255, 255), wxBRUSHSTYLE_SOLID));
72                 dc.DrawRectangle (wxRect (rect.GetLeft() + xo, rect.GetTop() + yo, INDICATOR_SIZE, INDICATOR_SIZE));
73
74                 float const value = lexical_cast<float> (wx_to_std (grid.GetCellValue (row, col)));
75                 float const value_dB = 20 * log10 (value);
76                 int const range = 18;
77                 int height = 0;
78                 if (value_dB > -range) {
79                         height = INDICATOR_SIZE * (1 + value_dB / range);
80                 }
81
82                 height = max (0, height);
83                 
84                 if (value > 0) {
85                         /* Make sure we get a little bit of the marker if there is any gain */
86                         height = max (3, height);
87                 }
88
89                 dc.SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (0, 255, 0), wxBRUSHSTYLE_SOLID));
90                 dc.DrawRectangle (wxRect (rect.GetLeft() + xo, rect.GetTop() + yo + INDICATOR_SIZE - height, INDICATOR_SIZE, height));
91         }
92
93         wxSize GetBestSize (wxGrid &, wxGridCellAttr &, wxDC &, int, int)
94         {
95                 return wxSize (INDICATOR_SIZE + 4, INDICATOR_SIZE + 4);
96         }
97         
98         wxGridCellRenderer* Clone () const
99         {
100                 return new ValueRenderer;
101         }
102 };
103
104
105 AudioMappingView::AudioMappingView (wxWindow* parent)
106         : wxPanel (parent, wxID_ANY)
107         , _menu_row (0)
108         , _menu_column (1)
109         , _last_tooltip_row (0)
110         , _last_tooltip_column (0)
111 {
112         _grid = new wxGrid (this, wxID_ANY);
113
114         _grid->CreateGrid (0, MAX_AUDIO_CHANNELS + 1);
115         _grid->HideRowLabels ();
116         _grid->DisableDragRowSize ();
117         _grid->DisableDragColSize ();
118         _grid->EnableEditing (false);
119         _grid->SetCellHighlightPenWidth (0);
120         _grid->SetDefaultRenderer (new NoSelectionStringRenderer);
121
122         set_column_labels ();
123
124         _sizer = new wxBoxSizer (wxVERTICAL);
125         _sizer->Add (_grid, 1, wxEXPAND | wxALL);
126         SetSizerAndFit (_sizer);
127
128         Bind (wxEVT_GRID_CELL_LEFT_CLICK, boost::bind (&AudioMappingView::left_click, this, _1));
129         Bind (wxEVT_GRID_CELL_RIGHT_CLICK, boost::bind (&AudioMappingView::right_click, this, _1));
130         _grid->GetGridWindow()->Bind (wxEVT_MOTION, boost::bind (&AudioMappingView::mouse_moved, this, _1));
131
132         _menu = new wxMenu;
133         _menu->Append (ID_off, _("Off"));
134         _menu->Append (ID_full, _("Full"));
135         _menu->Append (ID_minus3dB, _("-3dB"));
136         _menu->Append (ID_edit, _("Edit..."));
137
138         Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&AudioMappingView::off, this), ID_off);
139         Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&AudioMappingView::full, this), ID_full);
140         Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&AudioMappingView::minus3dB, this), ID_minus3dB);
141         Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&AudioMappingView::edit, this), ID_edit);
142 }
143
144 void
145 AudioMappingView::map_changed ()
146 {
147         update_cells ();
148         Changed (_map);
149         _last_tooltip_column = -1;
150 }       
151
152 void
153 AudioMappingView::left_click (wxGridEvent& ev)
154 {
155         if (ev.GetCol() == 0) {
156                 return;
157         }
158
159         dcp::Channel d = static_cast<dcp::Channel> (ev.GetCol() - 1);
160         
161         if (_map.get (ev.GetRow(), d) > 0) {
162                 _map.set (ev.GetRow(), d, 0);
163         } else {
164                 _map.set (ev.GetRow(), d, 1);
165         }
166
167         map_changed ();
168 }
169
170 void
171 AudioMappingView::right_click (wxGridEvent& ev)
172 {
173         if (ev.GetCol() == 0) {
174                 return;
175         }
176
177         _menu_row = ev.GetRow ();
178         _menu_column = ev.GetCol ();
179         PopupMenu (_menu, ev.GetPosition ());
180 }
181
182 void
183 AudioMappingView::off ()
184 {
185         _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 0);
186         map_changed ();
187 }
188
189 void
190 AudioMappingView::full ()
191 {
192         _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1);
193         map_changed ();
194 }
195
196 void
197 AudioMappingView::minus3dB ()
198 {
199         _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2));
200         map_changed ();
201 }
202
203 void
204 AudioMappingView::edit ()
205 {
206         dcp::Channel d = static_cast<dcp::Channel> (_menu_column - 1);
207         
208         AudioGainDialog* dialog = new AudioGainDialog (this, _menu_row, _menu_column - 1, _map.get (_menu_row, d));
209         if (dialog->ShowModal () == wxID_OK) {
210                 _map.set (_menu_row, d, dialog->value ());
211                 map_changed ();
212         }
213         
214         dialog->Destroy ();
215 }
216
217 void
218 AudioMappingView::set (AudioMapping map)
219 {
220         _map = map;
221         update_cells ();
222 }
223
224 void
225 AudioMappingView::update_cells ()
226 {
227         LocaleGuard lg;
228         
229         if (_grid->GetNumberRows ()) {
230                 _grid->DeleteRows (0, _grid->GetNumberRows ());
231         }
232
233         _grid->InsertRows (0, _map.content_channels ());
234
235         for (int i = 0; i < _map.content_channels(); ++i) {
236                 for (int j = 0; j < MAX_AUDIO_CHANNELS; ++j) {
237                         _grid->SetCellRenderer (i, j + 1, new ValueRenderer);
238                 }
239         }
240         
241         for (int i = 0; i < _map.content_channels(); ++i) {
242                 _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
243
244                 for (int j = 1; j < _grid->GetNumberCols(); ++j) {
245                         _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
246                 }
247         }
248
249         _grid->AutoSize ();
250 }
251
252 /** @param c Number of DCP channels */
253 void
254 AudioMappingView::set_channels (int c)
255 {
256         c++;
257
258         if (c < _grid->GetNumberCols ()) {
259                 _grid->DeleteCols (c, _grid->GetNumberCols() - c);
260         } else if (c > _grid->GetNumberCols ()) {
261                 _grid->InsertCols (_grid->GetNumberCols(), c - _grid->GetNumberCols());
262                 set_column_labels ();
263         }
264
265         update_cells ();
266 }
267
268 void
269 AudioMappingView::set_column_labels ()
270 {
271         int const c = _grid->GetNumberCols ();
272         
273         _grid->SetColLabelValue (0, _("Content"));
274
275 #if MAX_AUDIO_CHANNELS != 12
276 #warning AudioMappingView::set_column_labels() is expecting the wrong MAX_AUDIO_CHANNELS
277 #endif  
278         
279         if (c > 0) {
280                 _grid->SetColLabelValue (1, _("L"));
281         }
282         
283         if (c > 1) {
284                 _grid->SetColLabelValue (2, _("R"));
285         }
286         
287         if (c > 2) {
288                 _grid->SetColLabelValue (3, _("C"));
289         }
290         
291         if (c > 3) {
292                 _grid->SetColLabelValue (4, _("Lfe"));
293         }
294         
295         if (c > 4) {
296                 _grid->SetColLabelValue (5, _("Ls"));
297         }
298         
299         if (c > 5) {
300                 _grid->SetColLabelValue (6, _("Rs"));
301         }
302
303         if (c > 6) {
304                 _grid->SetColLabelValue (7, _("HI"));
305         }
306
307         if (c > 7) {
308                 _grid->SetColLabelValue (8, _("VI"));
309         }
310
311         if (c > 8) {
312                 _grid->SetColLabelValue (9, _("Lc"));
313         }
314
315         if (c > 9) {
316                 _grid->SetColLabelValue (10, _("Rc"));
317         }
318
319         if (c > 10) {
320                 _grid->SetColLabelValue (11, _("BsL"));
321         }
322
323         if (c > 11) {
324                 _grid->SetColLabelValue (12, _("BsR"));
325         }
326
327         _grid->AutoSize ();
328 }
329
330 void
331 AudioMappingView::mouse_moved (wxMouseEvent& ev)
332 {
333         int xx;
334         int yy;
335         _grid->CalcUnscrolledPosition (ev.GetX(), ev.GetY(), &xx, &yy);
336
337         int const row = _grid->YToRow (yy);
338         int const column = _grid->XToCol (xx);
339
340         if (row < 0 || column < 1) {
341                 _grid->GetGridWindow()->SetToolTip ("");
342                 _last_tooltip_row = row;
343                 _last_tooltip_column = column;
344         }
345
346         if (row != _last_tooltip_row || column != _last_tooltip_column) {
347
348                 wxString s;
349                 float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
350                 if (gain == 0) {
351                         s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
352                 } else if (gain == 1) {
353                         s = wxString::Format (_("Audio will be passed from content channel %d to DCP channel %d unaltered."), row + 1, column);
354                 } else {
355                         float const dB = 20 * log10 (gain);
356                         s = wxString::Format (_("Audio will be passed from content channel %d to DCP channel %d with gain %.1fdB."), row + 1, column, dB);
357                 }
358                 
359                 _grid->GetGridWindow()->SetToolTip (s + " " + _("Right click to change gain."));
360                 _last_tooltip_row = row;
361                 _last_tooltip_column = column;
362         }
363
364         ev.Skip ();
365 }