/* * knob.cpp - powerful knob-widget * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "knob.h" #include "caption_menu.h" #include "config_mgr.h" #include "ControllerConnection.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "MainWindow.h" #include "ProjectJournal.h" #include "song.h" #include "string_pair_drag.h" #include "templates.h" #include "text_float.h" textFloat * knob::s_textFloat = NULL; knob::knob( int _knob_num, QWidget * _parent, const QString & _name ) : QWidget( _parent ), FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), m_knobNum( _knob_num ), m_label( "" ), m_knobPixmap( NULL ), m_volumeKnob( false ), m_mouseOffset( 0.0f ), m_buttonPressed( false ), m_angle( -10 ), m_outerColor( NULL ) { if( s_textFloat == NULL ) { s_textFloat = new textFloat; } setAccessibleName( _name ); if( m_knobNum != knobStyled ) { m_knobPixmap = new QPixmap( embed::getIconPixmap( QString( "knob0" + QString::number( m_knobNum + 1 ) ).toUtf8().constData() ) ); setFixedSize( m_knobPixmap->width(), m_knobPixmap->height() ); } setTotalAngle( 270.0f ); setInnerRadius( 1.0f ); setOuterRadius( 10.0f ); setFocusPolicy( Qt::ClickFocus ); doConnections(); } knob::~knob() { if( m_knobPixmap ) { delete m_knobPixmap; } } void knob::setLabel( const QString & _txt ) { m_label = _txt; if( m_knobPixmap ) { setFixedSize( qMax( m_knobPixmap->width(), QFontMetrics( pointSizeF( font(), 6 ) ).width( m_label ) ), m_knobPixmap->height() + 10 ); } update(); } void knob::setTotalAngle( float _angle ) { if( _angle < 10.0 ) { m_totalAngle = 10.0; } else { m_totalAngle = _angle; } update(); } float knob::innerRadius() const { return m_innerRadius; } void knob::setInnerRadius( float _r ) { m_innerRadius = _r; } float knob::outerRadius() const { return m_outerRadius; } void knob::setOuterRadius( float _r ) { m_outerRadius = _r; } QPointF knob::centerPoint() const { return m_centerPoint; } float knob::centerPointX() const { return m_centerPoint.x(); } void knob::setCenterPointX( float _c ) { m_centerPoint.setX( _c ); } float knob::centerPointY() const { return m_centerPoint.y(); } void knob::setCenterPointY( float _c ) { m_centerPoint.setY( _c ); } float knob::lineWidth() const { return m_lineWidth; } void knob::setLineWidth( float _w ) { m_lineWidth = _w; } QColor knob::outerColor() const { if( m_outerColor ) { return *m_outerColor; } else { return QColor(); } } void knob::setOuterColor( const QColor & _c ) { if( m_outerColor ) { *m_outerColor = _c; } else { m_outerColor = new QColor( _c ); } } QLineF knob::calculateLine( const QPointF & _mid, float _radius, float _innerRadius ) const { const float rarc = m_angle * M_PI / 180.0; const float ca = cos( rarc ); const float sa = -sin( rarc ); return QLineF( _mid.x() - sa*_innerRadius, _mid.y() - ca*_innerRadius, _mid.x() - sa*_radius, _mid.y() - ca*_radius ); } bool knob::updateAngle() { int angle = 0; if( model() && model()->maxValue() != model()->minValue() ) { float a = ( model()->value() - 0.5 * ( model()->minValue() + model()->maxValue() ) ) / ( model()->maxValue() - model()->minValue() ) * m_totalAngle; angle = static_cast( a ) % 360; } if( qAbs( angle - m_angle ) > 3 ) { m_angle = angle; return true; } return false; } void knob::drawKnob( QPainter * _p ) { if( updateAngle() == false && !m_cache.isNull() ) { _p->drawImage( 0, 0, m_cache ); return; } m_cache = QImage( size(), QImage::Format_ARGB32 ); m_cache.fill( qRgba( 0, 0, 0, 0 ) ); QPainter p( &m_cache ); QPoint mid; if( m_knobNum == knobStyled ) { p.setRenderHint( QPainter::Antialiasing ); // Perhaps this can move to setOuterRadius() if( m_outerColor ) { QRadialGradient gradient( centerPoint(), outerRadius() ); gradient.setColorAt(0.4, _p->pen().brush().color() ); gradient.setColorAt(1, *m_outerColor ); p.setPen( QPen( gradient, lineWidth(), Qt::SolidLine, Qt::RoundCap ) ); } else { QPen pen = p.pen(); pen.setWidth( (int) lineWidth() ); pen.setCapStyle( Qt::RoundCap ); p.setPen( pen ); } p.drawLine( calculateLine( centerPoint(), outerRadius(), innerRadius() ) ); p.end(); _p->drawImage( 0, 0, m_cache ); return; } // Old-skool knobs const float radius = m_knobPixmap->width() / 2.0f - 1; mid = QPoint( width() / 2, m_knobPixmap->height() / 2 ); p.drawPixmap( static_cast( width() / 2 - m_knobPixmap->width() / 2 ), 0, *m_knobPixmap ); p.setPen( QPen( QColor( 200, 0, 0 ), 2 ) ); p.setRenderHint( QPainter::Antialiasing ); switch( m_knobNum ) { case knobSmall_17: { p.drawLine( calculateLine( mid, radius ) ); break; } case knobBright_26: { p.drawLine( calculateLine( mid, radius-5 ) ); break; } case knobDark_28: { const float rb = qMax( ( radius - 10 ) / 3.0, 0.0 ); const float re = qMax( ( radius - 4 ), 0.0 ); QLineF ln = calculateLine( mid, re, rb ); ln.translate( 1, 1 ); p.drawLine( ln ); break; } case knobGreen_17: { p.setPen( QPen( QColor( 0, 200, 0 ), 2 ) ); p.drawLine( calculateLine( mid, radius ) ); break; } } p.end(); _p->drawImage( 0, 0, m_cache ); } float knob::getValue( const QPoint & _p ) { if( engine::mainWindow()->isShiftPressed() ) { return( ( _p.y() - m_origMousePos.y() ) * model()->step() ); } return( ( _p.y() - m_origMousePos.y() ) * pageSize() ); } void knob::contextMenuEvent( QContextMenuEvent * ) { // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden // and it isn't shown again until user does something which causes // an QApplication::restoreOverrideCursor()-call... mouseReleaseEvent( NULL ); captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu.exec( QCursor::pos() ); } void knob::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "float_value," "automatable_model" ); } void knob::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString val = stringPairDrag::decodeValue( _de ); if( type == "float_value" ) { model()->setValue( val.toFloat() ); _de->accept(); } else if( type == "automatable_model" ) { AutomatableModel * mod = dynamic_cast( engine::projectJournal()-> journallingObject( val.toInt() ) ); if( mod != NULL ) { AutomatableModel::linkModels( model(), mod ); mod->setValue( model()->value() ); } } } void knob::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && ! ( _me->modifiers() & Qt::ControlModifier ) && ! ( _me->modifiers() & Qt::ShiftModifier ) ) { model()->prepareJournalEntryFromOldVal(); const QPoint & p = _me->pos(); m_origMousePos = p; emit sliderPressed(); QApplication::setOverrideCursor( Qt::BlankCursor ); s_textFloat->setText( displayValue() ); s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); s_textFloat->show(); m_buttonPressed = true; } else if( _me->button() == Qt::LeftButton && engine::mainWindow()->isShiftPressed() == true ) { new stringPairDrag( "float_value", QString::number( model()->value() ), QPixmap(), this ); } else { FloatModelView::mousePressEvent( _me ); } } void knob::mouseMoveEvent( QMouseEvent * _me ) { if( m_buttonPressed == true && _me->pos() != m_origMousePos ) { setPosition( _me->pos() ); emit sliderMoved( model()->value() ); QCursor::setPos( mapToGlobal( m_origMousePos ) ); } s_textFloat->setText( displayValue() ); } void knob::mouseReleaseEvent( QMouseEvent * /* _me*/ ) { model()->addJournalEntryFromOldToCurVal(); m_buttonPressed = false; m_mouseOffset = 0; emit sliderReleased(); QApplication::restoreOverrideCursor(); s_textFloat->hide(); } void knob::focusOutEvent( QFocusEvent * _fe ) { // make sure we don't loose mouse release event mouseReleaseEvent( NULL ); QWidget::focusOutEvent( _fe ); } void knob::mouseDoubleClickEvent( QMouseEvent * ) { enterValue(); } void knob::paintEvent( QPaintEvent * _me ) { QPainter p( this ); drawKnob( &p ); if( !m_label.isEmpty() ) { p.setFont( pointSizeF( p.font(), 6 ) ); /* p.setPen( QColor( 64, 64, 64 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2 + 1, height() - 1, m_label );*/ p.setPen( QColor( 255, 255, 255 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2, height() - 2, m_label ); } } void knob::wheelEvent( QWheelEvent * _we ) { _we->accept(); const int inc = ( _we->delta() > 0 ) ? 1 : -1; model()->incValue( inc ); s_textFloat->setText( displayValue() ); s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); s_textFloat->setVisibilityTimeOut( 1000 ); emit sliderMoved( model()->value() ); } void knob::setPosition( const QPoint & _p ) { model()->setValue( model()->value() - getValue( _p ) ); } void knob::enterValue() { bool ok; float new_val; if( isVolumeKnob() && configManager::inst()->value( "app", "displaydbv" ).toInt() ) { new_val = QInputDialog::getDouble( this, accessibleName(), tr( "Please enter a new value between " "-96.0 dBV and 6.0 dBV:" ), 20.0 * log10( model()->value() / 100.0 ), -96.0, 6.0, 4, &ok ); if( new_val <= -96.0 ) { new_val = 0.0f; } else { new_val = pow( 10.0, ( new_val / 20.0 ) ) * 100.0; } } else { new_val = QInputDialog::getDouble( this, accessibleName(), tr( "Please enter a new value between " "%1 and %2:" ). arg( model()->minValue() ). arg( model()->maxValue() ), model()->value(), model()->minValue(), model()->maxValue(), 4, &ok ); } if( ok ) { model()->setValue( new_val ); } } void knob::friendlyUpdate() { if( model()->getControllerConnection() == NULL || model()->getControllerConnection()->getController()-> frequentUpdates() == false || Controller::runningFrames() % (256*4) == 0 ) { update(); } } QString knob::displayValue() const { if( isVolumeKnob() && configManager::inst()->value( "app", "displaydbv" ).toInt() ) { return m_description.trimmed() + QString( " %1 dBV" ). arg( 20.0 * log10( model()->value() / 100.0 ), 3, 'f', 2 ); } return m_description.trimmed() + QString( " %1" ). arg( model()->value() ) + m_unit; } void knob::doConnections() { if( model() != NULL ) { QObject::connect( model(), SIGNAL( dataChanged() ), this, SLOT( friendlyUpdate() ) ); QObject::connect( model(), SIGNAL( propertiesChanged() ), this, SLOT( update() ) ); } } void knob::displayHelp() { QWhatsThis::showText( mapToGlobal( rect().bottomRight() ), whatsThis() ); } #include "moc_knob.cxx"