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