comparison widgets/AudioDial.cpp @ 0:fc9323a41f5a

start base : Sonic Visualiser sv1-1.0rc1
author lbajardsilogic
date Fri, 11 May 2007 09:08:14 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:fc9323a41f5a
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 /**
16 * A rotary dial widget.
17 *
18 * Based on an original design by Thorsten Wilms.
19 *
20 * Implemented as a widget for the Rosegarden MIDI and audio sequencer
21 * and notation editor by Chris Cannam.
22 *
23 * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas
24 * and adapted for use in QSynth.
25 *
26 * Ported to Qt4 by Chris Cannam.
27 *
28 * This file copyright 2003-2006 Chris Cannam, copyright 2005 Pedro
29 * Lopez-Cabanillas, copyright 2006 Queen Mary, University of London.
30 *
31 * This program is free software; you can redistribute it and/or
32 * modify it under the terms of the GNU General Public License as
33 * published by the Free Software Foundation; either version 2 of the
34 * License, or (at your option) any later version. See the file
35 * COPYING included with this distribution for more information.
36 */
37
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 using std::endl;
54 using std::cerr;
55
56
57 //!!! Pedro updated his version to use my up/down response code from RG -- need to grab that code in preference to this version from Rui
58
59
60 //-------------------------------------------------------------------------
61 // AudioDial - Instance knob widget class.
62 //
63
64 #define AUDIO_DIAL_MIN (0.25 * M_PI)
65 #define AUDIO_DIAL_MAX (1.75 * M_PI)
66 #define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN)
67
68
69 //static int dialsExtant = 0;
70
71
72 // Constructor.
73 AudioDial::AudioDial(QWidget *parent) :
74 QDial(parent),
75 m_knobColor(Qt::black),
76 m_meterColor(Qt::white),
77 m_defaultValue(0),
78 m_mappedValue(0),
79 m_noMappedUpdate(false),
80 m_showTooltip(true),
81 m_rangeMapper(0)
82 {
83 m_mouseDial = false;
84 m_mousePressed = false;
85 // ++dialsExtant;
86 }
87
88
89 // Destructor.
90 AudioDial::~AudioDial (void)
91 {
92 delete m_rangeMapper;
93 // --dialsExtant;
94 }
95
96
97 void AudioDial::setRangeMapper(RangeMapper *mapper)
98 {
99 // std::cerr << "AudioDial[" << this << "][\"" << objectName().toStdString() << "\"::setRangeMapper(" << mapper << ") [current is " << m_rangeMapper << "] (have " << dialsExtant << " dials extant)" << std::endl;
100
101 if (m_rangeMapper == mapper) return;
102
103 if (!m_rangeMapper && mapper) {
104 connect(this, SIGNAL(valueChanged(int)),
105 this, SLOT(updateMappedValue(int)));
106 }
107
108 delete m_rangeMapper;
109 m_rangeMapper = mapper;
110
111 updateMappedValue(value());
112 }
113
114
115 void AudioDial::paintEvent(QPaintEvent *)
116 {
117 QPainter paint;
118
119 float angle = AUDIO_DIAL_MIN // offset
120 + (AUDIO_DIAL_RANGE *
121 (float(QDial::value() - QDial::minimum()) /
122 (float(QDial::maximum() - QDial::minimum()))));
123 int degrees = int(angle * 180.0 / M_PI);
124
125 int ns = notchSize();
126 int numTicks = 1 + (maximum() + ns - minimum()) / ns;
127
128 QColor knobColor(m_knobColor);
129 if (knobColor == Qt::black)
130 knobColor = palette().background().color();
131
132 QColor meterColor(m_meterColor);
133 if (!isEnabled())
134 meterColor = palette().mid().color();
135 else if (m_meterColor == Qt::white)
136 meterColor = palette().highlight().color();
137
138 int m_size = width() < height() ? width() : height();
139 int scale = 1;
140 int width = m_size - 2*scale;
141
142 paint.begin(this);
143 paint.setRenderHint(QPainter::Antialiasing, true);
144 paint.translate(1, 1);
145
146 QPen pen;
147 QColor c;
148
149 // Knob body and face...
150
151 c = knobColor;
152 pen.setColor(knobColor);
153 pen.setWidth(scale * 2);
154 pen.setCapStyle(Qt::FlatCap);
155
156 paint.setPen(pen);
157 paint.setBrush(c);
158
159 int indent = (int)(width * 0.15 + 1);
160
161 paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent);
162
163 pen.setWidth(3 * scale);
164 int pos = indent-1 + (width-2*indent) / 20;
165 int darkWidth = (width-2*indent) * 3 / 4;
166 while (darkWidth) {
167 c = c.light(102);
168 pen.setColor(c);
169 paint.setPen(pen);
170 paint.drawEllipse(pos, pos, darkWidth, darkWidth);
171 if (!--darkWidth) break;
172 paint.drawEllipse(pos, pos, darkWidth, darkWidth);
173 if (!--darkWidth) break;
174 paint.drawEllipse(pos, pos, darkWidth, darkWidth);
175 ++pos; --darkWidth;
176 }
177
178 // Tick notches...
179
180 if ( notchesVisible() ) {
181 // std::cerr << "Notches visible" << std::endl;
182 pen.setColor(palette().dark().color());
183 pen.setWidth(scale);
184 paint.setPen(pen);
185 for (int i = 0; i < numTicks; ++i) {
186 int div = numTicks;
187 if (div > 1) --div;
188 drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
189 width, true);
190 }
191 }
192
193 // The bright metering bit...
194
195 c = meterColor;
196 pen.setColor(c);
197 pen.setWidth(indent);
198 paint.setPen(pen);
199
200 // std::cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << std::endl;
201
202 int arcLen = -(degrees - 45) * 16;
203 if (arcLen == 0) arcLen = -16;
204
205 paint.drawArc(indent/2, indent/2,
206 width-indent, width-indent, (180 + 45) * 16, arcLen);
207
208 paint.setBrush(Qt::NoBrush);
209
210 // Shadowing...
211
212 pen.setWidth(scale);
213 paint.setPen(pen);
214
215 // Knob shadow...
216
217 int shadowAngle = -720;
218 c = knobColor.dark();
219 for (int arc = 120; arc < 2880; arc += 240) {
220 pen.setColor(c);
221 paint.setPen(pen);
222 paint.drawArc(indent, indent,
223 width-2*indent, width-2*indent, shadowAngle + arc, 240);
224 paint.drawArc(indent, indent,
225 width-2*indent, width-2*indent, shadowAngle - arc, 240);
226 c = c.light(110);
227 }
228
229 // Scale shadow...
230
231 shadowAngle = 2160;
232 c = palette().dark().color();
233 for (int arc = 120; arc < 2880; arc += 240) {
234 pen.setColor(c);
235 paint.setPen(pen);
236 paint.drawArc(scale/2, scale/2,
237 width-scale, width-scale, shadowAngle + arc, 240);
238 paint.drawArc(scale/2, scale/2,
239 width-scale, width-scale, shadowAngle - arc, 240);
240 c = c.light(108);
241 }
242
243 // Undraw the bottom part...
244
245 pen.setColor(palette().background().color());
246 pen.setWidth(scale * 4);
247 paint.setPen(pen);
248 paint.drawArc(scale/2, scale/2,
249 width-scale, width-scale, -45 * 16, -92 * 16);
250
251 // Scale ends...
252
253 pen.setColor(palette().dark().color());
254 pen.setWidth(scale);
255 paint.setPen(pen);
256 for (int i = 0; i < numTicks; ++i) {
257 if (i != 0 && i != numTicks - 1) continue;
258 int div = numTicks;
259 if (div > 1) --div;
260 drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
261 width, false);
262 }
263
264 // Pointer notch...
265
266 float hyp = float(width) / 2.0;
267 float len = hyp - indent;
268 --len;
269
270 float x0 = hyp;
271 float y0 = hyp;
272
273 float x = hyp - len * sin(angle);
274 float y = hyp + len * cos(angle);
275
276 c = palette().dark().color();
277 pen.setColor(isEnabled() ? c.dark(130) : c);
278 pen.setWidth(scale * 2);
279 paint.setPen(pen);
280 paint.drawLine(int(x0), int(y0), int(x), int(y));
281
282 paint.end();
283 }
284
285
286 void AudioDial::drawTick(QPainter &paint,
287 float angle, int size, bool internal)
288 {
289 float hyp = float(size) / 2.0;
290 float x0 = hyp - (hyp - 1) * sin(angle);
291 float y0 = hyp + (hyp - 1) * cos(angle);
292
293 // cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl;
294
295 if (internal) {
296
297 float len = hyp / 4;
298 float x1 = hyp - (hyp - len) * sin(angle);
299 float y1 = hyp + (hyp - len) * cos(angle);
300
301 paint.drawLine(int(x0), int(y0), int(x1), int(y1));
302
303 } else {
304
305 float len = hyp / 4;
306 float x1 = hyp - (hyp + len) * sin(angle);
307 float y1 = hyp + (hyp + len) * cos(angle);
308
309 paint.drawLine(int(x0), int(y0), int(x1), int(y1));
310 }
311 }
312
313
314 void AudioDial::setKnobColor(const QColor& color)
315 {
316 m_knobColor = color;
317 update();
318 }
319
320
321 void AudioDial::setMeterColor(const QColor& color)
322 {
323 m_meterColor = color;
324 update();
325 }
326
327
328 void AudioDial::setMouseDial(bool mouseDial)
329 {
330 m_mouseDial = mouseDial;
331 }
332
333
334 void AudioDial::setDefaultValue(int defaultValue)
335 {
336 m_defaultValue = defaultValue;
337 }
338
339
340 void AudioDial::setValue(int value)
341 {
342 QDial::setValue(value);
343 updateMappedValue(value);
344 }
345
346
347 void AudioDial::setMappedValue(float mappedValue)
348 {
349 if (m_rangeMapper) {
350 int newPosition = m_rangeMapper->getPositionForValue(mappedValue);
351 bool changed = (m_mappedValue != mappedValue);
352 m_mappedValue = mappedValue;
353 m_noMappedUpdate = true;
354 std::cerr << "AudioDial::setMappedValue(" << mappedValue << "): new position is " << newPosition << std::endl;
355 if (newPosition != value()) {
356 setValue(newPosition);
357 } else if (changed) {
358 emit valueChanged(newPosition);
359 }
360 m_noMappedUpdate = false;
361 } else {
362 setValue(int(mappedValue));
363 }
364 }
365
366
367 void AudioDial::setShowToolTip(bool show)
368 {
369 m_showTooltip = show;
370 m_noMappedUpdate = true;
371 updateMappedValue(value());
372 m_noMappedUpdate = false;
373 }
374
375
376 float AudioDial::mappedValue() const
377 {
378 if (m_rangeMapper) {
379 std::cerr << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << std::endl;
380 return m_mappedValue;
381 }
382 return value();
383 }
384
385
386 void AudioDial::updateMappedValue(int value)
387 {
388 if (!m_noMappedUpdate) {
389 if (m_rangeMapper) {
390 m_mappedValue = m_rangeMapper->getValueForPosition(value);
391 } else {
392 m_mappedValue = value;
393 }
394 }
395
396 if (m_showTooltip) {
397 QString name = objectName();
398 QString unit = "";
399 QString text;
400 if (m_rangeMapper) unit = m_rangeMapper->getUnit();
401 if (name != "") {
402 text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit);
403 } else {
404 text = tr("%2%3").arg(m_mappedValue).arg(unit);
405 }
406 setToolTip(text);
407 }
408 }
409
410
411 // Alternate mouse behavior event handlers.
412 void AudioDial::mousePressEvent(QMouseEvent *mouseEvent)
413 {
414 if (m_mouseDial) {
415 QDial::mousePressEvent(mouseEvent);
416 } else if (mouseEvent->button() == Qt::MidButton ||
417 ((mouseEvent->button() == Qt::LeftButton) &&
418 (mouseEvent->modifiers() & Qt::ControlModifier))) {
419 int dv = m_defaultValue;
420 if (dv < minimum()) dv = minimum();
421 if (dv > maximum()) dv = maximum();
422 setValue(m_defaultValue);
423 } else if (mouseEvent->button() == Qt::LeftButton) {
424 m_mousePressed = true;
425 m_posMouse = mouseEvent->pos();
426 }
427 }
428
429
430 void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
431 {
432 //!!! needs a common base class with Thumbwheel
433
434 if (m_mouseDial) {
435 QDial::mouseDoubleClickEvent(mouseEvent);
436 } else if (mouseEvent->button() != Qt::LeftButton) {
437 return;
438 }
439
440 bool ok = false;
441
442 if (m_rangeMapper) {
443
444 float min = m_rangeMapper->getValueForPosition(minimum());
445 float max = m_rangeMapper->getValueForPosition(maximum());
446
447 if (min > max) {
448 float tmp = min;
449 min = max;
450 max = tmp;
451 }
452
453 QString unit = m_rangeMapper->getUnit();
454
455 QString text;
456 if (objectName() != "") {
457 if (unit != "") {
458 text = tr("New value for %1, from %2 to %3 %4:")
459 .arg(objectName()).arg(min).arg(max).arg(unit);
460 } else {
461 text = tr("New value for %1, from %2 to %3:")
462 .arg(objectName()).arg(min).arg(max);
463 }
464 } else {
465 if (unit != "") {
466 text = tr("Enter a new value from %1 to %2 %3:")
467 .arg(min).arg(max).arg(unit);
468 } else {
469 text = tr("Enter a new value from %1 to %2:")
470 .arg(min).arg(max);
471 }
472 }
473
474 float newValue = QInputDialog::getDouble
475 (this,
476 tr("Enter new value"),
477 text,
478 m_mappedValue,
479 min,
480 max,
481 4,
482 &ok);
483
484 if (ok) {
485 setMappedValue(newValue);
486 }
487
488 } else {
489
490 int newPosition = QInputDialog::getInteger
491 (this,
492 tr("Enter new value"),
493 tr("Enter a new value from %1 to %2:")
494 .arg(minimum()).arg(maximum()),
495 value(), minimum(), maximum(), pageStep(), &ok);
496
497 if (ok) {
498 setValue(newPosition);
499 }
500 }
501 }
502
503
504 void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent)
505 {
506 if (m_mouseDial) {
507 QDial::mouseMoveEvent(mouseEvent);
508 } else if (m_mousePressed) {
509 const QPoint& posMouse = mouseEvent->pos();
510 int v = QDial::value()
511 + (posMouse.x() - m_posMouse.x())
512 + (m_posMouse.y() - posMouse.y());
513 if (v > QDial::maximum())
514 v = QDial::maximum();
515 else
516 if (v < QDial::minimum())
517 v = QDial::minimum();
518 m_posMouse = posMouse;
519 QDial::setValue(v);
520 }
521 }
522
523
524 void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent)
525 {
526 if (m_mouseDial) {
527 QDial::mouseReleaseEvent(mouseEvent);
528 } else if (m_mousePressed) {
529 m_mousePressed = false;
530 }
531 }
532
533 void
534 AudioDial::enterEvent(QEvent *e)
535 {
536 QDial::enterEvent(e);
537 emit mouseEntered();
538 }
539
540 void
541 AudioDial::leaveEvent(QEvent *e)
542 {
543 QDial::enterEvent(e);
544 emit mouseLeft();
545 }
546