Merge master.
[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 "lib/audio_mapping.h"
29 #include "lib/util.h"
30 #include "audio_mapping_view.h"
31 #include "wx_util.h"
32 #include "audio_gain_dialog.h"
33
34 using std::cout;
35 using std::list;
36 using std::string;
37 using std::max;
38 using boost::shared_ptr;
39 using boost::lexical_cast;
40
41 #define INDICATOR_SIZE 16
42
43 enum {
44         ID_off = 1,
45         ID_full = 2,
46         ID_minus3dB = 3,
47         ID_edit = 4
48 };
49
50 class NoSelectionStringRenderer : public wxGridCellStringRenderer
51 {
52 public:
53         void Draw (wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool)
54         {
55                 wxGridCellStringRenderer::Draw (grid, attr, dc, rect, row, col, false);
56         }
57 };
58
59 /** @class ValueRenderer
60  *  @brief wxGridCellRenderer for a gain value.
61  */
62 class ValueRenderer : public wxGridCellRenderer
63 {
64 public:
65
66         void Draw (wxGrid& grid, wxGridCellAttr &, wxDC& dc, const wxRect& rect, int row, int col, bool)
67         {
68                 LocaleGuard lg;
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_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_minus3dB, _("-3dB"));
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::minus3dB, this), ID_minus3dB);
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::minus3dB ()
205 {
206         _map.set (_menu_row, static_cast<dcp::Channel> (_menu_column - 1), 1 / sqrt (2));
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         LocaleGuard lg;
235         
236         if (_grid->GetNumberRows ()) {
237                 _grid->DeleteRows (0, _grid->GetNumberRows ());
238         }
239
240         _grid->InsertRows (0, _map.content_channels ());
241
242         for (int i = 0; i < _map.content_channels(); ++i) {
243                 for (int j = 0; j < MAX_AUDIO_CHANNELS; ++j) {
244                         _grid->SetCellRenderer (i, j + 1, new ValueRenderer);
245                 }
246         }
247         
248         for (int i = 0; i < _map.content_channels(); ++i) {
249                 _grid->SetCellValue (i, 0, wxString::Format (wxT("%d"), i + 1));
250
251                 for (int j = 1; j < _grid->GetNumberCols(); ++j) {
252                         _grid->SetCellValue (i, j, std_to_wx (lexical_cast<string> (_map.get (i, static_cast<dcp::Channel> (j - 1)))));
253                 }
254         }
255
256         _grid->AutoSize ();
257 }
258
259 /** @param c Number of DCP channels */
260 void
261 AudioMappingView::set_channels (int c)
262 {
263         c++;
264
265         if (c < _grid->GetNumberCols ()) {
266                 _grid->DeleteCols (c, _grid->GetNumberCols() - c);
267         } else if (c > _grid->GetNumberCols ()) {
268                 _grid->InsertCols (_grid->GetNumberCols(), c - _grid->GetNumberCols());
269                 set_column_labels ();
270         }
271
272         update_cells ();
273 }
274
275 void
276 AudioMappingView::set_column_labels ()
277 {
278         int const c = _grid->GetNumberCols ();
279         
280         _grid->SetColLabelValue (0, _("Content"));
281
282 #if MAX_AUDIO_CHANNELS != 12
283 #warning AudioMappingView::set_column_labels() is expecting the wrong MAX_AUDIO_CHANNELS
284 #endif  
285         
286         if (c > 0) {
287                 _grid->SetColLabelValue (1, _("L"));
288         }
289         
290         if (c > 1) {
291                 _grid->SetColLabelValue (2, _("R"));
292         }
293         
294         if (c > 2) {
295                 _grid->SetColLabelValue (3, _("C"));
296         }
297         
298         if (c > 3) {
299                 _grid->SetColLabelValue (4, _("Lfe"));
300         }
301         
302         if (c > 4) {
303                 _grid->SetColLabelValue (5, _("Ls"));
304         }
305         
306         if (c > 5) {
307                 _grid->SetColLabelValue (6, _("Rs"));
308         }
309
310         if (c > 6) {
311                 _grid->SetColLabelValue (7, _("HI"));
312         }
313
314         if (c > 7) {
315                 _grid->SetColLabelValue (8, _("VI"));
316         }
317
318         if (c > 8) {
319                 _grid->SetColLabelValue (9, _("Lc"));
320         }
321
322         if (c > 9) {
323                 _grid->SetColLabelValue (10, _("Rc"));
324         }
325
326         if (c > 10) {
327                 _grid->SetColLabelValue (11, _("BsL"));
328         }
329
330         if (c > 11) {
331                 _grid->SetColLabelValue (12, _("BsR"));
332         }
333
334         _grid->AutoSize ();
335 }
336
337 void
338 AudioMappingView::mouse_moved (wxMouseEvent& ev)
339 {
340         int xx;
341         int yy;
342         _grid->CalcUnscrolledPosition (ev.GetX(), ev.GetY(), &xx, &yy);
343
344         int const row = _grid->YToRow (yy);
345         int const column = _grid->XToCol (xx);
346
347         if (row < 0 || column < 1) {
348                 _grid->GetGridWindow()->SetToolTip ("");
349                 _last_tooltip_row = row;
350                 _last_tooltip_column = column;
351         }
352
353         if (row != _last_tooltip_row || column != _last_tooltip_column) {
354
355                 wxString s;
356                 float const gain = _map.get (row, static_cast<dcp::Channel> (column - 1));
357                 if (gain == 0) {
358                         s = wxString::Format (_("No audio will be passed from content channel %d to DCP channel %d."), row + 1, column);
359                 } else if (gain == 1) {
360                         s = wxString::Format (_("Audio will be passed from content channel %d to DCP channel %d unaltered."), row + 1, column);
361                 } else {
362                         float const dB = 20 * log10 (gain);
363                         s = wxString::Format (_("Audio will be passed from content channel %d to DCP channel %d with gain %.1fdB."), row + 1, column, dB);
364                 }
365                 
366                 _grid->GetGridWindow()->SetToolTip (s + " " + _("Right click to change gain."));
367                 _last_tooltip_row = row;
368                 _last_tooltip_column = column;
369         }
370
371         ev.Skip ();
372 }