You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rosegarden/src/gui/widgets/Rotary.cpp

559 lines
14 KiB

/*
Rosegarden
A MIDI and audio sequencer and musical notation editor.
This program is Copyright 2000-2008
Guillaume Laurent <glaurent@telegraph-road.org>,
Chris Cannam <cannam@all-day-breakfast.com>,
Richard Bown <richard.bown@ferventsoftware.com>
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 <tdeapplication.h>
#include <tdelocale.h>
#include <tqbrush.h>
#include <tqcolor.h>
#include <tqdialog.h>
#include <tqimage.h>
#include <tqpainter.h>
#include <tqpalette.h>
#include <tqpen.h>
#include <tqpixmap.h>
#include <tqpoint.h>
#include <tqstring.h>
#include <tqtimer.h>
#include <tqtooltip.h>
#include <tqwidget.h>
#include <cmath>
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"