LevelPanWidget.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7 
8  This program is free software; you can redistribute it and/or
9  modify it under the terms of the GNU General Public License as
10  published by the Free Software Foundation; either version 2 of the
11  License, or (at your option) any later version. See the file
12  COPYING included with this distribution for more information.
13 */
14 
15 #include "LevelPanWidget.h"
16 
17 #include <QPainter>
18 #include <QMouseEvent>
19 #include <QWheelEvent>
20 
21 #include "layer/ColourMapper.h"
22 #include "base/AudioLevel.h"
23 
24 #include "WidgetScale.h"
25 
26 #include <iostream>
27 #include <cmath>
28 #include <cassert>
29 
30 using std::cerr;
31 using std::endl;
32 
64 static const int maxPan = 2; // range is -maxPan to maxPan
65 
67  QWidget(parent),
68  m_minNotch(0),
69  m_maxNotch(10),
70  m_notch(m_maxNotch),
71  m_pan(0),
72  m_monitorLeft(-1),
73  m_monitorRight(-1),
74  m_editable(true),
75  m_editing(false),
76  m_includeMute(true),
77  m_includeHalfSteps(true)
78 {
79  setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
80  setLevel(1.0);
81  setPan(0.0);
82 }
83 
85 {
86 }
87 
88 void
90 {
91  setLevel(1.0);
92  setPan(0.0);
95 }
96 
97 QSize
99 {
100  return WidgetScale::scaleQSize(QSize(40, 40));
101 }
102 
103 int
105 {
106  if (notch < m_minNotch) notch = m_minNotch;
107  if (notch > m_maxNotch) notch = m_maxNotch;
108  if (!m_includeHalfSteps) {
109  notch = (notch / 2) * 2;
110  }
111  return notch;
112 }
113 
114 int
116 {
117  if (pan < -maxPan) pan = -maxPan;
118  if (pan > maxPan) pan = maxPan;
119  return pan;
120 }
121 
122 int
123 LevelPanWidget::audioLevelToNotch(float audioLevel) const
124 {
125  int notch = AudioLevel::multiplier_to_fader
126  (audioLevel, m_maxNotch, AudioLevel::ShortFader);
127  return clampNotch(notch);
128 }
129 
130 float
132 {
133  return float(AudioLevel::fader_to_multiplier
134  (notch, m_maxNotch, AudioLevel::ShortFader));
135 }
136 
137 void
139 {
140  int notch = audioLevelToNotch(level);
141  if (notch != m_notch) {
142  m_notch = notch;
143  float convertsTo = getLevel();
144  if (fabsf(convertsTo - level) > 1e-5) {
146  }
147  update();
148  }
149 }
150 
151 float
153 {
154  return notchToAudioLevel(m_notch);
155 }
156 
157 int
158 LevelPanWidget::audioPanToPan(float audioPan) const
159 {
160  int pan = int(round(audioPan * maxPan));
161  pan = clampPan(pan);
162  return pan;
163 }
164 
165 float
167 {
168  return float(pan) / float(maxPan);
169 }
170 
171 void
173 {
174  int pan = audioPanToPan(fpan);
175  if (pan != m_pan) {
176  m_pan = pan;
177  update();
178  }
179 }
180 
181 float
183 {
184  return panToAudioPan(m_pan);
185 }
186 
187 void
188 LevelPanWidget::setMonitoringLevels(float left, float right)
189 {
190  m_monitorLeft = left;
191  m_monitorRight = right;
192  update();
193 }
194 
195 bool
197 {
198  return m_editable;
199 }
200 
201 bool
203 {
204  return m_includeMute;
205 }
206 
207 void
209 {
210  m_editable = editable;
211  update();
212 }
213 
214 void
216 {
217  m_includeMute = include;
218  if (m_includeMute) {
219  m_minNotch = 0;
220  } else {
221  m_minNotch = 1;
222  }
224  update();
225 }
226 
227 void
229 {
230  emit levelChanged(getLevel());
231 }
232 
233 void
235 {
236  emit panChanged(getPan());
237 }
238 
239 void
241 {
242  if (e->button() == Qt::MidButton ||
243  ((e->button() == Qt::LeftButton) &&
244  (e->modifiers() & Qt::ControlModifier))) {
245  setToDefault();
246  } else if (e->button() == Qt::LeftButton) {
247  m_editing = true;
248  mouseMoveEvent(e);
249  }
250 }
251 
252 void
254 {
255  mouseMoveEvent(e);
256  m_editing = false;
257 }
258 
259 void
261 {
262  if (!m_editable) return;
263  if (!m_editing) return;
264 
265  int notch = coordsToNotch(rect(), e->pos());
266  int pan = coordsToPan(rect(), e->pos());
267 
268  if (notch == m_notch && pan == m_pan) {
269  return;
270  }
271  if (notch != m_notch) {
272  m_notch = notch;
274  }
275  if (pan != m_pan) {
276  m_pan = pan;
277  emitPanChanged();
278  }
279  update();
280 }
281 
282 void
284 {
285  int delta = m_wheelCounter.count(e);
286 
287  if (delta == 0) {
288  return;
289  }
290 
291  if (e->modifiers() & Qt::ControlModifier) {
292  m_pan = clampPan(m_pan + delta);
293  emitPanChanged();
294  update();
295  } else {
296  m_notch = clampNotch(m_notch + delta);
298  update();
299  }
300 }
301 
302 int
303 LevelPanWidget::coordsToNotch(QRectF rect, QPointF loc) const
304 {
305  double h = rect.height();
306 
307  int nnotch = m_maxNotch + 1;
308  double cell = h / nnotch;
309 
310  int notch = int((h - (loc.y() - rect.y())) / cell);
311  notch = clampNotch(notch);
312 
313  return notch;
314 }
315 
316 int
317 LevelPanWidget::coordsToPan(QRectF rect, QPointF loc) const
318 {
319  double w = rect.width();
320 
321  int npan = maxPan * 2 + 1;
322  double cell = w / npan;
323 
324  int pan = int((loc.x() - rect.x()) / cell) - maxPan;
325  pan = clampPan(pan);
326 
327  return pan;
328 }
329 
330 QSizeF
331 LevelPanWidget::cellSize(QRectF rect) const
332 {
333  double w = rect.width(), h = rect.height();
334  int ncol = maxPan * 2 + 1;
335  int nrow = m_maxNotch/2;
336  double wcell = w / ncol, hcell = h / nrow;
337  return QSizeF(wcell, hcell);
338 }
339 
340 QPointF
341 LevelPanWidget::cellCentre(QRectF rect, int row, int col) const
342 {
343  QSizeF cs = cellSize(rect);
344  return QPointF(rect.x() +
345  cs.width() * (col + maxPan) + cs.width() / 2.,
346  rect.y() + rect.height() -
347  cs.height() * (row + 1) + cs.height() / 2.);
348 }
349 
350 QSizeF
352 {
353  double extent = 0.7;
354  QSizeF cs = cellSize(rect);
355  double m = std::min(cs.width(), cs.height());
356  return QSizeF(m * extent, m * extent);
357 }
358 
359 QRectF
360 LevelPanWidget::cellLightRect(QRectF rect, int row, int col) const
361 {
362  QSizeF cls = cellLightSize(rect);
363  QPointF cc = cellCentre(rect, row, col);
364  return QRectF(cc.x() - cls.width() / 2.,
365  cc.y() - cls.height() / 2.,
366  cls.width(),
367  cls.height());
368 }
369 
370 double
372 {
373  double tw = ceil(rect.width() / (maxPan * 2. * 10.));
374  double th = ceil(rect.height() / (m_maxNotch/2 * 10.));
375  return std::min(th, tw);
376 }
377 
378 double
380 {
381  QSizeF cs = cellSize(rect);
382  double m = std::min(cs.width(), cs.height());
383  return m / 5;
384 }
385 
386 QRectF
387 LevelPanWidget::cellOutlineRect(QRectF rect, int row, int col) const
388 {
389  QRectF clr = cellLightRect(rect, row, col);
390  double adj = thinLineWidth(rect)/2 + 0.5;
391  return clr.adjusted(-adj, -adj, adj, adj);
392 }
393 
394 QColor
396 {
397  if (cell < 1) return Qt::black;
398  if (cell < 2) return QColor(80, 0, 0);
399  if (cell < 3) return QColor(160, 0, 0);
400  if (cell < 4) return QColor(255, 0, 0);
401  return QColor(255, 255, 0);
402 }
403 
404 void
405 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
406 {
407  QPainter paint(dev);
408 
409  paint.setRenderHint(QPainter::Antialiasing, true);
410 
411  double thin = thinLineWidth(rect);
412  double radius = cornerRadius(rect);
413 
414  QColor columnBackground = QColor(180, 180, 180);
415 
416  bool monitoring = (m_monitorLeft > 0.f || m_monitorRight > 0.f);
417 
418  QPen pen;
419  if (isEnabled()) {
420  pen.setColor(Qt::black);
421  } else {
422  pen.setColor(Qt::darkGray);
423  }
424  pen.setWidthF(thin);
425  pen.setCapStyle(Qt::FlatCap);
426  pen.setJoinStyle(Qt::MiterJoin);
427 
428  for (int pan = -maxPan; pan <= maxPan; ++pan) {
429 
430  paint.setPen(Qt::NoPen);
431  paint.setBrush(columnBackground);
432 
433  QRectF top = cellOutlineRect(rect, m_maxNotch/2 - 1, pan);
434  QRectF bottom = cellOutlineRect(rect, 0, pan);
435  paint.drawRoundedRect(QRectF(top.x(),
436  top.y(),
437  top.width(),
438  bottom.y() + bottom.height() - top.y()),
439  radius, radius);
440 
441  if (!asIfEditable && m_includeMute && m_notch == 0) {
442  // We will instead be drawing a single big X for mute,
443  // after this loop
444  continue;
445  }
446 
447  if (!monitoring && m_pan != pan) {
448  continue;
449  }
450 
451  int monitorNotch = 0;
452  if (monitoring) {
453  float rprop = float(pan - (-maxPan)) / float(maxPan * 2);
454  float lprop = float(maxPan - pan) / float(maxPan * 2);
455  float monitorLevel =
456  lprop * m_monitorLeft * m_monitorLeft +
457  rprop * m_monitorRight * m_monitorRight;
458  monitorNotch = audioLevelToNotch(monitorLevel);
459  }
460 
461  int firstCell = 0;
462  int lastCell = m_maxNotch / 2 - 1;
463 
464  for (int cell = firstCell; cell <= lastCell; ++cell) {
465 
466  QRectF clr = cellLightRect(rect, cell, pan);
467 
468  if (m_includeMute && m_pan == pan && m_notch == 0) {
469  // X for mute in the bottom cell
470  paint.setPen(pen);
471  paint.drawLine(clr.topLeft(), clr.bottomRight());
472  paint.drawLine(clr.bottomLeft(), clr.topRight());
473  break;
474  }
475 
476  const int none = 0, half = 1, full = 2;
477 
478  int fill = none;
479 
480  int outline = none;
481  if (m_pan == pan && m_notch > cell * 2 + 1) {
482  outline = full;
483  } else if (m_pan == pan && m_notch == cell * 2 + 1) {
484  outline = half;
485  }
486 
487  if (monitoring) {
488  if (monitorNotch > cell * 2 + 1) {
489  fill = full;
490  } else if (monitorNotch == cell * 2 + 1) {
491  fill = half;
492  }
493  } else {
494  if (isEnabled()) {
495  fill = outline;
496  }
497  }
498 
499  // If one of {fill, outline} is "full" and the other is
500  // "half", then we draw the "half" one first (because we
501  // need to erase half of it)
502 
503  if (fill == half || outline == half) {
504  if (fill == half) {
505  paint.setBrush(cellToColour(cell));
506  } else {
507  paint.setBrush(Qt::NoBrush);
508  }
509  if (outline == half) {
510  paint.setPen(pen);
511  } else {
512  paint.setPen(Qt::NoPen);
513  }
514 
515  paint.drawRoundedRect(clr, radius, radius);
516 
517  paint.setBrush(columnBackground);
518 
519  if (cell == lastCell) {
520  QPen bgpen(pen);
521  bgpen.setColor(columnBackground);
522  paint.setPen(bgpen);
523  paint.drawRoundedRect(QRectF(clr.x(),
524  clr.y(),
525  clr.width(),
526  clr.height()/4),
527  radius, radius);
528  paint.drawRect(QRectF(clr.x(),
529  clr.y() + clr.height()/4,
530  clr.width(),
531  clr.height()/4));
532  } else {
533  paint.setPen(Qt::NoPen);
534  QRectF cor = cellOutlineRect(rect, cell, pan);
535  paint.drawRect(QRectF(cor.x(),
536  cor.y() - 0.5,
537  cor.width(),
538  cor.height()/2));
539  }
540  }
541 
542  if (outline == full || fill == full) {
543 
544  if (fill == full) {
545  paint.setBrush(cellToColour(cell));
546  } else {
547  paint.setBrush(Qt::NoBrush);
548  }
549  if (outline == full) {
550  paint.setPen(pen);
551  } else {
552  paint.setPen(Qt::NoPen);
553  }
554 
555  paint.drawRoundedRect(clr, radius, radius);
556  }
557  }
558  }
559 
560  if (!asIfEditable && m_includeMute && m_notch == 0) {
561  // The X for mute takes up the whole display when we're not
562  // being rendered in editable style
563  pen.setColor(Qt::black);
564  pen.setWidthF(thin * 2);
565  pen.setCapStyle(Qt::RoundCap);
566  paint.setPen(pen);
567  paint.drawLine(cellCentre(rect, 0, -maxPan),
568  cellCentre(rect, m_maxNotch/2 - 1, maxPan));
569  paint.drawLine(cellCentre(rect, m_maxNotch/2 - 1, -maxPan),
570  cellCentre(rect, 0, maxPan));
571  }
572 }
573 
574 void
576 {
577  renderTo(this, rect(), m_editable);
578 }
579 
580 void
582 {
583  QWidget::enterEvent(e);
584  emit mouseEntered();
585 }
586 
587 void
589 {
590  QWidget::enterEvent(e);
591  emit mouseLeft();
592 }
593 
int clampPan(int pan) const
int clampNotch(int notch) const
QRectF cellOutlineRect(QRectF, int row, int col) const
void setLevel(float)
Set level.
void setPan(float)
Set pan in the range [-1,1]. The value will be rounded.
QPointF cellCentre(QRectF, int row, int col) const
QSizeF cellSize(QRectF) const
int coordsToNotch(QRectF rect, QPointF pos) const
void enterEvent(QEvent *) override
QRectF cellLightRect(QRectF, int row, int col) const
void mouseMoveEvent(QMouseEvent *ev) override
float panToAudioPan(int pan) const
void levelChanged(float)
void setIncludeMute(bool)
Specify whether the level range should include muting or not.
void mousePressEvent(QMouseEvent *ev) override
int audioLevelToNotch(float audioLevel) const
void setEditable(bool)
Specify whether the widget is editable or read-only (default editable)
double thinLineWidth(QRectF) const
static QSize scaleQSize(QSize size)
Definition: WidgetScale.h:58
void renderTo(QPaintDevice *, QRectF, bool asIfEditable) const
Draw a suitably sized copy of the widget&#39;s contents to the given device.
void mouseReleaseEvent(QMouseEvent *ev) override
void leaveEvent(QEvent *) override
QSize sizeHint() const override
void wheelEvent(QWheelEvent *ev) override
void setMonitoringLevels(float, float)
Set left and right peak monitoring levels in the range [0,1].
void panChanged(float)
float notchToAudioLevel(int notch) const
static const int maxPan
Gain and pan scales:
LevelPanWidget(QWidget *parent=0)
WheelCounter m_wheelCounter
void mouseEntered()
float getPan() const
Return pan as a value in the range [-1,1].
bool isEditable() const
Find out whether the widget is editable.
int coordsToPan(QRectF rect, QPointF pos) const
int count(QWheelEvent *e)
Definition: WheelCounter.h:31
void paintEvent(QPaintEvent *ev) override
double cornerRadius(QRectF) const
bool includesMute() const
Discover whether the level range includes muting or not.
QColor cellToColour(int cell) const
void setToDefault()
Reset to default values.
float getLevel() const
Return level as a gain value.
QSizeF cellLightSize(QRectF) const
int audioPanToPan(float audioPan) const