/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rosegarden A MIDI and audio sequencer and musical notation editor. Copyright 2000-2011 the Rosegarden development team. Other copyrights also apply to some parts of this work. Please see the AUTHORS file and individual file headers for details. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "Rotary.h" #include "misc/Debug.h" #include "base/Profiler.h" #include "gui/dialogs/FloatEdit.h" #include "gui/general/GUIPalette.h" #include "TextFloat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Rosegarden { #define ROTARY_MIN (0.25 * M_PI) #define ROTARY_MAX (1.75 * M_PI) #define ROTARY_RANGE (ROTARY_MAX - ROTARY_MIN) Rotary::PixmapCache Rotary::m_pixmaps; Rotary::Rotary(QWidget *parent, float minimum, float maximum, float step, float pageStep, float initialPosition, int size, TickMode ticks, bool snapToTicks, bool centred, bool logarithmic) : QWidget(parent), m_minimum(minimum), m_maximum(maximum), m_step(step), m_pageStep(pageStep), m_size(size), m_tickMode(ticks), m_snapToTicks(snapToTicks), m_centred(centred), m_logarithmic(logarithmic), m_position(initialPosition), m_snapPosition(m_position), m_initialPosition(initialPosition), m_buttonPressed(false), m_lastY(0), m_lastX(0), m_knobColour(0, 0, 0) { setObjectName("RotaryWidget"); setBackgroundMode(Qt::NoBackground); this->setToolTip(tr("

Click and drag up and down or left and right to modify.

Double click to edit value directly.

")); setFixedSize(size, size); emit valueChanged(m_snapPosition); } Rotary::~Rotary() { } void Rotary::setKnobColour(const QColor &colour) { m_knobColour = colour; repaint(); } void Rotary::paintEvent(QPaintEvent *) { Profiler profiler("Rotary::paintEvent"); //!!! This should be pulled from GUIPalette eventually. We're no longer // relying on Qt to come up with dark and mid and highlight and whatnot // colors, because they're getting inverted to a far lighter color than is // appropriate, even against my stock lightish slate blue default KDE // background, let alone the new darker scheme we're imposing through the // stylesheet. // same color as slider grooves and VU meter backgrounds // const QColor Dark = QColor(0x20, 0x20, 0x20); const QColor Dark = QColor(0x10, 0x10, 0x10); // this is the undefined color state for a knob, which probably indicates // some issue with our internal color hanlding. It looks like this was a // hack to try to get around black knobs related to a bug setting up the // color index when making new knobs in the studio controller editor. We'll // just make these a really obvious ah hah color then: const QColor Base = Qt::white; // the knob pointer should be a sharp, high contrast color const QColor Pointer = Qt::black; // tick marks should contrast against Dark and Base const QColor Ticks = QColor(0xAA, 0xAA, 0xAA); QPainter paint; double angle = ROTARY_MIN // offset + (ROTARY_RANGE * (double(m_snapPosition - m_minimum) / (double(m_maximum) - double(m_minimum)))); int degrees = int(angle * 180.0 / M_PI); // RG_DEBUG << "degrees: " << degrees << ", size " << m_size << ", pixel " << m_knobColour.pixel() << endl; int numTicks = 0; switch (m_tickMode) { case LimitTicks: numTicks = 2; break; case IntervalTicks: numTicks = 5; break; case PageStepTicks: numTicks = 1 + (m_maximum + 0.0001 - m_minimum) / m_pageStep; break; case StepTicks: numTicks = 1 + (m_maximum + 0.0001 - m_minimum) / m_step; break; default: break; } CacheIndex index(m_size, m_knobColour.pixel(), degrees, numTicks, m_centred); if (m_pixmaps.find(index) != m_pixmaps.end()) { paint.begin(this); paint.drawPixmap(0, 0, m_pixmaps[index]); paint.end(); return ; } int scale = 4; int width = m_size * scale; QPixmap map(width, width); map.fill(paletteBackgroundColor()); paint.begin(&map); QPen pen; pen.setColor(Dark); pen.setWidth(scale); paint.setPen(pen); if (m_knobColour != QColor(Qt::black)) { paint.setBrush(m_knobColour); } else { paint.setBrush(Base); } QColor c(m_knobColour); pen.setColor(c); paint.setPen(pen); int indent = width * 0.15 + 1; // draw a base knob color circle paint.drawEllipse(indent, indent, width - 2*indent, width - 2*indent); // draw a highlight computed from the knob color pen.setWidth(2 * scale); int pos = indent + (width - 2 * indent) / 8; int darkWidth = (width - 2 * indent) * 2 / 3; while (darkWidth) { c = c.light(101); pen.setColor(c); paint.setPen(pen); paint.drawEllipse(pos, pos, darkWidth, darkWidth); if (!--darkWidth) break; paint.drawEllipse(pos, pos, darkWidth, darkWidth); if (!--darkWidth) break; paint.drawEllipse(pos, pos, darkWidth, darkWidth); ++pos; --darkWidth; } paint.setBrush(Qt::NoBrush); // draw the tick marks on larger sized knobs pen.setColor(Ticks); pen.setWidth(scale); paint.setPen(pen); for (int i = 0; i < numTicks; ++i) { int div = numTicks; if (div > 1) --div; drawTick(paint, ROTARY_MIN + (ROTARY_MAX - ROTARY_MIN) * i / div, width, i != 0 && i != numTicks - 1); } // draw the bright metering bit pen.setColor(GUIPalette::getColour(GUIPalette::RotaryMeter)); pen.setWidth(indent); paint.setPen(pen); if (m_centred) { paint.drawArc(indent / 2, indent / 2, width - indent, width - indent, 90 * 16, -(degrees - 180) * 16); } else { paint.drawArc(indent / 2, indent / 2, width - indent, width - indent, (180 + 45) * 16, -(degrees - 45) * 16); } pen.setWidth(scale); paint.setPen(pen); // draw a dark circle to outline the knob int shadowAngle = -720; c = Dark; for (int arc = 120; arc < 2880; arc += 240) { pen.setColor(c); paint.setPen(pen); paint.drawArc(indent, indent, width - 2*indent, width - 2*indent, shadowAngle + arc, 240); paint.drawArc(indent, indent, width - 2*indent, width - 2*indent, shadowAngle - arc, 240); c = c.lighter(110); } // draw a computed trough thingie all the way around the knob shadowAngle = 2160; c = Dark; for (int arc = 120; arc < 2880; arc += 240) { pen.setColor(c); paint.setPen(pen); paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, shadowAngle + arc, 240); paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, shadowAngle - arc, 240); c = c.lighter(109); } // and un-draw the bottom part of the arc pen.setColor(paletteBackgroundColor()); paint.setPen(pen); paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, -45 * 16, -90 * 16); // calculate and draw the pointer double hyp = double(width) / 2.0; double len = hyp - indent; --len; double x0 = hyp; double y0 = hyp; double x = hyp - len * sin(angle); double y = hyp + len * cos(angle); pen.setWidth(scale * 2); pen.setColor(Pointer); paint.setPen(pen); paint.drawLine(int(x0), int(y0), int(x), int(y)); paint.end(); QImage i = map.convertToImage().smoothScale(m_size, m_size); m_pixmaps[index] = QPixmap::fromImage(i); paint.begin(this); paint.drawPixmap(0, 0, m_pixmaps[index]); paint.end(); } void Rotary::drawTick(QPainter &paint, double angle, int size, bool internal) { double hyp = double(size) / 2.0; double x0 = hyp - (hyp - 1) * sin(angle); double y0 = hyp + (hyp - 1) * cos(angle); if (internal) { double len = hyp / 4; double x1 = hyp - (hyp - len) * sin(angle); double y1 = hyp + (hyp - len) * cos(angle); paint.drawLine(int(x0), int(y0), int(x1), int(y1)); } else { double len = hyp / 4; double x1 = hyp - (hyp + len) * sin(angle); double y1 = hyp + (hyp + len) * cos(angle); paint.drawLine(int(x0), int(y0), int(x1), int(y1)); } } void Rotary::snapPosition() { m_snapPosition = m_position; if (m_snapToTicks) { switch (m_tickMode) { case NoTicks: break; // meaningless case LimitTicks: if (m_position < (m_minimum + m_maximum) / 2.0) { m_snapPosition = m_minimum; } else { m_snapPosition = m_maximum; } break; case IntervalTicks: m_snapPosition = m_minimum + (m_maximum - m_minimum) / 4.0 * int((m_snapPosition - m_minimum) / ((m_maximum - m_minimum) / 4.0)); break; case PageStepTicks: m_snapPosition = m_minimum + m_pageStep * int((m_snapPosition - m_minimum) / m_pageStep); break; case StepTicks: m_snapPosition = m_minimum + m_step * int((m_snapPosition - m_minimum) / m_step); break; } } } void Rotary::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_buttonPressed = true; m_lastY = e->y(); m_lastX = e->x(); } else if (e->button() == Qt::MidButton) // reset to default { m_position = m_initialPosition; snapPosition(); update(); emit valueChanged(m_snapPosition); } else if (e->button() == Qt::RightButton) // reset to centre position { m_position = (m_maximum + m_minimum) / 2.0; snapPosition(); update(); emit valueChanged(m_snapPosition); } TextFloat *textFloat = TextFloat::getTextFloat(); if (m_logarithmic) { textFloat->setText(QString("%1").arg(powf(10, m_position))); } else { textFloat->setText(QString("%1").arg(m_position)); } QPoint offset = QPoint(width() + width() / 5, height() / 5); textFloat->display(offset); // std::cerr << "Rotary::mousePressEvent: logarithmic = " << m_logarithmic // << ", position = " << m_position << std::endl; if (e->button() == Qt::RightButton || e->button() == Qt::MidButton) { // wait 500ms then hide text float textFloat->hideAfterDelay(500); } } void Rotary::mouseDoubleClickEvent(QMouseEvent * /*e*/) { float minv = m_minimum; float maxv = m_maximum; float val = m_position; float step = m_step; if (m_logarithmic) { minv = powf(10, minv); maxv = powf(10, maxv); val = powf(10, val); step = powf(10, step); if (step > 0.001) step = 0.001; } FloatEdit dialog(this, tr("Select a new value"), tr("Enter a new value"), minv, maxv, val, step); // Reposition - we need to sum the relative positions up to the // topLevel or dialog to please move(). Move just top/right of the rotary // // (Copied from the text float moving code Yves fixed up.) // dialog.reparent(this); QWidget *par = parentWidget(); QPoint totalPos = this->pos(); while (par->parentWidget() && !par->isWindow()) { par = par->parentWidget(); totalPos += par->pos(); } dialog.move(totalPos + QPoint(width() + 2, -height() / 2)); dialog.show(); if (dialog.exec() == QDialog::Accepted) { float newval = dialog.getValue(); if (m_logarithmic) { if (m_position < powf(10, -10)) m_position = -10; else m_position = log10f(newval); } else { m_position = newval; } snapPosition(); update(); emit valueChanged(m_snapPosition); } } void Rotary::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { m_buttonPressed = false; m_lastY = 0; m_lastX = 0; // Hide the float text // TextFloat::getTextFloat()->hideAfterDelay(500); } } void Rotary::mouseMoveEvent(QMouseEvent *e) { if (m_buttonPressed) { // Dragging by x or y axis when clicked modifies value // float newValue = m_position + (m_lastY - float(e->y()) + float(e->x()) - m_lastX) * m_step; if (newValue > m_maximum) m_position = m_maximum; else if (newValue < m_minimum) m_position = m_minimum; else m_position = newValue; m_lastY = e->y(); m_lastX = e->x(); snapPosition(); // don't update if there's nothing to update // if (m_lastPosition == m_snapPosition) return; update(); emit valueChanged(m_snapPosition); // draw on the float text TextFloat *textFloat = TextFloat::getTextFloat(); if (m_logarithmic) { textFloat->setText(QString("%1").arg(powf(10, m_snapPosition))); } else { textFloat->setText(QString("%1").arg(m_snapPosition)); } } } void Rotary::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) m_position -= m_pageStep; else m_position += m_pageStep; if (m_position > m_maximum) m_position = m_maximum; if (m_position < m_minimum) m_position = m_minimum; snapPosition(); update(); TextFloat *textFloat = TextFloat::getTextFloat(); // draw on the float text if (m_logarithmic) { textFloat->setText(QString("%1").arg(powf(10, m_snapPosition))); } else { textFloat->setText(QString("%1").arg(m_snapPosition)); } // Move just top/right of the rotary QPoint offset = QPoint(width() + width() / 5, height() / 5); textFloat->display(offset); // Keep text float visible for 500ms textFloat->hideAfterDelay(500); emit valueChanged(m_snapPosition); } void Rotary::enterEvent(QEvent *) { TextFloat::getTextFloat()->attach(this); } void Rotary::setPosition(float position) { m_position = position; snapPosition(); update(); } } #include "Rotary.moc"