From 15246b2758df80fbd6e10301315524df9797a5bf Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 8 Jul 2023 13:50:15 +0200 Subject: [PATCH] Initial version of FloatModelEditorBase Create an initial version of `FloatModelEditorBase`. It is intended to become the base widget for all widgets that manipulate a float model and provides some base functionalities like context menus, edit dialogs, mouse handling, etc. The initial version is a copy of `Knob`. The enum and its values have been suffixed with "Temp" as they will be removed later anyway. Same applies for the function `convertPixmapToGrayScaleTemp`. --- include/FloatModelEditorBase.h | 217 ++++++ src/gui/CMakeLists.txt | 1 + src/gui/LadspaWidgetFactory.cpp | 12 +- src/gui/widgets/FloatModelEditorBase.cpp | 873 +++++++++++++++++++++++ 4 files changed, 1102 insertions(+), 1 deletion(-) create mode 100644 include/FloatModelEditorBase.h create mode 100644 src/gui/widgets/FloatModelEditorBase.cpp diff --git a/include/FloatModelEditorBase.h b/include/FloatModelEditorBase.h new file mode 100644 index 000000000..84f4274bd --- /dev/null +++ b/include/FloatModelEditorBase.h @@ -0,0 +1,217 @@ +/* + * FloatModelEditorBase.h - Base editor for float models + * + * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * 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. + * + */ + +#ifndef LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H +#define LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H + +#include +#include +#include +#include +#include + +#include "AutomatableModelView.h" + + +class QPixmap; + +namespace lmms::gui +{ + + +class SimpleTextFloat; + +enum knobTypesTemp +{ + knobDark_28Temp, knobBright_26Temp, knobSmall_17Temp, knobVintage_32Temp, knobStyledTemp +} ; + + +void convertPixmapToGrayScaleTemp(QPixmap &pixMap); + +class LMMS_EXPORT FloatModelEditorBase : public QWidget, public FloatModelView +{ + Q_OBJECT + Q_ENUMS( knobTypesTemp ) + + Q_PROPERTY(float innerRadius READ innerRadius WRITE setInnerRadius) + Q_PROPERTY(float outerRadius READ outerRadius WRITE setOuterRadius) + + Q_PROPERTY(float centerPointX READ centerPointX WRITE setCenterPointX) + Q_PROPERTY(float centerPointY READ centerPointY WRITE setCenterPointY) + + Q_PROPERTY(float lineWidth READ lineWidth WRITE setLineWidth) + + // Unfortunately, the gradient syntax doesn't create our gradient + // correctly so we need to do this: + Q_PROPERTY(QColor outerColor READ outerColor WRITE setOuterColor) + + Q_PROPERTY(QColor lineActiveColor MEMBER m_lineActiveColor) + Q_PROPERTY(QColor lineInactiveColor MEMBER m_lineInactiveColor) + Q_PROPERTY(QColor arcActiveColor MEMBER m_arcActiveColor) + Q_PROPERTY(QColor arcInactiveColor MEMBER m_arcInactiveColor) + + mapPropertyFromModel(bool,isVolumeKnob,setVolumeKnob,m_volumeKnob); + mapPropertyFromModel(float,volumeRatio,setVolumeRatio,m_volumeRatio); + + Q_PROPERTY(knobTypesTemp knobNum READ knobNum WRITE setknobNum) + + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) + + void initUi( const QString & _name ); //!< to be called by ctors + void onKnobNumUpdated(); //!< to be called when you updated @a m_knobNum + +public: + FloatModelEditorBase( knobTypesTemp _knob_num, QWidget * _parent = nullptr, const QString & _name = QString() ); + FloatModelEditorBase( QWidget * _parent = nullptr, const QString & _name = QString() ); //!< default ctor + FloatModelEditorBase( const FloatModelEditorBase& other ) = delete; + + // TODO: remove + inline void setHintText( const QString & _txt_before, + const QString & _txt_after ) + { + setDescription( _txt_before ); + setUnit( _txt_after ); + } + void setLabel( const QString & txt ); + void setHtmlLabel( const QString &htmltxt ); + + void setTotalAngle( float angle ); + + // Begin styled knob accessors + float innerRadius() const; + void setInnerRadius( float r ); + + float outerRadius() const; + void setOuterRadius( float r ); + + knobTypesTemp knobNum() const; + void setknobNum( knobTypesTemp k ); + + QPointF centerPoint() const; + float centerPointX() const; + void setCenterPointX( float c ); + float centerPointY() const; + void setCenterPointY( float c ); + + float lineWidth() const; + void setLineWidth( float w ); + + QColor outerColor() const; + void setOuterColor( const QColor & c ); + + QColor textColor() const; + void setTextColor( const QColor & c ); + + +signals: + void sliderPressed(); + void sliderReleased(); + void sliderMoved( float value ); + + +protected: + void contextMenuEvent( QContextMenuEvent * _me ) override; + void dragEnterEvent( QDragEnterEvent * _dee ) override; + void dropEvent( QDropEvent * _de ) override; + void focusOutEvent( QFocusEvent * _fe ) override; + void mousePressEvent( QMouseEvent * _me ) override; + void mouseReleaseEvent( QMouseEvent * _me ) override; + void mouseMoveEvent( QMouseEvent * _me ) override; + void mouseDoubleClickEvent( QMouseEvent * _me ) override; + void paintEvent( QPaintEvent * _me ) override; + void wheelEvent( QWheelEvent * _me ) override; + void changeEvent(QEvent * ev) override; + + virtual float getValue( const QPoint & _p ); + +private slots: + virtual void enterValue(); + void friendlyUpdate(); + void toggleScale(); + +private: + virtual QString displayValue() const; + + void doConnections() override; + + QLineF calculateLine( const QPointF & _mid, float _radius, + float _innerRadius = 1) const; + + void drawKnob( QPainter * _p ); + void setPosition( const QPoint & _p ); + bool updateAngle(); + + int angleFromValue( float value, float minValue, float maxValue, float totalAngle ) const + { + return static_cast( ( value - 0.5 * ( minValue + maxValue ) ) / ( maxValue - minValue ) * m_totalAngle ) % 360; + } + + inline float pageSize() const + { + return ( model()->maxValue() - model()->minValue() ) / 100.0f; + } + + + static SimpleTextFloat * s_textFloat; + + QString m_label; + bool m_isHtmlLabel; + QTextDocument* m_tdRenderer; + + std::unique_ptr m_knobPixmap; + BoolModel m_volumeKnob; + FloatModel m_volumeRatio; + + QPoint m_lastMousePos; //!< mouse position in last mouseMoveEvent + float m_leftOver; + bool m_buttonPressed; + + float m_totalAngle; + int m_angle; + QImage m_cache; + + // Styled knob stuff, could break out + QPointF m_centerPoint; + float m_innerRadius; + float m_outerRadius; + float m_lineWidth; + QColor m_outerColor; + + QColor m_lineActiveColor; + QColor m_lineInactiveColor; + QColor m_arcActiveColor; + QColor m_arcInactiveColor; + + QColor m_textColor; + + knobTypesTemp m_knobNum; + +} ; + + +} // namespace lmms::gui + +#endif // LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8446d3fce..ee80359ec 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -101,6 +101,7 @@ SET(LMMS_SRCS gui/widgets/ComboBox.cpp gui/widgets/CustomTextKnob.cpp gui/widgets/Fader.cpp + gui/widgets/FloatModelEditorBase.cpp gui/widgets/Graph.cpp gui/widgets/GroupBox.cpp gui/widgets/Knob.cpp diff --git a/src/gui/LadspaWidgetFactory.cpp b/src/gui/LadspaWidgetFactory.cpp index c900095fa..ac1f9beb3 100644 --- a/src/gui/LadspaWidgetFactory.cpp +++ b/src/gui/LadspaWidgetFactory.cpp @@ -31,6 +31,8 @@ #include "LadspaBase.h" #include "BarModelEditor.h" +// TODO Only for testing! Remove! +#include "FloatModelEditorBase.h" #include "LedCheckBox.h" #include "TempoSyncKnob.h" @@ -65,7 +67,15 @@ QWidget * LadspaWidgetFactory::createWidget(LadspaControl * ladspaControl, QWidg knob->setModel(ladspaControl->knobModel()); knob->setLabel(name); break;*/ - return new BarModelEditor(name, ladspaControl->knobModel(), parent); + { + FloatModelEditorBase * fme = new FloatModelEditorBase(knobBright_26Temp, parent, name); + fme->setModel(ladspaControl->knobModel()); + fme->setLabel(name); + fme->setHintText(QObject::tr("Value:"), ""); + return fme; + } + // FloatModelEditorBase + //return new BarModelEditor(name, ladspaControl->knobModel(), parent); case TIME: knob = new TempoSyncKnob(knobBright_26, parent, name); diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp new file mode 100644 index 000000000..e9204a7a9 --- /dev/null +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -0,0 +1,873 @@ +/* + * FloatModelEditorBase.cpp - Base editor for float models + * + * Copyright (c) 2004-2014 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * 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 "FloatModelEditorBase.h" + +#include +#include +#include +#include +#include +#include + +#ifndef __USE_XOPEN +#define __USE_XOPEN +#endif + +#include "lmms_math.h" +#include "CaptionMenu.h" +#include "ConfigManager.h" +#include "ControllerConnection.h" +#include "DeprecationHelper.h" +#include "embed.h" +#include "gui_templates.h" +#include "GuiApplication.h" +#include "LocaleHelper.h" +#include "MainWindow.h" +#include "ProjectJournal.h" +#include "SimpleTextFloat.h" +#include "StringPairDrag.h" + + +namespace lmms::gui +{ + +SimpleTextFloat * FloatModelEditorBase::s_textFloat = nullptr; + + + + +FloatModelEditorBase::FloatModelEditorBase( knobTypesTemp _knob_num, QWidget * _parent, const QString & _name ) : + QWidget( _parent ), + FloatModelView( new FloatModel( 0, 0, 0, 1, nullptr, _name, true ), this ), + m_label( "" ), + m_isHtmlLabel(false), + m_tdRenderer(nullptr), + m_volumeKnob( false ), + m_volumeRatio( 100.0, 0.0, 1000000.0 ), + m_buttonPressed( false ), + m_angle( -10 ), + m_lineWidth( 0 ), + m_textColor( 255, 255, 255 ), + m_knobNum( _knob_num ) +{ + initUi( _name ); +} + +FloatModelEditorBase::FloatModelEditorBase( QWidget * _parent, const QString & _name ) : + FloatModelEditorBase( knobBright_26Temp, _parent, _name ) +{ +} + + + + +void FloatModelEditorBase::initUi( const QString & _name ) +{ + if( s_textFloat == nullptr ) + { + s_textFloat = new SimpleTextFloat; + } + + setWindowTitle( _name ); + + onKnobNumUpdated(); + setTotalAngle( 270.0f ); + setInnerRadius( 1.0f ); + setOuterRadius( 10.0f ); + setFocusPolicy( Qt::ClickFocus ); + + // This is a workaround to enable style sheets for knobs which are not styled knobs. + // + // It works as follows: the palette colors that are assigned as the line color previously + // had been hard coded in the drawKnob method for the different knob types. Now the + // drawKnob method uses the line color to draw the lines. By assigning the palette colors + // as the line colors here the knob lines will be drawn in this color unless the stylesheet + // overrides that color. + switch (knobNum()) + { + case knobSmall_17Temp: + case knobBright_26Temp: + case knobDark_28Temp: + m_lineActiveColor = QApplication::palette().color(QPalette::Active, QPalette::WindowText); + m_arcActiveColor = QColor(QApplication::palette().color( + QPalette::Active, QPalette::WindowText)); + m_arcActiveColor.setAlpha(70); + break; + case knobVintage_32Temp: + m_lineActiveColor = QApplication::palette().color(QPalette::Active, QPalette::Shadow); + m_arcActiveColor = QColor(QApplication::palette().color( + QPalette::Active, QPalette::Shadow)); + m_arcActiveColor.setAlpha(70); + break; + default: + break; + } + + doConnections(); +} + + + + +void FloatModelEditorBase::onKnobNumUpdated() +{ + if( m_knobNum != knobStyledTemp ) + { + QString knobFilename; + switch (m_knobNum) + { + case knobDark_28Temp: + knobFilename = "knob01"; + break; + case knobBright_26Temp: + knobFilename = "knob02"; + break; + case knobSmall_17Temp: + knobFilename = "knob03"; + break; + case knobVintage_32Temp: + knobFilename = "knob05"; + break; + case knobStyledTemp: // only here to stop the compiler from complaining + break; + } + + // If knobFilename is still empty here we should get the fallback pixmap of size 1x1 + m_knobPixmap = std::make_unique(QPixmap(embed::getIconPixmap(knobFilename.toUtf8().constData()))); + if (!this->isEnabled()) + { + convertPixmapToGrayScaleTemp(*m_knobPixmap.get()); + } + setFixedSize( m_knobPixmap->width(), m_knobPixmap->height() ); + } +} + + + + +void FloatModelEditorBase::setLabel( const QString & txt ) +{ + m_label = txt; + m_isHtmlLabel = false; + if( m_knobPixmap ) + { + setFixedSize(qMax( m_knobPixmap->width(), + horizontalAdvance(QFontMetrics(pointSizeF(font(), 6.5)), m_label)), + m_knobPixmap->height() + 10); + } + + update(); +} + + +void FloatModelEditorBase::setHtmlLabel(const QString &htmltxt) +{ + m_label = htmltxt; + m_isHtmlLabel = true; + // Put the rendered HTML content into cache + if (!m_tdRenderer) + { + m_tdRenderer = new QTextDocument(this); + } + + m_tdRenderer->setHtml(QString("%2").arg(textColor().name(), m_label)); + + if (m_knobPixmap) + { + setFixedSize(m_knobPixmap->width(), + m_knobPixmap->height() + 15); + } + + update(); +} + + + + +void FloatModelEditorBase::setTotalAngle( float angle ) +{ + if( angle < 10.0 ) + { + m_totalAngle = 10.0; + } + else + { + m_totalAngle = angle; + } + + update(); +} + + + + +float FloatModelEditorBase::innerRadius() const +{ + return m_innerRadius; +} + + + +void FloatModelEditorBase::setInnerRadius( float r ) +{ + m_innerRadius = r; +} + + + +float FloatModelEditorBase::outerRadius() const +{ + return m_outerRadius; +} + + + +void FloatModelEditorBase::setOuterRadius( float r ) +{ + m_outerRadius = r; +} + + + + +knobTypesTemp FloatModelEditorBase::knobNum() const +{ + return m_knobNum; +} + + + + +void FloatModelEditorBase::setknobNum( knobTypesTemp k ) +{ + if( m_knobNum != k ) + { + m_knobNum = k; + onKnobNumUpdated(); + } +} + + + + +QPointF FloatModelEditorBase::centerPoint() const +{ + return m_centerPoint; +} + + + +float FloatModelEditorBase::centerPointX() const +{ + return m_centerPoint.x(); +} + + + +void FloatModelEditorBase::setCenterPointX( float c ) +{ + m_centerPoint.setX( c ); +} + + + +float FloatModelEditorBase::centerPointY() const +{ + return m_centerPoint.y(); +} + + + +void FloatModelEditorBase::setCenterPointY( float c ) +{ + m_centerPoint.setY( c ); +} + + + +float FloatModelEditorBase::lineWidth() const +{ + return m_lineWidth; +} + + + +void FloatModelEditorBase::setLineWidth( float w ) +{ + m_lineWidth = w; +} + + + +QColor FloatModelEditorBase::outerColor() const +{ + return m_outerColor; +} + + + +void FloatModelEditorBase::setOuterColor( const QColor & c ) +{ + m_outerColor = c; +} + + + +QColor FloatModelEditorBase::textColor() const +{ + return m_textColor; +} + + + +void FloatModelEditorBase::setTextColor( const QColor & c ) +{ + m_textColor = c; +} + + + +QLineF FloatModelEditorBase::calculateLine( const QPointF & _mid, float _radius, float _innerRadius ) const +{ + const float rarc = m_angle * F_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 FloatModelEditorBase::updateAngle() +{ + int angle = 0; + if( model() && model()->maxValue() != model()->minValue() ) + { + angle = angleFromValue( model()->inverseScaledValue( model()->value() ), model()->minValue(), model()->maxValue(), m_totalAngle ); + } + if( qAbs( angle - m_angle ) > 0 ) + { + m_angle = angle; + return true; + } + return false; +} + + + + +void FloatModelEditorBase::drawKnob( QPainter * _p ) +{ + bool enabled = this->isEnabled(); + QColor currentArcColor = enabled ? m_arcActiveColor : m_arcInactiveColor; + QColor currentLineColor = enabled ? m_lineActiveColor : m_lineInactiveColor; + + 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 == knobStyledTemp ) + { + p.setRenderHint( QPainter::Antialiasing ); + + // Perhaps this can move to setOuterRadius() + if( m_outerColor.isValid() ) + { + 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.setRenderHint( QPainter::Antialiasing ); + + const int centerAngle = angleFromValue( model()->inverseScaledValue( model()->centerValue() ), model()->minValue(), model()->maxValue(), m_totalAngle ); + + const int arcLineWidth = 2; + const int arcRectSize = m_knobPixmap->width() - arcLineWidth; + + p.setPen(QPen(currentArcColor, 2)); + p.drawArc( mid.x() - arcRectSize/2, 1, arcRectSize, arcRectSize, 315*16, 16*m_totalAngle ); + + p.setPen(QPen(currentLineColor, 2)); + switch( m_knobNum ) + { + case knobSmall_17Temp: + { + p.drawLine( calculateLine( mid, radius-2 ) ); + break; + } + case knobBright_26Temp: + { + p.drawLine( calculateLine( mid, radius-5 ) ); + break; + } + case knobDark_28Temp: + { + 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 knobVintage_32Temp: + { + p.drawLine( calculateLine( mid, radius-2, 2 ) ); + break; + } + case knobStyledTemp: + break; + } + + p.drawArc( mid.x() - arcRectSize/2, 1, arcRectSize, arcRectSize, (90-centerAngle)*16, -16*(m_angle-centerAngle) ); + + p.end(); + + _p->drawImage( 0, 0, m_cache ); +} + +float FloatModelEditorBase::getValue( const QPoint & _p ) +{ + float value; + + // knob value increase is linear to mouse movement + value = .4f * _p.y(); + + // if shift pressed we want slower movement + if( getGUI()->mainWindow()->isShiftPressed() ) + { + value /= 4.0f; + value = qBound( -4.0f, value, 4.0f ); + } + return value * pageSize(); +} + + + + +void FloatModelEditorBase::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( nullptr ); + + CaptionMenu contextMenu( model()->displayName(), this ); + addDefaultActions( &contextMenu ); + contextMenu.addAction( QPixmap(), + model()->isScaleLogarithmic() ? tr( "Set linear" ) : tr( "Set logarithmic" ), + this, SLOT(toggleScale())); + contextMenu.addSeparator(); + contextMenu.exec( QCursor::pos() ); +} + + +void FloatModelEditorBase::toggleScale() +{ + model()->setScaleLogarithmic( ! model()->isScaleLogarithmic() ); + update(); +} + + + +void FloatModelEditorBase::dragEnterEvent( QDragEnterEvent * _dee ) +{ + StringPairDrag::processDragEnterEvent( _dee, "float_value," + "automatable_model" ); +} + + + + +void FloatModelEditorBase::dropEvent( QDropEvent * _de ) +{ + QString type = StringPairDrag::decodeKey( _de ); + QString val = StringPairDrag::decodeValue( _de ); + if( type == "float_value" ) + { + model()->setValue( LocaleHelper::toFloat(val) ); + _de->accept(); + } + else if( type == "automatable_model" ) + { + auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); + if( mod != nullptr ) + { + AutomatableModel::linkModels( model(), mod ); + mod->setValue( model()->value() ); + } + } +} + + + + +void FloatModelEditorBase::mousePressEvent( QMouseEvent * _me ) +{ + if( _me->button() == Qt::LeftButton && + ! ( _me->modifiers() & Qt::ControlModifier ) && + ! ( _me->modifiers() & Qt::ShiftModifier ) ) + { + AutomatableModel *thisModel = model(); + if( thisModel ) + { + thisModel->addJournalCheckPoint(); + thisModel->saveJournallingState( false ); + } + + const QPoint & p = _me->pos(); + m_lastMousePos = p; + m_leftOver = 0.0f; + + emit sliderPressed(); + + s_textFloat->setText( displayValue() ); + s_textFloat->moveGlobal( this, + QPoint( width() + 2, 0 ) ); + s_textFloat->show(); + m_buttonPressed = true; + } + else if( _me->button() == Qt::LeftButton && + (_me->modifiers() & Qt::ShiftModifier) ) + { + new StringPairDrag( "float_value", + QString::number( model()->value() ), + QPixmap(), this ); + } + else + { + FloatModelView::mousePressEvent( _me ); + } +} + + + + +void FloatModelEditorBase::mouseMoveEvent( QMouseEvent * _me ) +{ + if( m_buttonPressed && _me->pos() != m_lastMousePos ) + { + // knob position is changed depending on last mouse position + setPosition( _me->pos() - m_lastMousePos ); + emit sliderMoved( model()->value() ); + // original position for next time is current position + m_lastMousePos = _me->pos(); + } + s_textFloat->setText( displayValue() ); +} + + + + +void FloatModelEditorBase::mouseReleaseEvent( QMouseEvent* event ) +{ + if( event && event->button() == Qt::LeftButton ) + { + AutomatableModel *thisModel = model(); + if( thisModel ) + { + thisModel->restoreJournallingState(); + } + } + + m_buttonPressed = false; + + emit sliderReleased(); + + QApplication::restoreOverrideCursor(); + + s_textFloat->hide(); +} + + + + +void FloatModelEditorBase::focusOutEvent( QFocusEvent * _fe ) +{ + // make sure we don't loose mouse release event + mouseReleaseEvent( nullptr ); + QWidget::focusOutEvent( _fe ); +} + + + + +void FloatModelEditorBase::mouseDoubleClickEvent( QMouseEvent * ) +{ + enterValue(); +} + + + + +void FloatModelEditorBase::paintEvent( QPaintEvent * _me ) +{ + QPainter p( this ); + + drawKnob( &p ); + if( !m_label.isEmpty() ) + { + if (!m_isHtmlLabel) + { + p.setFont(pointSizeF(p.font(), 6.5)); + p.setPen(textColor()); + p.drawText(width() / 2 - + horizontalAdvance(p.fontMetrics(), m_label) / 2, + height() - 2, m_label); + } + else + { + m_tdRenderer->setDefaultFont(pointSizeF(p.font(), 6.5)); + p.translate((width() - m_tdRenderer->idealWidth()) / 2, (height() - m_tdRenderer->pageSize().height()) / 2); + m_tdRenderer->drawContents(&p); + } + } +} + + + + +void FloatModelEditorBase::wheelEvent(QWheelEvent * we) +{ + we->accept(); + const float stepMult = model()->range() / 2000 / model()->step(); + const int inc = ((we->angleDelta().y() > 0 ) ? 1 : -1) * ((stepMult < 1 ) ? 1 : stepMult); + model()->incValue( inc ); + + + s_textFloat->setText( displayValue() ); + s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); + s_textFloat->setVisibilityTimeOut( 1000 ); + + emit sliderMoved( model()->value() ); +} + + + + +void FloatModelEditorBase::setPosition( const QPoint & _p ) +{ + const float value = getValue( _p ) + m_leftOver; + const auto step = model()->step(); + const float oldValue = model()->value(); + + + + if( model()->isScaleLogarithmic() ) // logarithmic code + { + const float pos = model()->minValue() < 0 + ? oldValue / qMax( qAbs( model()->maxValue() ), qAbs( model()->minValue() ) ) + : ( oldValue - model()->minValue() ) / model()->range(); + const float ratio = 0.1f + qAbs( pos ) * 15.f; + float newValue = value * ratio; + if( qAbs( newValue ) >= step ) + { + float roundedValue = qRound( ( oldValue - value ) / step ) * step; + model()->setValue( roundedValue ); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } + + else // linear code + { + if( qAbs( value ) >= step ) + { + float roundedValue = qRound( ( oldValue - value ) / step ) * step; + model()->setValue( roundedValue ); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } +} + + + + +void FloatModelEditorBase::enterValue() +{ + bool ok; + float new_val; + + if( isVolumeKnob() && + ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ) + { + new_val = QInputDialog::getDouble( + this, tr( "Set value" ), + tr( "Please enter a new value between " + "-96.0 dBFS and 6.0 dBFS:" ), + ampToDbfs( model()->getRoundedValue() / 100.0 ), + -96.0, 6.0, model()->getDigitCount(), &ok ); + if( new_val <= -96.0 ) + { + new_val = 0.0f; + } + else + { + new_val = dbfsToAmp( new_val ) * 100.0; + } + } + else + { + new_val = QInputDialog::getDouble( + this, tr( "Set value" ), + tr( "Please enter a new value between " + "%1 and %2:" ). + arg( model()->minValue() ). + arg( model()->maxValue() ), + model()->getRoundedValue(), + model()->minValue(), + model()->maxValue(), model()->getDigitCount(), &ok ); + } + + if( ok ) + { + model()->setValue( new_val ); + } +} + + + + +void FloatModelEditorBase::friendlyUpdate() +{ + if (model() && (model()->controllerConnection() == nullptr || + model()->controllerConnection()->getController()->frequentUpdates() == false || + Controller::runningFrames() % (256*4) == 0)) + { + update(); + } +} + + + + +QString FloatModelEditorBase::displayValue() const +{ + if( isVolumeKnob() && + ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ) + { + return m_description.trimmed() + QString( " %1 dBFS" ). + arg( ampToDbfs( model()->getRoundedValue() / volumeRatio() ), + 3, 'f', 2 ); + } + return m_description.trimmed() + QString( " %1" ). + arg( model()->getRoundedValue() ) + m_unit; +} + + + + +void FloatModelEditorBase::doConnections() +{ + if( model() != nullptr ) + { + QObject::connect( model(), SIGNAL(dataChanged()), + this, SLOT(friendlyUpdate())); + + QObject::connect( model(), SIGNAL(propertiesChanged()), + this, SLOT(update())); + } +} + + +void FloatModelEditorBase::changeEvent(QEvent * ev) +{ + if (ev->type() == QEvent::EnabledChange) + { + onKnobNumUpdated(); + if (!m_label.isEmpty()) + { + setLabel(m_label); + } + m_cache = QImage(); + update(); + } +} + + +void convertPixmapToGrayScaleTemp(QPixmap& pixMap) +{ + QImage temp = pixMap.toImage().convertToFormat(QImage::Format_ARGB32); + for (int i = 0; i < temp.height(); ++i) + { + for (int j = 0; j < temp.width(); ++j) + { + const auto pix = temp.pixelColor(i, j); + const auto gscale = 0.2126 * pix.redF() + 0.7152 * pix.greenF() + 0.0722 * pix.blueF(); + const auto pixGray = QColor::fromRgbF(gscale, gscale, gscale, pix.alphaF()); + temp.setPixelColor(i, j, pixGray); + } + } + pixMap.convertFromImage(temp); +} + + +} // namespace lmms::gui