Chris@132: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@132: Chris@132: /* Chris@132: Sonic Visualiser Chris@132: An audio file viewer and annotation editor. Chris@132: Centre for Digital Music, Queen Mary, University of London. Chris@182: This file copyright 2006 QMUL. Chris@132: Chris@132: This program is free software; you can redistribute it and/or Chris@132: modify it under the terms of the GNU General Public License as Chris@132: published by the Free Software Foundation; either version 2 of the Chris@132: License, or (at your option) any later version. See the file Chris@132: COPYING included with this distribution for more information. Chris@132: */ Chris@132: Chris@132: #include "Thumbwheel.h" Chris@132: Chris@187: #include "base/RangeMapper.h" Chris@190: #include "base/Profiler.h" Chris@187: Chris@132: #include Chris@132: #include Chris@132: #include Chris@187: #include Chris@132: #include Chris@190: #include Chris@1586: #include Chris@1586: Chris@1586: #include "MenuTitle.h" Chris@132: Chris@132: #include Chris@132: #include Chris@132: Chris@133: Thumbwheel::Thumbwheel(Qt::Orientation orientation, Chris@1266: QWidget *parent) : Chris@132: QWidget(parent), Chris@133: m_min(0), Chris@133: m_max(100), Chris@133: m_default(50), Chris@133: m_value(50), Chris@187: m_mappedValue(50), Chris@187: m_noMappedUpdate(false), Chris@165: m_rotation(0.5), Chris@132: m_orientation(orientation), Chris@165: m_speed(1.0), Chris@132: m_tracking(true), Chris@132: m_showScale(true), Chris@132: m_clicked(false), Chris@133: m_atDefault(true), Chris@187: m_clickRotation(m_rotation), Chris@187: m_showTooltip(true), Chris@1586: m_provideContextMenu(true), Chris@1586: m_lastContextMenu(nullptr), Chris@1408: m_rangeMapper(nullptr) Chris@132: { Chris@1586: setContextMenuPolicy(Qt::CustomContextMenu); Chris@1586: connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), Chris@1586: this, SLOT(contextMenuRequested(const QPoint &))); Chris@132: } Chris@132: Chris@132: Thumbwheel::~Thumbwheel() Chris@132: { Chris@187: delete m_rangeMapper; Chris@1586: delete m_lastContextMenu; Chris@1586: } Chris@1586: Chris@1586: void Chris@1586: Thumbwheel::contextMenuRequested(const QPoint &pos) Chris@1586: { Chris@1586: if (!m_provideContextMenu) { Chris@1586: return; Chris@1586: } Chris@1586: Chris@1589: delete m_lastContextMenu; Chris@1589: m_lastContextMenu = new QMenu; Chris@1589: auto m = m_lastContextMenu; Chris@1586: Chris@1586: if (m_title == "") { Chris@1586: MenuTitle::addTitle(m, tr("Thumbwheel")); Chris@1586: } else { Chris@1586: MenuTitle::addTitle(m, m_title); Chris@1586: } Chris@1586: Chris@1602: m->addAction(tr("&Edit..."), this, SLOT(edit())); Chris@1602: m->addAction(tr("&Reset to Default"), this, SLOT(resetToDefault())); Chris@1586: Chris@1586: m->popup(mapToGlobal(pos)); Chris@187: } Chris@187: Chris@187: void Chris@187: Thumbwheel::setRangeMapper(RangeMapper *mapper) Chris@187: { Chris@187: if (m_rangeMapper == mapper) return; Chris@187: Chris@187: if (!m_rangeMapper && mapper) { Chris@187: connect(this, SIGNAL(valueChanged(int)), Chris@187: this, SLOT(updateMappedValue(int))); Chris@187: } Chris@187: Chris@187: delete m_rangeMapper; Chris@187: m_rangeMapper = mapper; Chris@187: Chris@187: updateMappedValue(getValue()); Chris@187: } Chris@187: Chris@187: void Chris@187: Thumbwheel::setShowToolTip(bool show) Chris@187: { Chris@187: m_showTooltip = show; Chris@187: m_noMappedUpdate = true; Chris@187: updateMappedValue(getValue()); Chris@187: m_noMappedUpdate = false; Chris@132: } Chris@132: Chris@132: void Chris@1589: Thumbwheel::setProvideContextMenu(bool provide) Chris@1589: { Chris@1589: m_provideContextMenu = provide; Chris@1589: } Chris@1589: Chris@1589: void Chris@133: Thumbwheel::setMinimumValue(int min) Chris@133: { Chris@133: if (m_min == min) return; Chris@133: Chris@133: m_min = min; Chris@133: if (m_max <= m_min) m_max = m_min + 1; Chris@133: if (m_value < m_min) m_value = m_min; Chris@133: if (m_value > m_max) m_value = m_max; Chris@165: Chris@165: m_rotation = float(m_value - m_min) / float(m_max - m_min); Chris@165: update(); Chris@133: } Chris@133: Chris@133: int Chris@133: Thumbwheel::getMinimumValue() const Chris@133: { Chris@133: return m_min; Chris@133: } Chris@133: Chris@133: void Chris@133: Thumbwheel::setMaximumValue(int max) Chris@133: { Chris@133: if (m_max == max) return; Chris@133: Chris@133: m_max = max; Chris@133: if (m_min >= m_max) m_min = m_max - 1; Chris@133: if (m_value < m_min) m_value = m_min; Chris@133: if (m_value > m_max) m_value = m_max; Chris@165: Chris@165: m_rotation = float(m_value - m_min) / float(m_max - m_min); Chris@165: update(); Chris@133: } Chris@133: Chris@133: int Chris@133: Thumbwheel::getMaximumValue() const Chris@133: { Chris@133: return m_max; Chris@133: } Chris@133: Chris@133: void Chris@133: Thumbwheel::setDefaultValue(int deft) Chris@133: { Chris@133: if (m_default == deft) return; Chris@133: Chris@133: m_default = deft; Chris@133: if (m_atDefault) { Chris@133: setValue(m_default); Chris@165: m_atDefault = true; // setValue unsets this Chris@382: m_cache = QImage(); Chris@133: emit valueChanged(getValue()); Chris@133: } Chris@133: } Chris@133: Chris@187: void Chris@908: Thumbwheel::setMappedValue(double mappedValue) Chris@187: { Chris@187: if (m_rangeMapper) { Chris@187: int newValue = m_rangeMapper->getPositionForValue(mappedValue); Chris@190: bool changed = (m_mappedValue != mappedValue); Chris@187: m_mappedValue = mappedValue; Chris@187: m_noMappedUpdate = true; Chris@587: // SVDEBUG << "Thumbwheel::setMappedValue(" << mappedValue << "): new value is " << newValue << " (visible " << isVisible() << ")" << endl; Chris@187: if (newValue != getValue()) { Chris@187: setValue(newValue); Chris@190: changed = true; Chris@382: m_cache = QImage(); Chris@187: } Chris@190: if (changed) emit valueChanged(newValue); Chris@187: m_noMappedUpdate = false; Chris@187: } else { Chris@190: int v = int(mappedValue); Chris@190: if (v != getValue()) { Chris@190: setValue(v); Chris@382: m_cache = QImage(); Chris@190: emit valueChanged(v); Chris@190: } Chris@187: } Chris@187: } Chris@187: Chris@133: int Chris@133: Thumbwheel::getDefaultValue() const Chris@133: { Chris@133: return m_default; Chris@133: } Chris@133: Chris@133: void Chris@132: Thumbwheel::setValue(int value) Chris@132: { Chris@587: // SVDEBUG << "Thumbwheel::setValue(" << value << ") (from " << m_value Chris@585: // << ", rotation " << m_rotation << ")" << " (visible " << isVisible() << ")" << endl; Chris@133: Chris@165: if (m_value != value) { Chris@165: Chris@165: m_atDefault = false; Chris@165: Chris@165: if (value < m_min) value = m_min; Chris@165: if (value > m_max) value = m_max; Chris@165: m_value = value; Chris@165: } Chris@165: Chris@165: m_rotation = float(m_value - m_min) / float(m_max - m_min); Chris@382: m_cache = QImage(); Chris@1586: Chris@1586: if (isVisible()) { Chris@1586: update(); Chris@1586: updateTitle(); Chris@1586: } Chris@132: } Chris@132: Chris@133: void Chris@133: Thumbwheel::resetToDefault() Chris@133: { Chris@133: if (m_default == m_value) return; Chris@133: setValue(m_default); Chris@133: m_atDefault = true; Chris@382: m_cache = QImage(); Chris@133: emit valueChanged(getValue()); Chris@133: } Chris@133: Chris@132: int Chris@132: Thumbwheel::getValue() const Chris@132: { Chris@132: return m_value; Chris@132: } Chris@132: Chris@908: double Chris@187: Thumbwheel::getMappedValue() const Chris@187: { Chris@187: if (m_rangeMapper) { Chris@587: // SVDEBUG << "Thumbwheel::getMappedValue(): value = " << getValue() << ", mappedValue = " << m_mappedValue << endl; Chris@187: return m_mappedValue; Chris@187: } Chris@187: return getValue(); Chris@187: } Chris@187: Chris@187: void Chris@187: Thumbwheel::updateMappedValue(int value) Chris@187: { Chris@187: if (!m_noMappedUpdate) { Chris@187: if (m_rangeMapper) { Chris@187: m_mappedValue = m_rangeMapper->getValueForPosition(value); Chris@187: } else { Chris@187: m_mappedValue = value; Chris@187: } Chris@187: } Chris@187: Chris@1586: updateTitle(); Chris@1586: } Chris@1586: Chris@1586: void Chris@1586: Thumbwheel::updateTitle() Chris@1586: { Chris@1586: QString name = objectName(); Chris@1586: QString unit = ""; Chris@1586: QString text; Chris@1586: double mappedValue = getMappedValue(); Chris@1586: Chris@1586: if (m_rangeMapper) unit = m_rangeMapper->getUnit(); Chris@1586: if (name != "") { Chris@1586: text = tr("%1: %2%3").arg(name).arg(mappedValue).arg(unit); Chris@1586: } else { Chris@1586: text = tr("%2%3").arg(mappedValue).arg(unit); Chris@1586: } Chris@1586: Chris@1586: m_title = text; Chris@1586: Chris@187: if (m_showTooltip) { Chris@187: setToolTip(text); Chris@1586: } else { Chris@1586: setToolTip(""); Chris@187: } Chris@187: } Chris@187: Chris@132: void Chris@256: Thumbwheel::scroll(bool up) Chris@256: { Chris@908: int step = int(lrintf(m_speed)); Chris@256: if (step == 0) step = 1; Chris@256: Chris@256: if (up) { Chris@1266: setValue(m_value + step); Chris@256: } else { Chris@1266: setValue(m_value - step); Chris@256: } Chris@256: Chris@256: emit valueChanged(getValue()); Chris@256: } Chris@256: Chris@256: void Chris@132: Thumbwheel::setSpeed(float speed) Chris@132: { Chris@132: m_speed = speed; Chris@132: } Chris@132: Chris@132: float Chris@132: Thumbwheel::getSpeed() const Chris@132: { Chris@132: return m_speed; Chris@132: } Chris@132: Chris@132: void Chris@132: Thumbwheel::setTracking(bool tracking) Chris@132: { Chris@132: m_tracking = tracking; Chris@132: } Chris@132: Chris@132: bool Chris@132: Thumbwheel::getTracking() const Chris@132: { Chris@132: return m_tracking; Chris@132: } Chris@132: Chris@132: void Chris@132: Thumbwheel::setShowScale(bool showScale) Chris@132: { Chris@132: m_showScale = showScale; Chris@132: } Chris@132: Chris@132: bool Chris@132: Thumbwheel::getShowScale() const Chris@132: { Chris@132: return m_showScale; Chris@132: } Chris@132: Chris@132: void Chris@189: Thumbwheel::enterEvent(QEvent *) Chris@189: { Chris@189: emit mouseEntered(); Chris@189: } Chris@189: Chris@189: void Chris@189: Thumbwheel::leaveEvent(QEvent *) Chris@189: { Chris@189: emit mouseLeft(); Chris@189: } Chris@189: Chris@189: void Chris@132: Thumbwheel::mousePressEvent(QMouseEvent *e) Chris@132: { Chris@187: if (e->button() == Qt::MidButton || Chris@187: ((e->button() == Qt::LeftButton) && Chris@187: (e->modifiers() & Qt::ControlModifier))) { Chris@187: resetToDefault(); Chris@187: } else if (e->button() == Qt::LeftButton) { Chris@133: m_clicked = true; Chris@133: m_clickPos = e->pos(); Chris@165: m_clickRotation = m_rotation; Chris@133: } Chris@132: } Chris@132: Chris@132: void Chris@187: Thumbwheel::mouseDoubleClickEvent(QMouseEvent *mouseEvent) Chris@132: { Chris@188: //!!! needs a common base class with AudioDial (and Panner?) Chris@187: Chris@187: if (mouseEvent->button() != Qt::LeftButton) { Chris@187: return; Chris@187: } Chris@187: Chris@1586: edit(); Chris@1586: } Chris@1586: Chris@1586: void Chris@1586: Thumbwheel::edit() Chris@1586: { Chris@187: bool ok = false; Chris@187: Chris@187: if (m_rangeMapper) { Chris@187: Chris@908: double min = m_rangeMapper->getValueForPosition(m_min); Chris@908: double max = m_rangeMapper->getValueForPosition(m_max); Chris@187: Chris@187: if (min > max) { Chris@908: double tmp = min; Chris@187: min = max; Chris@187: max = tmp; Chris@187: } Chris@187: Chris@187: QString unit = m_rangeMapper->getUnit(); Chris@187: Chris@187: QString text; Chris@187: if (objectName() != "") { Chris@187: if (unit != "") { Chris@187: text = tr("New value for %1, from %2 to %3 %4:") Chris@187: .arg(objectName()).arg(min).arg(max).arg(unit); Chris@187: } else { Chris@187: text = tr("New value for %1, from %2 to %3:") Chris@187: .arg(objectName()).arg(min).arg(max); Chris@187: } Chris@187: } else { Chris@187: if (unit != "") { Chris@187: text = tr("Enter a new value from %1 to %2 %3:") Chris@187: .arg(min).arg(max).arg(unit); Chris@187: } else { Chris@187: text = tr("Enter a new value from %1 to %2:") Chris@187: .arg(min).arg(max); Chris@187: } Chris@187: } Chris@187: Chris@908: double newValue = QInputDialog::getDouble Chris@187: (this, Chris@187: tr("Enter new value"), Chris@187: text, Chris@187: m_mappedValue, Chris@187: min, Chris@187: max, Chris@187: 4, Chris@187: &ok); Chris@187: Chris@187: if (ok) { Chris@187: setMappedValue(newValue); Chris@187: } Chris@187: Chris@187: } else { Chris@187: Chris@616: int newValue = QInputDialog::getInt Chris@187: (this, Chris@187: tr("Enter new value"), Chris@187: tr("Enter a new value from %1 to %2:") Chris@187: .arg(m_min).arg(m_max), Chris@187: getValue(), m_min, m_max, 1, &ok); Chris@187: Chris@187: if (ok) { Chris@187: setValue(newValue); Chris@187: } Chris@187: } Chris@132: } Chris@132: Chris@187: Chris@132: void Chris@132: Thumbwheel::mouseMoveEvent(QMouseEvent *e) Chris@132: { Chris@133: if (!m_clicked) return; Chris@132: int dist = 0; Chris@132: if (m_orientation == Qt::Horizontal) { Chris@132: dist = e->x() - m_clickPos.x(); Chris@132: } else { Chris@132: dist = e->y() - m_clickPos.y(); Chris@132: } Chris@165: Chris@908: float rotation = m_clickRotation + (m_speed * float(dist)) / 100; Chris@165: if (rotation < 0.f) rotation = 0.f; Chris@165: if (rotation > 1.f) rotation = 1.f; Chris@908: int value = int(lrintf(float(m_min) + float(m_max - m_min) * m_rotation)); Chris@132: if (value != m_value) { Chris@132: setValue(value); Chris@132: if (m_tracking) emit valueChanged(getValue()); Chris@165: m_rotation = rotation; Chris@165: } else if (fabsf(rotation - m_rotation) > 0.001) { Chris@165: m_rotation = rotation; Chris@165: repaint(); Chris@165: } Chris@132: } Chris@132: Chris@132: void Chris@132: Thumbwheel::mouseReleaseEvent(QMouseEvent *e) Chris@132: { Chris@133: if (!m_clicked) return; Chris@132: bool reallyTracking = m_tracking; Chris@132: m_tracking = true; Chris@132: mouseMoveEvent(e); Chris@132: m_tracking = reallyTracking; Chris@133: m_clicked = false; Chris@132: } Chris@132: Chris@132: void Chris@132: Thumbwheel::wheelEvent(QWheelEvent *e) Chris@132: { Chris@1303: int delta = m_wheelCounter.count(e); Chris@132: Chris@1303: if (delta == 0) { Chris@1303: return; Chris@132: } Chris@1303: Chris@1303: setValue(m_value + delta); Chris@132: emit valueChanged(getValue()); Chris@132: } Chris@132: Chris@132: void Chris@132: Thumbwheel::paintEvent(QPaintEvent *) Chris@132: { Chris@382: Profiler profiler("Thumbwheel::paintEvent"); Chris@382: Chris@382: if (!m_cache.isNull()) { Chris@382: QPainter paint(this); Chris@1192: paint.drawImage(rect(), m_cache, m_cache.rect()); Chris@382: return; Chris@382: } Chris@382: Chris@382: Profiler profiler2("Thumbwheel::paintEvent (no cache)"); Chris@382: Chris@1192: QSize imageSize = size() * devicePixelRatio(); Chris@1192: m_cache = QImage(imageSize, QImage::Format_ARGB32); Chris@382: m_cache.fill(Qt::transparent); Chris@190: Chris@1193: int w = m_cache.width(); Chris@1193: int h = m_cache.height(); Chris@1192: int bw = 3; // border width Chris@191: Chris@191: QRect subclip; Chris@191: if (m_orientation == Qt::Horizontal) { Chris@1192: subclip = QRect(bw, bw+1, w - bw*2, h - bw*2 - 2); Chris@191: } else { Chris@1192: subclip = QRect(bw+1, bw, w - bw*2 - 2, h - bw*2); Chris@191: } Chris@191: Chris@382: QPainter paint(&m_cache); Chris@1192: paint.setClipRect(m_cache.rect()); Chris@1478: paint.fillRect(subclip, palette().window().color()); Chris@190: Chris@190: paint.setRenderHint(QPainter::Antialiasing, true); Chris@133: Chris@908: double w0 = 0.5; Chris@908: double w1 = w - 0.5; Chris@190: Chris@908: double h0 = 0.5; Chris@908: double h1 = h - 0.5; Chris@190: Chris@190: for (int i = bw-1; i >= 0; --i) { Chris@190: Chris@133: int grey = (i + 1) * (256 / (bw + 1)); Chris@133: QColor fc = QColor(grey, grey, grey); Chris@133: paint.setPen(fc); Chris@190: Chris@190: QPainterPath path; Chris@190: Chris@190: if (m_orientation == Qt::Horizontal) { Chris@190: path.moveTo(w0 + i, h0 + i + 2); Chris@190: path.quadTo(w/2, i * 1.25, w1 - i, h0 + i + 2); Chris@190: path.lineTo(w1 - i, h1 - i - 2); Chris@190: path.quadTo(w/2, h - i * 1.25, w0 + i, h1 - i - 2); Chris@190: path.closeSubpath(); Chris@190: } else { Chris@190: path.moveTo(w0 + i + 2, h0 + i); Chris@190: path.quadTo(i * 1.25, h/2, w0 + i + 2, h1 - i); Chris@190: path.lineTo(w1 - i - 2, h1 - i); Chris@190: path.quadTo(w - i * 1.25, h/2, w1 - i - 2, h0 + i); Chris@190: path.closeSubpath(); Chris@190: } Chris@190: Chris@190: paint.drawPath(path); Chris@133: } Chris@133: Chris@191: paint.setClipRect(subclip); Chris@133: Chris@908: double radians = m_rotation * 1.5f * M_PI; Chris@132: Chris@682: // cerr << "value = " << m_value << ", min = " << m_min << ", max = " << m_max << ", rotation = " << rotation << endl; Chris@132: Chris@1192: int ww = (m_orientation == Qt::Horizontal ? w : h) - bw*2; // wheel width Chris@132: Chris@132: // total number of notches on the entire wheel Chris@132: int notches = 25; Chris@132: Chris@132: // radius of the wheel including invisible part Chris@1192: int radius = int(ww / 2 + 2); Chris@132: Chris@132: for (int i = 0; i < notches; ++i) { Chris@132: Chris@908: double a0 = (2.0 * M_PI * i) / notches + radians; Chris@908: double a1 = a0 + M_PI / (notches * 2); Chris@908: double a2 = (2.0 * M_PI * (i + 1)) / notches + radians; Chris@132: Chris@908: double depth = cos((a0 + a2) / 2); Chris@132: if (depth < 0) continue; Chris@132: Chris@1192: double x0 = radius * sin(a0) + ww/2; Chris@1192: double x1 = radius * sin(a1) + ww/2; Chris@1192: double x2 = radius * sin(a2) + ww/2; Chris@1192: if (x2 < 0 || x0 > ww) continue; Chris@132: Chris@132: if (x0 < 0) x0 = 0; Chris@1192: if (x2 > ww) x2 = ww; Chris@132: Chris@133: x0 += bw; Chris@133: x1 += bw; Chris@133: x2 += bw; Chris@133: Chris@908: int grey = int(lrint(120 * depth)); Chris@541: Chris@132: QColor fc = QColor(grey, grey, grey); Chris@541: QColor oc = palette().highlight().color(); Chris@132: Chris@541: paint.setPen(fc); Chris@132: Chris@132: if (m_showScale) { Chris@132: Chris@132: paint.setBrush(oc); Chris@132: Chris@908: double prop; Chris@132: if (i >= notches / 4) { Chris@908: prop = double(notches - (((i - double(notches) / 4.f) * 4.f) / 3.f)) Chris@132: / notches; Chris@132: } else { Chris@132: prop = 0.f; Chris@132: } Chris@132: Chris@132: if (m_orientation == Qt::Horizontal) { Chris@1192: paint.drawRect(QRectF(x1, h - (h - bw*2) * prop - bw, Chris@1192: x2 - x1, h * prop)); Chris@132: } else { Chris@1192: paint.drawRect(QRectF(bw, x1, (w - bw*2) * prop, x2 - x1)); Chris@132: } Chris@132: } Chris@132: Chris@541: paint.setPen(fc); Chris@1478: paint.setBrush(palette().window().color()); Chris@132: Chris@132: if (m_orientation == Qt::Horizontal) { Chris@1192: paint.drawRect(QRectF(x0, bw, x1 - x0, h - bw*2)); Chris@132: } else { Chris@1192: paint.drawRect(QRectF(bw, x0, w - bw*2, x1 - x0)); Chris@132: } Chris@132: } Chris@382: Chris@382: QPainter paint2(this); Chris@1192: paint2.drawImage(rect(), m_cache, m_cache.rect()); Chris@132: } Chris@132: Chris@132: QSize Chris@132: Thumbwheel::sizeHint() const Chris@132: { Chris@132: if (m_orientation == Qt::Horizontal) { Chris@132: return QSize(80, 12); Chris@132: } else { Chris@132: return QSize(12, 80); Chris@132: } Chris@132: } Chris@132: