| Chris@0 | 1 /* -*- c-basic-offset: 4 -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@0 | 2 | 
| Chris@0 | 3 /* | 
| Chris@0 | 4   A waveform viewer and audio annotation editor. | 
| Chris@5 | 5   Chris Cannam, Queen Mary University of London, 2005-2006 | 
| Chris@0 | 6 | 
| Chris@0 | 7   This is experimental software.  Not for distribution. | 
| Chris@0 | 8 */ | 
| Chris@0 | 9 | 
| Chris@0 | 10 /** | 
| Chris@0 | 11  * A rotary dial widget. | 
| Chris@0 | 12  * | 
| Chris@0 | 13  * Based on an original design by Thorsten Wilms. | 
| Chris@0 | 14  * | 
| Chris@0 | 15  * Implemented as a widget for the Rosegarden MIDI and audio sequencer | 
| Chris@0 | 16  * and notation editor by Chris Cannam. | 
| Chris@0 | 17  * | 
| Chris@0 | 18  * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas | 
| Chris@0 | 19  * and adapted for use in QSynth. | 
| Chris@0 | 20  * | 
| Chris@0 | 21  * Ported to Qt4 by Chris Cannam. | 
| Chris@0 | 22  * | 
| Chris@0 | 23  * This file copyright 2003-2005 Chris Cannam, copyright 2005 Pedro | 
| Chris@0 | 24  * Lopez-Cabanillas. | 
| Chris@0 | 25  * | 
| Chris@0 | 26  * This program is free software; you can redistribute it and/or | 
| Chris@0 | 27  * modify it under the terms of the GNU General Public License as | 
| Chris@0 | 28  * published by the Free Software Foundation; either version 2 of the | 
| Chris@0 | 29  * License, or (at your option) any later version.  See the file | 
| Chris@0 | 30  * COPYING included with this distribution for more information. | 
| Chris@0 | 31  */ | 
| Chris@0 | 32 | 
| Chris@0 | 33 #include "AudioDial.h" | 
| Chris@0 | 34 | 
| Chris@0 | 35 #include <cmath> | 
| Chris@0 | 36 #include <iostream> | 
| Chris@0 | 37 | 
| Chris@0 | 38 #include <QTimer> | 
| Chris@0 | 39 #include <QPainter> | 
| Chris@0 | 40 #include <QPixmap> | 
| Chris@0 | 41 #include <QColormap> | 
| Chris@0 | 42 #include <QMouseEvent> | 
| Chris@0 | 43 #include <QPaintEvent> | 
| Chris@0 | 44 | 
| Chris@0 | 45 using std::endl; | 
| Chris@0 | 46 using std::cerr; | 
| Chris@0 | 47 | 
| Chris@0 | 48 | 
| Chris@0 | 49 //!!! 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 | 50 | 
| Chris@0 | 51 | 
| Chris@0 | 52 //------------------------------------------------------------------------- | 
| Chris@0 | 53 // AudioDial - Instance knob widget class. | 
| Chris@0 | 54 // | 
| Chris@0 | 55 | 
| Chris@0 | 56 #define AUDIO_DIAL_MIN (0.25 * M_PI) | 
| Chris@0 | 57 #define AUDIO_DIAL_MAX (1.75 * M_PI) | 
| Chris@0 | 58 #define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) | 
| Chris@0 | 59 | 
| Chris@0 | 60 | 
| Chris@0 | 61 // Constructor. | 
| Chris@0 | 62 AudioDial::AudioDial(QWidget *parent) : | 
| Chris@0 | 63     QDial(parent), | 
| Chris@0 | 64     m_knobColor(Qt::black), m_meterColor(Qt::white) | 
| Chris@0 | 65 { | 
| Chris@0 | 66     m_mouseDial = false; | 
| Chris@0 | 67     m_mousePressed = false; | 
| Chris@0 | 68 } | 
| Chris@0 | 69 | 
| Chris@0 | 70 | 
| Chris@0 | 71 // Destructor. | 
| Chris@0 | 72 AudioDial::~AudioDial (void) | 
| Chris@0 | 73 { | 
| Chris@0 | 74 } | 
| Chris@0 | 75 | 
| Chris@0 | 76 | 
| Chris@0 | 77 void AudioDial::paintEvent(QPaintEvent *) | 
| Chris@0 | 78 { | 
| Chris@0 | 79     QPainter paint; | 
| Chris@0 | 80 | 
| Chris@0 | 81     float angle = AUDIO_DIAL_MIN // offset | 
| Chris@0 | 82 	+ (AUDIO_DIAL_RANGE * | 
| Chris@0 | 83 	   (float(QDial::value() - QDial::minimum()) / | 
| Chris@0 | 84 	    (float(QDial::maximum() - QDial::minimum())))); | 
| Chris@0 | 85     int degrees = int(angle * 180.0 / M_PI); | 
| Chris@0 | 86 | 
| Chris@0 | 87     int ns = notchSize(); | 
| Chris@0 | 88     int numTicks = 1 + (maximum() + ns - minimum()) / ns; | 
| Chris@0 | 89 | 
| Chris@0 | 90     QColor knobColor(m_knobColor); | 
| Chris@0 | 91     if (knobColor == Qt::black) | 
| Chris@0 | 92 	knobColor = palette().mid().color(); | 
| Chris@0 | 93 | 
| Chris@0 | 94     QColor meterColor(m_meterColor); | 
| Chris@0 | 95     if (!isEnabled()) | 
| Chris@0 | 96 	meterColor = palette().mid().color(); | 
| Chris@0 | 97     else if (m_meterColor == Qt::white) | 
| Chris@0 | 98 	meterColor = palette().highlight().color(); | 
| Chris@0 | 99 | 
| Chris@0 | 100     int m_size = width() < height() ? width() : height(); | 
| Chris@0 | 101     int scale = 1; | 
| Chris@0 | 102     int width = m_size - 2*scale, height = m_size - 2*scale; | 
| Chris@0 | 103 | 
| Chris@0 | 104     paint.begin(this); | 
| Chris@0 | 105     paint.setRenderHint(QPainter::Antialiasing, true); | 
| Chris@0 | 106     paint.translate(1, 1); | 
| Chris@0 | 107 | 
| Chris@0 | 108     QPen pen; | 
| Chris@0 | 109     QColor c; | 
| Chris@0 | 110 | 
| Chris@0 | 111     // Knob body and face... | 
| Chris@0 | 112 | 
| Chris@0 | 113     c = knobColor; | 
| Chris@0 | 114     pen.setColor(knobColor); | 
| Chris@0 | 115     pen.setWidth(scale * 2); | 
| Chris@0 | 116     pen.setCapStyle(Qt::FlatCap); | 
| Chris@0 | 117 | 
| Chris@0 | 118     paint.setPen(pen); | 
| Chris@0 | 119     paint.setBrush(c); | 
| Chris@0 | 120 | 
| Chris@0 | 121     int indent = (int)(width * 0.15 + 1); | 
| Chris@0 | 122 | 
| Chris@0 | 123     paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent); | 
| Chris@0 | 124 | 
| Chris@0 | 125     pen.setWidth(3 * scale); | 
| Chris@0 | 126     int pos = indent-1 + (width-2*indent) / 20; | 
| Chris@0 | 127     int darkWidth = (width-2*indent) * 3 / 4; | 
| Chris@0 | 128     while (darkWidth) { | 
| Chris@0 | 129 	c = c.light(102); | 
| Chris@0 | 130 	pen.setColor(c); | 
| Chris@0 | 131 	paint.setPen(pen); | 
| Chris@0 | 132 	paint.drawEllipse(pos, pos, darkWidth, darkWidth); | 
| Chris@0 | 133 	if (!--darkWidth) break; | 
| Chris@0 | 134 	paint.drawEllipse(pos, pos, darkWidth, darkWidth); | 
| Chris@0 | 135 	if (!--darkWidth) break; | 
| Chris@0 | 136 	paint.drawEllipse(pos, pos, darkWidth, darkWidth); | 
| Chris@0 | 137 	++pos; --darkWidth; | 
| Chris@0 | 138     } | 
| Chris@0 | 139 | 
| Chris@0 | 140     // Tick notches... | 
| Chris@0 | 141 | 
| Chris@0 | 142     if ( true/* notchesVisible() */) { | 
| Chris@0 | 143 //	std::cerr << "Notches visible" << std::endl; | 
| Chris@0 | 144 	pen.setColor(palette().dark().color()); | 
| Chris@0 | 145 	pen.setWidth(scale); | 
| Chris@0 | 146 	paint.setPen(pen); | 
| Chris@0 | 147 	for (int i = 0; i < numTicks; ++i) { | 
| Chris@0 | 148 	    int div = numTicks; | 
| Chris@0 | 149 	    if (div > 1) --div; | 
| Chris@0 | 150 	    drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, | 
| Chris@0 | 151 		     width, true); | 
| Chris@0 | 152 	} | 
| Chris@0 | 153     } | 
| Chris@0 | 154 | 
| Chris@0 | 155     // The bright metering bit... | 
| Chris@0 | 156 | 
| Chris@0 | 157     c = meterColor; | 
| Chris@0 | 158     pen.setColor(c); | 
| Chris@0 | 159     pen.setWidth(indent); | 
| Chris@0 | 160     paint.setPen(pen); | 
| Chris@0 | 161 | 
| Chris@0 | 162 //    std::cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << std::endl; | 
| Chris@0 | 163 | 
| Chris@0 | 164     int arcLen = -(degrees - 45) * 16; | 
| Chris@0 | 165     if (arcLen == 0) arcLen = -16; | 
| Chris@0 | 166 | 
| Chris@0 | 167     paint.drawArc(indent/2, indent/2, | 
| Chris@0 | 168 		  width-indent, width-indent, (180 + 45) * 16, arcLen); | 
| Chris@0 | 169 | 
| Chris@0 | 170     paint.setBrush(Qt::NoBrush); | 
| Chris@0 | 171 | 
| Chris@0 | 172     // Shadowing... | 
| Chris@0 | 173 | 
| Chris@0 | 174     pen.setWidth(scale); | 
| Chris@0 | 175     paint.setPen(pen); | 
| Chris@0 | 176 | 
| Chris@0 | 177     // Knob shadow... | 
| Chris@0 | 178 | 
| Chris@0 | 179     int shadowAngle = -720; | 
| Chris@0 | 180     c = knobColor.dark(); | 
| Chris@0 | 181     for (int arc = 120; arc < 2880; arc += 240) { | 
| Chris@0 | 182 	pen.setColor(c); | 
| Chris@0 | 183 	paint.setPen(pen); | 
| Chris@0 | 184 	paint.drawArc(indent, indent, | 
| Chris@0 | 185 		      width-2*indent, width-2*indent, shadowAngle + arc, 240); | 
| Chris@0 | 186 	paint.drawArc(indent, indent, | 
| Chris@0 | 187 		      width-2*indent, width-2*indent, shadowAngle - arc, 240); | 
| Chris@0 | 188 	c = c.light(110); | 
| Chris@0 | 189     } | 
| Chris@0 | 190 | 
| Chris@0 | 191     // Scale shadow... | 
| Chris@0 | 192 | 
| Chris@0 | 193     shadowAngle = 2160; | 
| Chris@0 | 194     c = palette().dark().color(); | 
| Chris@0 | 195     for (int arc = 120; arc < 2880; arc += 240) { | 
| Chris@0 | 196 	pen.setColor(c); | 
| Chris@0 | 197 	paint.setPen(pen); | 
| Chris@0 | 198 	paint.drawArc(scale/2, scale/2, | 
| Chris@0 | 199 		      width-scale, width-scale, shadowAngle + arc, 240); | 
| Chris@0 | 200 	paint.drawArc(scale/2, scale/2, | 
| Chris@0 | 201 		      width-scale, width-scale, shadowAngle - arc, 240); | 
| Chris@0 | 202 	c = c.light(108); | 
| Chris@0 | 203     } | 
| Chris@0 | 204 | 
| Chris@0 | 205     // Undraw the bottom part... | 
| Chris@0 | 206 | 
| Chris@0 | 207     pen.setColor(palette().background().color()); | 
| Chris@0 | 208     pen.setWidth(scale * 4); | 
| Chris@0 | 209     paint.setPen(pen); | 
| Chris@0 | 210     paint.drawArc(scale/2, scale/2, | 
| Chris@0 | 211 		  width-scale, width-scale, -45 * 16, -92 * 16); | 
| Chris@0 | 212 | 
| Chris@0 | 213     // Scale ends... | 
| Chris@0 | 214 | 
| Chris@0 | 215     pen.setColor(palette().dark().color()); | 
| Chris@0 | 216     pen.setWidth(scale); | 
| Chris@0 | 217     paint.setPen(pen); | 
| Chris@0 | 218     for (int i = 0; i < numTicks; ++i) { | 
| Chris@0 | 219 	if (i != 0 && i != numTicks - 1) continue; | 
| Chris@0 | 220 	int div = numTicks; | 
| Chris@0 | 221 	if (div > 1) --div; | 
| Chris@0 | 222 	drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, | 
| Chris@0 | 223 		 width, false); | 
| Chris@0 | 224     } | 
| Chris@0 | 225 | 
| Chris@0 | 226     // Pointer notch... | 
| Chris@0 | 227 | 
| Chris@0 | 228     float hyp = float(width) / 2.0; | 
| Chris@0 | 229     float len = hyp - indent; | 
| Chris@0 | 230     --len; | 
| Chris@0 | 231 | 
| Chris@0 | 232     float x0 = hyp; | 
| Chris@0 | 233     float y0 = hyp; | 
| Chris@0 | 234 | 
| Chris@0 | 235     float x = hyp - len * sin(angle); | 
| Chris@0 | 236     float y = hyp + len * cos(angle); | 
| Chris@0 | 237 | 
| Chris@0 | 238     c = palette().dark().color(); | 
| Chris@0 | 239     pen.setColor(isEnabled() ? c.dark(130) : c); | 
| Chris@0 | 240     pen.setWidth(scale * 2); | 
| Chris@0 | 241     paint.setPen(pen); | 
| Chris@0 | 242     paint.drawLine(int(x0), int(y0), int(x), int(y)); | 
| Chris@0 | 243 | 
| Chris@0 | 244     paint.end(); | 
| Chris@0 | 245 } | 
| Chris@0 | 246 | 
| Chris@0 | 247 | 
| Chris@0 | 248 void AudioDial::drawTick(QPainter &paint, | 
| Chris@0 | 249 			 float angle, int size, bool internal) | 
| Chris@0 | 250 { | 
| Chris@0 | 251     float hyp = float(size) / 2.0; | 
| Chris@0 | 252     float x0 = hyp - (hyp - 1) * sin(angle); | 
| Chris@0 | 253     float y0 = hyp + (hyp - 1) * cos(angle); | 
| Chris@0 | 254 | 
| Chris@0 | 255 //    cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl; | 
| Chris@0 | 256 | 
| Chris@0 | 257     if (internal) { | 
| Chris@0 | 258 | 
| Chris@0 | 259 	float len = hyp / 4; | 
| Chris@0 | 260 	float x1 = hyp - (hyp - len) * sin(angle); | 
| Chris@0 | 261 	float y1 = hyp + (hyp - len) * cos(angle); | 
| Chris@0 | 262 | 
| Chris@0 | 263 	paint.drawLine(int(x0), int(y0), int(x1), int(y1)); | 
| Chris@0 | 264 | 
| Chris@0 | 265     } else { | 
| Chris@0 | 266 | 
| Chris@0 | 267 	float len = hyp / 4; | 
| Chris@0 | 268 	float x1 = hyp - (hyp + len) * sin(angle); | 
| Chris@0 | 269 	float y1 = hyp + (hyp + len) * cos(angle); | 
| Chris@0 | 270 | 
| Chris@0 | 271 	paint.drawLine(int(x0), int(y0), int(x1), int(y1)); | 
| Chris@0 | 272     } | 
| Chris@0 | 273 } | 
| Chris@0 | 274 | 
| Chris@0 | 275 | 
| Chris@0 | 276 void AudioDial::setKnobColor(const QColor& color) | 
| Chris@0 | 277 { | 
| Chris@0 | 278     m_knobColor = color; | 
| Chris@0 | 279     update(); | 
| Chris@0 | 280 } | 
| Chris@0 | 281 | 
| Chris@0 | 282 | 
| Chris@0 | 283 void AudioDial::setMeterColor(const QColor& color) | 
| Chris@0 | 284 { | 
| Chris@0 | 285     m_meterColor = color; | 
| Chris@0 | 286     update(); | 
| Chris@0 | 287 } | 
| Chris@0 | 288 | 
| Chris@0 | 289 | 
| Chris@0 | 290 void AudioDial::setMouseDial(bool mouseDial) | 
| Chris@0 | 291 { | 
| Chris@0 | 292     m_mouseDial = mouseDial; | 
| Chris@0 | 293 } | 
| Chris@0 | 294 | 
| Chris@0 | 295 | 
| Chris@0 | 296 // Alternate mouse behavior event handlers. | 
| Chris@0 | 297 void AudioDial::mousePressEvent(QMouseEvent *mouseEvent) | 
| Chris@0 | 298 { | 
| Chris@0 | 299     if (m_mouseDial) { | 
| Chris@0 | 300 	QDial::mousePressEvent(mouseEvent); | 
| Chris@0 | 301     } else if (mouseEvent->button() == Qt::LeftButton) { | 
| Chris@0 | 302 	m_mousePressed = true; | 
| Chris@0 | 303 	m_posMouse = mouseEvent->pos(); | 
| Chris@0 | 304     } | 
| Chris@0 | 305 } | 
| Chris@0 | 306 | 
| Chris@0 | 307 | 
| Chris@0 | 308 void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent) | 
| Chris@0 | 309 { | 
| Chris@0 | 310     if (m_mouseDial) { | 
| Chris@0 | 311 	QDial::mouseMoveEvent(mouseEvent); | 
| Chris@0 | 312     } else if (m_mousePressed) { | 
| Chris@0 | 313 	const QPoint& posMouse = mouseEvent->pos(); | 
| Chris@0 | 314 	int v = QDial::value() | 
| Chris@0 | 315 	    + (posMouse.x() - m_posMouse.x()) | 
| Chris@0 | 316 	    + (m_posMouse.y() - posMouse.y()); | 
| Chris@0 | 317 	if (v > QDial::maximum()) | 
| Chris@0 | 318 	    v = QDial::maximum(); | 
| Chris@0 | 319 	else | 
| Chris@0 | 320 	    if (v < QDial::minimum()) | 
| Chris@0 | 321 		v = QDial::minimum(); | 
| Chris@0 | 322 	m_posMouse = posMouse; | 
| Chris@0 | 323 	QDial::setValue(v); | 
| Chris@0 | 324     } | 
| Chris@0 | 325 } | 
| Chris@0 | 326 | 
| Chris@0 | 327 | 
| Chris@0 | 328 void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent) | 
| Chris@0 | 329 { | 
| Chris@0 | 330     if (m_mouseDial) { | 
| Chris@0 | 331 	QDial::mouseReleaseEvent(mouseEvent); | 
| Chris@0 | 332     } else if (m_mousePressed) { | 
| Chris@0 | 333 	m_mousePressed = false; | 
| Chris@0 | 334     } | 
| Chris@0 | 335 } | 
| Chris@0 | 336 | 
| Chris@0 | 337 #ifdef INCLUDE_MOCFILES | 
| Chris@0 | 338 #include "AudioDial.moc.cpp" | 
| Chris@0 | 339 #endif | 
| Chris@0 | 340 |