+void
+AudioMappingView::paint_row_lines (wxDC& dc)
+{
+ for (size_t i = 0; i < _input_channels.size(); ++i) {
+ dc.DrawLine (
+ wxPoint(MINIMUM_COLUMN_WIDTH * 2, TOP_HEIGHT + ROW_HEIGHT * i),
+ wxPoint(LEFT_WIDTH + _column_widths_total, TOP_HEIGHT + ROW_HEIGHT * i)
+ );
+ }
+ dc.DrawLine (
+ wxPoint(MINIMUM_COLUMN_WIDTH * 2, TOP_HEIGHT + ROW_HEIGHT * _input_channels.size()),
+ wxPoint(LEFT_WIDTH + _column_widths_total, TOP_HEIGHT + ROW_HEIGHT * _input_channels.size())
+ );
+}
+
+
+void
+AudioMappingView::paint_indicators (wxDC& dc)
+{
+ /* _{input,output}_channels and _map may not always be in sync, be careful here */
+ size_t const output = min(_output_channels.size(), size_t(_map.output_channels()));
+ size_t const input = min(_input_channels.size(), size_t(_map.input_channels()));
+
+ int xp = LEFT_WIDTH;
+ for (size_t x = 0; x < output; ++x) {
+ for (size_t y = 0; y < input; ++y) {
+ dc.SetBrush (*wxWHITE_BRUSH);
+ dc.DrawRectangle (
+ wxRect(
+ xp + (_column_widths[x] - INDICATOR_SIZE) / 2,
+ TOP_HEIGHT + y * ROW_HEIGHT + (ROW_HEIGHT - INDICATOR_SIZE) / 2,
+ INDICATOR_SIZE, INDICATOR_SIZE
+ )
+ );
+
+ float const value_dB = linear_to_db(_map.get(_input_channels[y].index, _output_channels[x].index));
+ auto const colour = value_dB <= 0 ? wxColour(0, 255, 0) : wxColour(255, 150, 0);
+ int const range = 18;
+ int height = 0;
+ if (value_dB > -range) {
+ height = min(INDICATOR_SIZE, static_cast<int>(INDICATOR_SIZE * (1 + value_dB / range)));
+ }
+
+ dc.SetBrush (*wxTheBrushList->FindOrCreateBrush(colour, wxBRUSHSTYLE_SOLID));
+ dc.DrawRectangle (
+ wxRect(
+ xp + (_column_widths[x] - INDICATOR_SIZE) / 2,
+ TOP_HEIGHT + y * ROW_HEIGHT + (ROW_HEIGHT - INDICATOR_SIZE) / 2 + INDICATOR_SIZE - height,
+ INDICATOR_SIZE, height
+ )
+ );
+
+ }
+ xp += _column_widths[x];
+ }
+}
+
+
+static
+void restore (wxDC& dc)
+{
+ dc.SetLogicalOrigin (0, 0);
+ dc.DestroyClippingRegion ();
+}
+
+
+void
+AudioMappingView::paint ()
+{
+ wxPaintDC dc (_body);
+
+ int const hs = _horizontal_scroll->GetThumbPosition ();
+ int const vs = _vertical_scroll->GetThumbPosition ();
+
+ paint_static (dc);
+
+ dc.SetClippingRegion (
+ LEFT_WIDTH,
+ 0,
+ _column_widths_total,
+ ROW_HEIGHT * (2 + _input_channels.size())
+ );
+ dc.SetLogicalOrigin (hs, 0);
+ paint_column_labels (dc);
+ restore (dc);
+
+ dc.SetClippingRegion(
+ 0,
+ TOP_HEIGHT,
+ LEFT_WIDTH,
+ min(int(ROW_HEIGHT * _input_channels.size()), GetSize().GetHeight() - TOP_HEIGHT) + 1
+ );
+ dc.SetLogicalOrigin (0, vs);
+ paint_row_labels (dc);
+ restore (dc);
+
+ dc.SetClippingRegion(
+ MINIMUM_COLUMN_WIDTH * 2,
+ TOP_HEIGHT,
+ MINIMUM_COLUMN_WIDTH + _column_widths_total,
+ min(int(ROW_HEIGHT * (2 + _input_channels.size())), GetSize().GetHeight() - TOP_HEIGHT)
+ );
+ dc.SetLogicalOrigin (hs, vs);
+ paint_row_lines (dc);
+ restore (dc);
+
+ dc.SetClippingRegion(
+ LEFT_WIDTH,
+ MINIMUM_COLUMN_WIDTH,
+ MINIMUM_COLUMN_WIDTH + _column_widths_total,
+ min(int(ROW_HEIGHT * (1 + _input_channels.size())), GetSize().GetHeight() - ROW_HEIGHT)
+ );
+ dc.SetLogicalOrigin(hs, vs);
+ paint_column_lines (dc);
+ restore (dc);
+
+ dc.SetClippingRegion (
+ LEFT_WIDTH,
+ TOP_HEIGHT,
+ _column_widths_total,
+ min(int(ROW_HEIGHT * _input_channels.size()), GetSize().GetHeight() - TOP_HEIGHT)
+ );
+ dc.SetLogicalOrigin(hs, vs);
+ paint_indicators (dc);
+ restore (dc);
+}
+
+
+optional<pair<NamedChannel, NamedChannel>>
+AudioMappingView::mouse_event_to_channels (wxMouseEvent& ev) const