/* Rosegarden A MIDI and audio sequencer and musical notation editor. This program is Copyright 2000-2008 Guillaume Laurent , Chris Cannam , Richard Bown The moral rights of Guillaume Laurent, Chris Cannam, and Richard Bown to claim authorship of this work have been asserted. 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 "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) static TextFloat* _float = 0; static TQTimer *_floatTimer = 0; Rotary::PixmapCache Rotary::m_pixmaps; Rotary::Rotary(TQWidget *parent, float minValue, float maxValue, float step, float pageStep, float initialPosition, int size, TickMode ticks, bool snapToTicks, bool centred, bool logarithmic) : TQWidget(parent), m_minValue(minValue), m_maxValue(maxValue), m_step(step), m_pageStep(pageStep), m_size(size), m_tickMode(ticks), m_snapToTicks(snapToTicks), m_centred(centred), m_position(initialPosition), m_snapPosition(m_position), m_initialPosition(initialPosition), m_buttonPressed(false), m_lastY(0), m_lastX(0), m_knobColour(0, 0, 0), m_logarithmic(logarithmic) { setBackgroundMode(TQt::NoBackground); if (!_float) _float = new TextFloat(this); if (!_floatTimer) { _floatTimer = new TQTimer(); } // connect timer connect(_floatTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotFloatTimeout())); _float->hide(); TQToolTip::add (this, i18n("Click and drag up and down or left and right to modify.\nDouble click to edit value directly.")); setFixedSize(size, size); emit valueChanged(m_snapPosition); } Rotary::~Rotary() { // Remove this connection // disconnect(_floatTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotFloatTimeout())); delete _float; _float = 0; } void Rotary::slotFloatTimeout() { if (_float) _float->hide(); } void Rotary::setKnobColour(const TQColor &colour) { m_knobColour = colour; repaint(); } void Rotary::paintEvent(TQPaintEvent *) { TQPainter paint; double angle = ROTARY_MIN // offset + (ROTARY_RANGE * (double(m_snapPosition - m_minValue) / (double(m_maxValue) - double(m_minValue)))); 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_maxValue + 0.0001 - m_minValue) / m_pageStep; break; case StepTicks: numTicks = 1 + (m_maxValue + 0.0001 - m_minValue) / 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; TQPixmap map(width, width); map.fill(paletteBackgroundColor()); paint.begin(&map); TQPen pen; pen.setColor(kapp->palette().color(TQPalette::Active, TQColorGroup::Dark)); pen.setWidth(scale); paint.setPen(pen); if (m_knobColour != TQt::black) { paint.setBrush(m_knobColour); } else { paint.setBrush( kapp->palette().color(TQPalette::Active, TQColorGroup::Base)); } TQColor c(m_knobColour); pen.setColor(c); paint.setPen(pen); int indent = width * 0.15 + 1; paint.drawEllipse(indent, indent, width - 2*indent, width - 2*indent); pen.setWidth(2 * scale); int pos = indent + (width - 2 * indent) / 8; int darkWidth = (width - 2 * indent) * 2 / 3; int darkQuote = (130 * 2 / (darkWidth ? darkWidth : 1)) + 100; 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(TQBrush::NoBrush); pen.setColor(colorGroup().dark()); 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); } // now 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); int shadowAngle = -720; c = colorGroup().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.light( 110 ); } shadowAngle = 2160; c = colorGroup().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.light( 109 ); } // and un-draw the bottom part pen.setColor(paletteBackgroundColor()); paint.setPen(pen); paint.drawArc(scale / 2, scale / 2, width - scale, width - scale, -45 * 16, -90 * 16); 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(colorGroup().dark()); paint.setPen(pen); paint.drawLine(int(x0), int(y0), int(x), int(y)); paint.end(); TQImage i = map.convertToImage().smoothScale(m_size, m_size); m_pixmaps[index] = TQPixmap(i); paint.begin(this); paint.drawPixmap(0, 0, m_pixmaps[index]); paint.end(); } void Rotary::drawTick(TQPainter &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_minValue + m_maxValue) / 2.0) { m_snapPosition = m_minValue; } else { m_snapPosition = m_maxValue; } break; case IntervalTicks: m_snapPosition = m_minValue + (m_maxValue - m_minValue) / 4.0 * int((m_snapPosition - m_minValue) / ((m_maxValue - m_minValue) / 4.0)); break; case PageStepTicks: m_snapPosition = m_minValue + m_pageStep * int((m_snapPosition - m_minValue) / m_pageStep); break; case StepTicks: m_snapPosition = m_minValue + m_step * int((m_snapPosition - m_minValue) / m_step); break; } } } void Rotary::mousePressEvent(TQMouseEvent *e) { if (e->button() == TQt::LeftButton) { m_buttonPressed = true; m_lastY = e->y(); m_lastX = e->x(); } else if (e->button() == TQt::MidButton) // reset to default { m_position = m_initialPosition; snapPosition(); update(); emit valueChanged(m_snapPosition); } else if (e->button() == TQt::RightButton) // reset to centre position { m_position = (m_maxValue + m_minValue) / 2.0; snapPosition(); update(); emit valueChanged(m_snapPosition); } TQPoint totalPos = mapTo(topLevelWidget(), TQPoint(0, 0)); if (!_float) _float = new TextFloat(this); _float->reparent(this); _float->move(totalPos + TQPoint(width() + 2, -height() / 2)); if (m_logarithmic) { _float->setText(TQString("%1").arg(powf(10, m_position))); } else { _float->setText(TQString("%1").arg(m_position)); } _float->show(); // std::cerr << "Rotary::mousePressEvent: logarithmic = " << m_logarithmic // << ", position = " << m_position << std::endl; if (e->button() == TQt::RightButton || e->button() == TQt::MidButton) { // one shot, 500ms _floatTimer->start(500, true); } } void Rotary::mouseDoubleClickEvent(TQMouseEvent * /*e*/) { float minv = m_minValue; float maxv = m_maxValue; 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, i18n("Select a new value"), i18n("Enter a new value"), minv, maxv, val, step); if (dialog.exec() == TQDialog::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(TQMouseEvent *e) { if (e->button() == TQt::LeftButton) { m_buttonPressed = false; m_lastY = 0; m_lastX = 0; // Hide the float text // if (_float) _float->hide(); } } void Rotary::mouseMoveEvent(TQMouseEvent *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_maxValue) m_position = m_maxValue; else if (newValue < m_minValue) m_position = m_minValue; 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 if (m_logarithmic) { _float->setText(TQString("%1").arg(powf(10, m_snapPosition))); } else { _float->setText(TQString("%1").arg(m_snapPosition)); } } } void Rotary::wheelEvent(TQWheelEvent *e) { if (e->delta() > 0) m_position -= m_pageStep; else m_position += m_pageStep; if (m_position > m_maxValue) m_position = m_maxValue; if (m_position < m_minValue) m_position = m_minValue; snapPosition(); update(); if (!_float) _float = new TextFloat(this); // draw on the float text if (m_logarithmic) { _float->setText(TQString("%1").arg(powf(10, m_snapPosition))); } else { _float->setText(TQString("%1").arg(m_snapPosition)); } // Reposition - we need to sum the relative positions up to the // topLevel or dialog to please move(). Move just top/right of the rotary // TQPoint totalPos = mapTo(topLevelWidget(), TQPoint(0, 0)); _float->reparent(this); _float->move(totalPos + TQPoint(width() + 2, -height() / 2)); _float->show(); // one shot, 500ms _floatTimer->start(500, true); // set it to show for a timeout value emit valueChanged(m_snapPosition); } void Rotary::setPosition(float position) { m_position = position; snapPosition(); update(); } } #include "Rotary.moc"