svgui  1.9
AudioDial.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 
38 #include "AudioDial.h"
39 
40 #include "base/RangeMapper.h"
41 
42 #include <cmath>
43 #include <iostream>
44 
45 #include <QTimer>
46 #include <QPainter>
47 #include <QPixmap>
48 #include <QColormap>
49 #include <QMouseEvent>
50 #include <QPaintEvent>
51 #include <QInputDialog>
52 
53 #include "base/Profiler.h"
54 
55 
56 
57 
58 
60 
61 
62 //-------------------------------------------------------------------------
63 // AudioDial - Instance knob widget class.
64 //
65 
66 #define AUDIO_DIAL_MIN (0.25 * M_PI)
67 #define AUDIO_DIAL_MAX (1.75 * M_PI)
68 #define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN)
69 
70 
71 //static int dialsExtant = 0;
72 
73 
74 // Constructor.
75 AudioDial::AudioDial(QWidget *parent) :
76  QDial(parent),
77  m_knobColor(Qt::black),
78  m_meterColor(Qt::white),
79  m_defaultValue(0),
80  m_defaultMappedValue(0),
81  m_mappedValue(0),
82  m_noMappedUpdate(false),
83  m_showTooltip(true),
84  m_rangeMapper(0)
85 {
86  m_mouseDial = false;
87  m_mousePressed = false;
88 // ++dialsExtant;
89 }
90 
91 
92 // Destructor.
94 {
95  delete m_rangeMapper;
96 // --dialsExtant;
97 }
98 
99 
100 void AudioDial::setRangeMapper(RangeMapper *mapper)
101 {
102 // cerr << "AudioDial[" << this << "][\"" << objectName() << "\"::setRangeMapper(" << mapper << ") [current is " << m_rangeMapper << "] (have " << dialsExtant << " dials extant)" << endl;
103 
104  if (m_rangeMapper == mapper) return;
105 
106  if (!m_rangeMapper && mapper) {
107  connect(this, SIGNAL(valueChanged(int)),
108  this, SLOT(updateMappedValue(int)));
109  }
110 
111  delete m_rangeMapper;
112  m_rangeMapper = mapper;
113 
114  updateMappedValue(value());
115 }
116 
117 
118 void AudioDial::paintEvent(QPaintEvent *)
119 {
120  Profiler profiler("AudioDial::paintEvent");
121 
122  QPainter paint;
123 
124  float angle = AUDIO_DIAL_MIN // offset
125  + (AUDIO_DIAL_RANGE *
126  (float(QDial::value() - QDial::minimum()) /
127  (float(QDial::maximum() - QDial::minimum()))));
128  int degrees = int(angle * 180.0 / M_PI);
129 
130  int ns = notchSize();
131  int numTicks = 1 + (maximum() + ns - minimum()) / ns;
132 
133  QColor knobColor(m_knobColor);
134  if (knobColor == Qt::black)
135  knobColor = palette().window().color();
136 
137  QColor meterColor(m_meterColor);
138  if (!isEnabled())
139  meterColor = palette().mid().color();
140  else if (m_meterColor == Qt::white)
141  meterColor = palette().highlight().color();
142 
143  int m_size = width() < height() ? width() : height();
144  int scale = 1;
145  int width = m_size - 2*scale;
146 
147  paint.begin(this);
148  paint.setRenderHint(QPainter::Antialiasing, true);
149  paint.translate(1, 1);
150 
151  QPen pen;
152  QColor c;
153 
154  // Knob body and face...
155 
156  c = knobColor;
157  pen.setColor(knobColor);
158  pen.setWidth(scale * 2);
159  pen.setCapStyle(Qt::FlatCap);
160 
161  paint.setPen(pen);
162  paint.setBrush(c);
163 
164  int indent = (int)(width * 0.15 + 1);
165 
166  paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent);
167 
168  pen.setWidth(3 * scale);
169  int pos = indent-1 + (width-2*indent) / 20;
170  int darkWidth = (width-2*indent) * 3 / 4;
171  while (darkWidth) {
172  c = c.light(102);
173  pen.setColor(c);
174  paint.setPen(pen);
175  paint.drawEllipse(pos, pos, darkWidth, darkWidth);
176  if (!--darkWidth) break;
177  paint.drawEllipse(pos, pos, darkWidth, darkWidth);
178  if (!--darkWidth) break;
179  paint.drawEllipse(pos, pos, darkWidth, darkWidth);
180  ++pos; --darkWidth;
181  }
182 
183  // Tick notches...
184 
185  if ( notchesVisible() ) {
186 // cerr << "Notches visible" << endl;
187  pen.setColor(palette().dark().color());
188  pen.setWidth(scale);
189  paint.setPen(pen);
190  for (int i = 0; i < numTicks; ++i) {
191  int div = numTicks;
192  if (div > 1) --div;
193  drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
194  width, true);
195  }
196  }
197 
198  // The bright metering bit...
199 
200  c = meterColor;
201  pen.setColor(c);
202  pen.setWidth(indent);
203  paint.setPen(pen);
204 
205 // cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << endl;
206 
207  int arcLen = -(degrees - 45) * 16;
208  if (arcLen == 0) arcLen = -16;
209 
210  paint.drawArc(indent/2, indent/2,
211  width-indent, width-indent, (180 + 45) * 16, arcLen);
212 
213  paint.setBrush(Qt::NoBrush);
214 
215  // Shadowing...
216 
217  pen.setWidth(scale);
218  paint.setPen(pen);
219 
220  // Knob shadow...
221 
222  int shadowAngle = -720;
223  c = knobColor.dark();
224  for (int arc = 120; arc < 2880; arc += 240) {
225  pen.setColor(c);
226  paint.setPen(pen);
227  paint.drawArc(indent, indent,
228  width-2*indent, width-2*indent, shadowAngle + arc, 240);
229  paint.drawArc(indent, indent,
230  width-2*indent, width-2*indent, shadowAngle - arc, 240);
231  c = c.light(110);
232  }
233 
234  // Scale shadow, omitting the bottom part...
235 
236  shadowAngle = 2160;
237  c = palette().shadow().color();
238  for (int i = 0; i < 5; ++i) {
239  pen.setColor(c);
240  paint.setPen(pen);
241  int arc = i * 240 + 120;
242  paint.drawArc(scale/2, scale/2,
243  width-scale, width-scale, shadowAngle + arc, 240);
244  c = c.light(110);
245  }
246  c = palette().shadow().color();
247  for (int i = 0; i < 12; ++i) {
248  pen.setColor(c);
249  paint.setPen(pen);
250  int arc = i * 240 + 120;
251  paint.drawArc(scale/2, scale/2,
252  width-scale, width-scale, shadowAngle - arc, 240);
253  c = c.light(110);
254  }
255 
256  // Scale ends...
257 
258  pen.setColor(palette().shadow().color());
259  pen.setWidth(scale);
260  paint.setPen(pen);
261  for (int i = 0; i < numTicks; ++i) {
262  if (i != 0 && i != numTicks - 1) continue;
263  int div = numTicks;
264  if (div > 1) --div;
265  drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
266  width, false);
267  }
268 
269  // Pointer notch...
270 
271  float hyp = float(width) / 2.0;
272  float len = hyp - indent;
273  --len;
274 
275  float x0 = hyp;
276  float y0 = hyp;
277 
278  float x = hyp - len * sin(angle);
279  float y = hyp + len * cos(angle);
280 
281  c = palette().dark().color();
282  pen.setColor(isEnabled() ? c.dark(130) : c);
283  pen.setWidth(scale * 2);
284  paint.setPen(pen);
285  paint.drawLine(int(x0), int(y0), int(x), int(y));
286 
287  paint.end();
288 }
289 
290 
291 void AudioDial::drawTick(QPainter &paint,
292  float angle, int size, bool internal)
293 {
294  float hyp = float(size) / 2.0;
295  float x0 = hyp - (hyp - 1) * sin(angle);
296  float y0 = hyp + (hyp - 1) * cos(angle);
297 
298 // cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl;
299 
300  if (internal) {
301 
302  float len = hyp / 4;
303  float x1 = hyp - (hyp - len) * sin(angle);
304  float y1 = hyp + (hyp - len) * cos(angle);
305 
306  paint.drawLine(int(x0), int(y0), int(x1), int(y1));
307 
308  } else {
309 
310  float len = hyp / 4;
311  float x1 = hyp - (hyp + len) * sin(angle);
312  float y1 = hyp + (hyp + len) * cos(angle);
313 
314  paint.drawLine(int(x0), int(y0), int(x1), int(y1));
315  }
316 }
317 
318 
319 void AudioDial::setKnobColor(const QColor& color)
320 {
321  m_knobColor = color;
322  update();
323 }
324 
325 
326 void AudioDial::setMeterColor(const QColor& color)
327 {
328  m_meterColor = color;
329  update();
330 }
331 
332 
333 void AudioDial::setMouseDial(bool mouseDial)
334 {
336 }
337 
338 
339 void AudioDial::setDefaultValue(int defaultValue)
340 {
342  if (m_rangeMapper) {
343  m_defaultMappedValue = m_rangeMapper->getValueForPosition(defaultValue);
344  }
345 }
346 
347 void AudioDial::setValue(int value)
348 {
349  QDial::setValue(value);
350  updateMappedValue(value);
351 }
352 
354 {
355  m_defaultMappedValue = value;
356  if (m_rangeMapper) {
357  m_defaultValue = m_rangeMapper->getPositionForValue(value);
358  }
359 }
360 
361 void AudioDial::setMappedValue(float mappedValue)
362 {
363  if (m_rangeMapper) {
364  int newPosition = m_rangeMapper->getPositionForValue(mappedValue);
365  bool changed = (m_mappedValue != mappedValue);
367  m_noMappedUpdate = true;
368  SVDEBUG << "AudioDial::setMappedValue(" << mappedValue << "): new position is " << newPosition << endl;
369  if (newPosition != value()) {
370  setValue(newPosition);
371  } else if (changed) {
372  emit valueChanged(newPosition);
373  }
374  m_noMappedUpdate = false;
375  } else {
376  setValue(int(mappedValue));
377  }
378 }
379 
380 
382 {
383  m_showTooltip = show;
384  m_noMappedUpdate = true;
385  updateMappedValue(value());
386  m_noMappedUpdate = false;
387 }
388 
389 
391 {
392  if (m_rangeMapper) {
393 // SVDEBUG << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << endl;
394  return m_mappedValue;
395  }
396  return value();
397 }
398 
399 
401 {
402  if (!m_noMappedUpdate) {
403  if (m_rangeMapper) {
404  m_mappedValue = m_rangeMapper->getValueForPosition(value);
405  } else {
406  m_mappedValue = value;
407  }
408  }
409 
410  if (m_showTooltip) {
411  QString name = objectName();
412  QString unit = "";
413  QString text;
414  if (m_rangeMapper) unit = m_rangeMapper->getUnit();
415  if (name != "") {
416  text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit);
417  } else {
418  text = tr("%2%3").arg(m_mappedValue).arg(unit);
419  }
420  setToolTip(text);
421  }
422 }
423 
424 void
426 {
427  if (m_rangeMapper) {
429  return;
430  }
431  int dv = m_defaultValue;
432  if (dv < minimum()) dv = minimum();
433  if (dv > maximum()) dv = maximum();
435 }
436 
437 // Alternate mouse behavior event handlers.
438 void AudioDial::mousePressEvent(QMouseEvent *mouseEvent)
439 {
440  if (m_mouseDial) {
441  QDial::mousePressEvent(mouseEvent);
442  } else if (mouseEvent->button() == Qt::MidButton ||
443  ((mouseEvent->button() == Qt::LeftButton) &&
444  (mouseEvent->modifiers() & Qt::ControlModifier))) {
445  setToDefault();
446  } else if (mouseEvent->button() == Qt::LeftButton) {
447  m_mousePressed = true;
448  m_posMouse = mouseEvent->pos();
449  }
450 }
451 
452 
453 void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
454 {
456 
457  if (m_mouseDial) {
458  QDial::mouseDoubleClickEvent(mouseEvent);
459  } else if (mouseEvent->button() != Qt::LeftButton) {
460  return;
461  }
462 
463  bool ok = false;
464 
465  if (m_rangeMapper) {
466 
467  float min = m_rangeMapper->getValueForPosition(minimum());
468  float max = m_rangeMapper->getValueForPosition(maximum());
469 
470  if (min > max) {
471  float tmp = min;
472  min = max;
473  max = tmp;
474  }
475 
476  QString unit = m_rangeMapper->getUnit();
477 
478  QString text;
479  if (objectName() != "") {
480  if (unit != "") {
481  text = tr("New value for %1, from %2 to %3 %4:")
482  .arg(objectName()).arg(min).arg(max).arg(unit);
483  } else {
484  text = tr("New value for %1, from %2 to %3:")
485  .arg(objectName()).arg(min).arg(max);
486  }
487  } else {
488  if (unit != "") {
489  text = tr("Enter a new value from %1 to %2 %3:")
490  .arg(min).arg(max).arg(unit);
491  } else {
492  text = tr("Enter a new value from %1 to %2:")
493  .arg(min).arg(max);
494  }
495  }
496 
497  float newValue = QInputDialog::getDouble
498  (this,
499  tr("Enter new value"),
500  text,
502  min,
503  max,
504  4,
505  &ok);
506 
507  if (ok) {
508  setMappedValue(newValue);
509  }
510 
511  } else {
512 
513  int newPosition = QInputDialog::getInt
514  (this,
515  tr("Enter new value"),
516  tr("Enter a new value from %1 to %2:")
517  .arg(minimum()).arg(maximum()),
518  value(), minimum(), maximum(), singleStep(), &ok);
519 
520  if (ok) {
521  setValue(newPosition);
522  }
523  }
524 }
525 
526 
527 void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent)
528 {
529  if (m_mouseDial) {
530  QDial::mouseMoveEvent(mouseEvent);
531  } else if (m_mousePressed) {
532  const QPoint& posMouse = mouseEvent->pos();
533  int v = QDial::value()
534  + (posMouse.x() - m_posMouse.x())
535  + (m_posMouse.y() - posMouse.y());
536  if (v > QDial::maximum())
537  v = QDial::maximum();
538  else
539  if (v < QDial::minimum())
540  v = QDial::minimum();
541  m_posMouse = posMouse;
542  QDial::setValue(v);
543  }
544 }
545 
546 
547 void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent)
548 {
549  if (m_mouseDial) {
550  QDial::mouseReleaseEvent(mouseEvent);
551  } else if (m_mousePressed) {
552  m_mousePressed = false;
553  }
554 }
555 
556 void
558 {
559  QDial::enterEvent(e);
560  emit mouseEntered();
561 }
562 
563 void
565 {
566  QDial::enterEvent(e);
567  emit mouseLeft();
568 }
569 
int defaultValue() const
Definition: AudioDial.h:78
#define AUDIO_DIAL_MIN
A rotary dial widget.
Definition: AudioDial.cpp:66
QPoint m_posMouse
Definition: AudioDial.h:143
#define AUDIO_DIAL_MAX
Definition: AudioDial.cpp:67
bool m_mousePressed
Definition: AudioDial.h:142
void setDefaultValue(int defaultValue)
Definition: AudioDial.cpp:339
void setMeterColor(const QColor &color)
Set the colour of the meter (the highlighted area around the knob that shows the current value).
Definition: AudioDial.cpp:326
float mappedValue() const
Definition: AudioDial.cpp:390
void setMappedValue(float mappedValue)
Definition: AudioDial.cpp:361
void setDefaultMappedValue(float mappedValue)
Definition: AudioDial.cpp:353
void setMouseDial(bool mouseDial)
Specify that the dial should respond to radial mouse movements in the same way as QDial.
Definition: AudioDial.cpp:333
QColor meterColor
Definition: AudioDial.h:63
void setToDefault()
Definition: AudioDial.cpp:425
bool m_mouseDial
Definition: AudioDial.h:141
virtual void enterEvent(QEvent *)
Definition: AudioDial.cpp:557
virtual void mouseDoubleClickEvent(QMouseEvent *pMouseEvent)
Definition: AudioDial.cpp:453
bool m_showTooltip
Definition: AudioDial.h:145
#define AUDIO_DIAL_RANGE
Definition: AudioDial.cpp:68
bool mouseDial
Definition: AudioDial.h:64
float m_defaultMappedValue
Definition: AudioDial.h:136
QColor knobColor
Definition: AudioDial.h:62
void setValue(int value)
Definition: AudioDial.cpp:347
void drawTick(QPainter &paint, float angle, int size, bool internal)
Definition: AudioDial.cpp:291
QColor m_knobColor
Definition: AudioDial.h:132
void mouseEntered()
int m_defaultValue
Definition: AudioDial.h:135
RangeMapper * m_rangeMapper
Definition: AudioDial.h:147
AudioDial(QWidget *parent=0)
Definition: AudioDial.cpp:75
void mouseLeft()
void setKnobColor(const QColor &color)
Set the colour of the knob.
Definition: AudioDial.cpp:319
float m_mappedValue
Definition: AudioDial.h:137
void setRangeMapper(RangeMapper *mapper)
Definition: AudioDial.cpp:100
virtual void paintEvent(QPaintEvent *)
Definition: AudioDial.cpp:118
virtual void mouseReleaseEvent(QMouseEvent *pMouseEvent)
Definition: AudioDial.cpp:547
virtual void mousePressEvent(QMouseEvent *pMouseEvent)
Definition: AudioDial.cpp:438
void updateMappedValue(int value)
Definition: AudioDial.cpp:400
virtual void leaveEvent(QEvent *)
Definition: AudioDial.cpp:564
virtual void mouseMoveEvent(QMouseEvent *pMouseEvent)
Definition: AudioDial.cpp:527
QColor m_meterColor
Definition: AudioDial.h:133
bool m_noMappedUpdate
Definition: AudioDial.h:138
void setShowToolTip(bool show)
Definition: AudioDial.cpp:381