annotate widgets/AudioDial.cpp @ 183:5f86ae638b04

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