diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 2b853395d..c4fe0e153 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -975,6 +975,11 @@ lmms--gui--CompressorControlDialog lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--BarModelEditor { + qproperty-backgroundBrush: rgba(28, 73, 51, 255); + qproperty-barBrush: rgba(17, 136, 71, 255); +} + /* palette information */ lmms--gui--LmmsPalette { diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 9cbf5885b..80d56a4bb 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -1019,6 +1019,11 @@ lmms--gui--CompressorControlDialog lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--BarModelEditor { + qproperty-backgroundBrush: rgba(28, 73, 51, 255); + qproperty-barBrush: rgba(17, 136, 71, 255); +} + /* palette information */ lmms--gui--LmmsPalette { diff --git a/include/BarModelEditor.h b/include/BarModelEditor.h new file mode 100644 index 000000000..79a320a7d --- /dev/null +++ b/include/BarModelEditor.h @@ -0,0 +1,76 @@ +/* + * BarModelEditor.h - edit model values using a bar display + * + * Copyright (c) 2023-now 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. + * + */ + +#pragma once + +#ifndef LMMS_GUI_BAR_MODEL_EDITOR_H +#define LMMS_GUI_BAR_MODEL_EDITOR_H + +#include "FloatModelEditorBase.h" + + +namespace lmms::gui +{ + +class LMMS_EXPORT BarModelEditor : public FloatModelEditorBase +{ + Q_OBJECT + +public: + Q_PROPERTY(QBrush backgroundBrush READ getBackgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush barBrush READ getBarBrush WRITE setBarBrush) + Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor) + + BarModelEditor(QString text, FloatModel * floatModel, QWidget * parent = nullptr); + + // Define how the widget will behave in a layout + QSizePolicy sizePolicy() const; + + QSize minimumSizeHint() const override; + + QSize sizeHint() const override; + + QBrush const & getBackgroundBrush() const; + void setBackgroundBrush(QBrush const & backgroundBrush); + + QBrush const & getBarBrush() const; + void setBarBrush(QBrush const & barBrush); + + QColor const & getTextColor() const; + void setTextColor(QColor const & textColor); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString const m_text; + + QBrush m_backgroundBrush; + QBrush m_barBrush; + QColor m_textColor; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_BAR_MODEL_EDITOR_H diff --git a/include/FloatModelEditorBase.h b/include/FloatModelEditorBase.h new file mode 100644 index 000000000..72f1450de --- /dev/null +++ b/include/FloatModelEditorBase.h @@ -0,0 +1,121 @@ +/* + * 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 "AutomatableModelView.h" + + +namespace lmms::gui +{ + +class SimpleTextFloat; + +class LMMS_EXPORT FloatModelEditorBase : public QWidget, public FloatModelView +{ + Q_OBJECT + + mapPropertyFromModel(bool, isVolumeKnob, setVolumeKnob, m_volumeKnob); + mapPropertyFromModel(float, volumeRatio, setVolumeRatio, m_volumeRatio); + + void initUi(const QString & name); //!< to be called by ctors + +public: + enum class DirectionOfManipulation + { + Vertical, + Horizontal + }; + + FloatModelEditorBase(DirectionOfManipulation directionOfManipulation = DirectionOfManipulation::Vertical, 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); + } + +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 enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + + virtual float getValue(const QPoint & p); + +private slots: + virtual void enterValue(); + void friendlyUpdate(); + void toggleScale(); + +private: + virtual QString displayValue() const; + + void doConnections() override; + + void showTextFloat(int msecBeforeDisplay, int msecDisplayTime); + void setPosition(const QPoint & p); + + inline float pageSize() const + { + return (model()->maxValue() - model()->minValue()) / 100.0f; + } + + static SimpleTextFloat * s_textFloat; + + BoolModel m_volumeKnob; + FloatModel m_volumeRatio; + + QPoint m_lastMousePos; //!< mouse position in last mouseMoveEvent + float m_leftOver; + bool m_buttonPressed; + + DirectionOfManipulation m_directionOfManipulation; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H diff --git a/include/Knob.h b/include/Knob.h index d5739bb1c..3c3339a6f 100644 --- a/include/Knob.h +++ b/include/Knob.h @@ -26,12 +26,9 @@ #define LMMS_GUI_KNOB_H #include -#include -#include -#include #include -#include "AutomatableModelView.h" +#include "FloatModelEditorBase.h" class QPixmap; @@ -50,7 +47,7 @@ enum class KnobType void convertPixmapToGrayScale(QPixmap &pixMap); -class LMMS_EXPORT Knob : public QWidget, public FloatModelView +class LMMS_EXPORT Knob : public FloatModelEditorBase { Q_OBJECT Q_ENUMS( KnobType ) @@ -72,9 +69,6 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView 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(KnobType knobNum READ knobNum WRITE setknobNum) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) @@ -87,13 +81,6 @@ public: Knob( QWidget * _parent = nullptr, const QString & _name = QString() ); //!< default ctor Knob( const Knob& 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 ); @@ -125,46 +112,16 @@ public: 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; - void enterEvent(QEvent *event) override; - void leaveEvent(QEvent *event) 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 showTextFloat(int msecBeforeDisplay, int msecDisplayTime); - void setPosition( const QPoint & _p ); bool updateAngle(); int angleFromValue( float value, float minValue, float maxValue, float totalAngle ) const @@ -172,25 +129,11 @@ private: 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; @@ -211,9 +154,7 @@ private: QColor m_textColor; KnobType m_knobNum; - -} ; - +}; } // namespace lmms::gui diff --git a/include/LadspaControl.h b/include/LadspaControl.h index e4f0cd745..8af8f9923 100644 --- a/include/LadspaControl.h +++ b/include/LadspaControl.h @@ -41,6 +41,7 @@ namespace gui { class LadspaControlView; +class LadspaMatrixControlDialog; } // namespace gui @@ -125,6 +126,7 @@ private: friend class gui::LadspaControlView; + friend class gui::LadspaMatrixControlDialog; } ; diff --git a/include/LedCheckBox.h b/include/LedCheckBox.h index e3629e143..aaafffaa1 100644 --- a/include/LedCheckBox.h +++ b/include/LedCheckBox.h @@ -47,10 +47,12 @@ public: LedCheckBox( const QString & _txt, QWidget * _parent, const QString & _name = QString(), - LedColor _color = LedColor::Yellow ); + LedColor _color = LedColor::Yellow, + bool legacyMode = true); LedCheckBox( QWidget * _parent, const QString & _name = QString(), - LedColor _color = LedColor::Yellow ); + LedColor _color = LedColor::Yellow, + bool legacyMode = true); ~LedCheckBox() override; @@ -74,8 +76,13 @@ private: QString m_text; + bool m_legacyMode; + void initUi( LedColor _color ); //!< to be called by ctors + void onTextUpdated(); //!< to be called when you updated @a m_text + void paintLegacy(QPaintEvent * p); + void paintNonLegacy(QPaintEvent * p); } ; diff --git a/include/TempoSyncBarModelEditor.h b/include/TempoSyncBarModelEditor.h new file mode 100644 index 000000000..c1b0bb26f --- /dev/null +++ b/include/TempoSyncBarModelEditor.h @@ -0,0 +1,90 @@ +/* + * TempoSyncBarModelEditor.h - adds bpm to ms conversion for the bar editor class + * + * Copyright (c) 2005-2008 Danny McRae + * Copyright (c) 2009-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. + * + */ + +#ifndef LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H +#define LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H + +#include +#include + +#include "BarModelEditor.h" +#include "TempoSyncKnobModel.h" + +namespace lmms::gui +{ + +class MeterDialog; + +class LMMS_EXPORT TempoSyncBarModelEditor : public BarModelEditor +{ + Q_OBJECT +public: + TempoSyncBarModelEditor(QString text, FloatModel * floatModel, QWidget * parent = nullptr); + ~TempoSyncBarModelEditor() override; + + const QString & syncDescription(); + void setSyncDescription(const QString & new_description); + + const QPixmap & syncIcon(); + void setSyncIcon(const QPixmap & new_pix); + + TempoSyncKnobModel * model() + { + return castModel(); + } + + void modelChanged() override; + + +signals: + void syncDescriptionChanged(const QString & new_description); + void syncIconChanged(); + + +protected: + void contextMenuEvent(QContextMenuEvent * me) override; + + +protected slots: + void updateDescAndIcon(); + void showCustom(); + + +private: + void updateTextDescription(); + void updateIcon(); + + +private: + QPixmap m_tempoSyncIcon; + QString m_tempoSyncDescription; + + QPointer m_custom; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H diff --git a/include/TempoSyncKnobModel.h b/include/TempoSyncKnobModel.h index 5cd2db067..af92a58aa 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -82,6 +82,9 @@ public: void setScale( float _new_scale ); + MeterModel & getCustomMeterModel() { return m_custom; } + MeterModel const & getCustomMeterModel() const { return m_custom; } + signals: void syncModeChanged( lmms::TempoSyncKnobModel::SyncMode _new_mode ); void scaleChanged( float _new_scale ); diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index a01eb950f..202a8dd04 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -1,6 +1,6 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaSubPluginFeatures.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h EMBEDDED_RESOURCES logo.png) +BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaMatrixControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaWidgetFactory.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h LadspaSubPluginFeatures.h LadspaWidgetFactory.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h EMBEDDED_RESOURCES logo.png) SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ladspa") diff --git a/plugins/LadspaEffect/LadspaControls.h b/plugins/LadspaEffect/LadspaControls.h index 2bef0c856..c91f3badd 100644 --- a/plugins/LadspaEffect/LadspaControls.h +++ b/plugins/LadspaEffect/LadspaControls.h @@ -27,6 +27,7 @@ #include "EffectControls.h" #include "LadspaControlDialog.h" +#include "LadspaMatrixControlDialog.h" namespace lmms { @@ -59,7 +60,7 @@ public: gui::EffectControlDialog* createView() override { - return new gui::LadspaControlDialog( this ); + return new gui::LadspaMatrixControlDialog( this ); } @@ -79,6 +80,7 @@ private: friend class gui::LadspaControlDialog; + friend class gui::LadspaMatrixControlDialog; friend class LadspaEffect; diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp new file mode 100644 index 000000000..88810cee6 --- /dev/null +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp @@ -0,0 +1,243 @@ +/* + * LadspaMatrixControlDialog.h - Dialog for displaying and editing control port + * values for LADSPA plugins in a matrix display + * + * Copyright (c) 2015 Michael Gregorius + * + * This file is part of LMMS - http://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 +#include +#include +#include +#include +#include +#include + + +#include "LadspaBase.h" +#include "LadspaControl.h" +#include "LadspaEffect.h" +#include "LadspaMatrixControlDialog.h" +#include "LadspaWidgetFactory.h" +#include "LadspaControlView.h" +#include "LedCheckBox.h" + +#include "GuiApplication.h" +#include "MainWindow.h" + + +namespace lmms::gui +{ + +LadspaMatrixControlDialog::LadspaMatrixControlDialog(LadspaControls * ladspaControls) : + EffectControlDialog(ladspaControls), + m_scrollArea(nullptr), + m_stereoLink(nullptr) +{ + QVBoxLayout * mainLayout = new QVBoxLayout(this); + + m_scrollArea = new QScrollArea(this); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setFrameShape(QFrame::NoFrame); + // Set to always on so that the elements do not move around when the + // scroll bar is hidden or shown. + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + // Add a scroll area that grows + mainLayout->addWidget(m_scrollArea, 1); + + // Populate the parameter matrix and put it into the scroll area + updateEffectView(ladspaControls); + + // Add button to link all channels if there's more than one channel + if (getChannelCount() > 1) + { + mainLayout->addSpacing(3); + + m_stereoLink = new LedCheckBox(tr("Link Channels"), this, QString(), LedCheckBox::LedColor::Green, false); + m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel); + mainLayout->addWidget(m_stereoLink, 0, Qt::AlignCenter); + } +} + +bool LadspaMatrixControlDialog::isResizable() const +{ + return true; +} + +bool LadspaMatrixControlDialog::needsLinkColumn() const +{ + LadspaControls * ladspaControls = getLadspaControls(); + + ch_cnt_t const channelCount = getChannelCount(); + for (ch_cnt_t i = 0; i < channelCount; ++i) + { + // Create a const reference so that the C++11 based for loop does not detach the Qt container + auto const & currentControls = ladspaControls->m_controls[i]; + for (auto ladspaControl : currentControls) + { + if (ladspaControl->m_link) + { + return true; + } + } + } + + return false; +} + +void LadspaMatrixControlDialog::arrangeControls(QWidget * parent, QGridLayout* gridLayout) +{ + LadspaControls * ladspaControls = getLadspaControls(); + + int const headerRow = 0; + int const linkColumn = 0; + + bool const linkColumnNeeded = needsLinkColumn(); + if (linkColumnNeeded) + { + gridLayout->addWidget(new QLabel("" + tr("Link") + "", parent), headerRow, linkColumn, Qt::AlignHCenter); + + // If there's a link column then it should not stretch + gridLayout->setColumnStretch(linkColumn, 0); + } + + int const channelStartColumn = linkColumnNeeded ? 1 : 0; + + // The header row should not grow vertically + gridLayout->setRowStretch(0, 0); + + // Records the maximum row with parameters so that we can add a vertical spacer after that row + int maxRow = 0; + + // Iterate the channels and add widgets for each control + // Note: the code assumes that all channels have the same structure, i.e. that all channels + // have the same number of parameters which are in the same order. + ch_cnt_t const numberOfChannels = getChannelCount(); + for (ch_cnt_t i = 0; i < numberOfChannels; ++i) + { + int currentChannelColumn = channelStartColumn + i; + gridLayout->setColumnStretch(currentChannelColumn, 1); + + // First add the channel header with the channel number + gridLayout->addWidget(new QLabel("" + tr("Channel %1").arg(QString::number(i + 1)) + "", parent), headerRow, currentChannelColumn, Qt::AlignHCenter); + + int currentRow = 1; + + if (i == 0) + { + // Configure the current parameter row to not stretch. + // Only do this once, i.e. when working with the first channel. + gridLayout->setRowStretch(currentRow, 0); + } + + // Create a const reference so that the C++11 based for loop does not detach the Qt container + auto const & currentControls = ladspaControls->m_controls[i]; + for (auto ladspaControl : currentControls) + { + // Only use the first channel to determine if we need to add link controls + if (i == 0 && ladspaControl->m_link) + { + LedCheckBox * linkCheckBox = new LedCheckBox("", parent, "", LedCheckBox::LedColor::Green); + linkCheckBox->setModel(&ladspaControl->m_linkEnabledModel); + linkCheckBox->setToolTip(tr("Link channels")); + gridLayout->addWidget(linkCheckBox, currentRow, linkColumn, Qt::AlignHCenter); + } + + QWidget * controlWidget = LadspaWidgetFactory::createWidget(ladspaControl, this); + if (controlWidget) + { + gridLayout->addWidget(controlWidget, currentRow, currentChannelColumn); + } + + // Record the maximum row so that we add a vertical spacer after that row + maxRow = std::max(maxRow, currentRow); + + ++currentRow; + } + } + + // Add a spacer item after the maximum row + QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + gridLayout->addItem(spacer, maxRow + 1, 0); +} + +QWidget * LadspaMatrixControlDialog::createMatrixWidget() +{ + QWidget *widget = new QWidget(this); + QGridLayout *gridLayout = new QGridLayout(widget); + gridLayout->setMargin(0); + widget->setLayout(gridLayout); + + arrangeControls(widget, gridLayout); + + return widget; +} + +void LadspaMatrixControlDialog::updateEffectView(LadspaControls * ladspaControls) +{ + m_effectControls = ladspaControls; + + // No need to delete the existing widget as it's deleted + // by the scroll view when we replace it. + QWidget * matrixWidget = createMatrixWidget(); + m_scrollArea->setWidget(matrixWidget); + + // Make sure that the horizontal scroll bar does not show + // From: https://forum.qt.io/topic/13374/solved-qscrollarea-vertical-scroll-only/4 + m_scrollArea->setMinimumWidth(matrixWidget->minimumSizeHint().width() + m_scrollArea->verticalScrollBar()->width()); + + + // Make sure that the widget is shown without a scrollbar whenever possible + // If the widget fits on the workspace we use the height of the widget as the minimum size of the scroll area. + // This will ensure that the scrollbar is not shown initially (and never will be). + // If the widget is larger than the workspace then we want it to mostly cover the workspace. + // + // This is somewhat ugly but I have no idea how to control the initial size of the scroll area otherwise + auto const workspaceSize = getGUI()->mainWindow()->workspace()->viewport()->size(); + // Make sure that we always account a minumum height for the workspace, i.e. that we never compute + // something close to 0 if the LMMS window is very small + int workspaceHeight = qMax(200, static_cast(workspaceSize.height() * 0.9)); + int minOfWidgetAndWorkspace = qMin(matrixWidget->minimumSizeHint().height(), workspaceHeight); + m_scrollArea->setMinimumHeight(minOfWidgetAndWorkspace); + + if (getChannelCount() > 1 && m_stereoLink != nullptr) + { + m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel); + } + + connect(ladspaControls, &LadspaControls::effectModelChanged, + this, &LadspaMatrixControlDialog::updateEffectView, + Qt::DirectConnection); +} + +LadspaControls * LadspaMatrixControlDialog::getLadspaControls() const +{ + return dynamic_cast(m_effectControls); +} + +ch_cnt_t LadspaMatrixControlDialog::getChannelCount() const +{ + return getLadspaControls()->m_processors; +} + +} // namespace lmms::gui diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.h b/plugins/LadspaEffect/LadspaMatrixControlDialog.h new file mode 100644 index 000000000..c5949fa15 --- /dev/null +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.h @@ -0,0 +1,91 @@ +/* + * LadspaMatrixControlDialog.h - Dialog for displaying and editing control port + * values for LADSPA plugins in a matrix display + * + * Copyright (c) 2015 Michael Gregorius + * + * This file is part of LMMS - http://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 LADSPA_MATRIX_CONTROL_DIALOG_H +#define LADSPA_MATRIX_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +#include "lmms_basics.h" + + +class QGridLayout; +class QScrollArea; + +namespace lmms +{ + +class LadspaControls; + +namespace gui +{ + +class LedCheckBox; + + +class LadspaMatrixControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + LadspaMatrixControlDialog(LadspaControls* ctl); + bool isResizable() const override; + +private slots: + void updateEffectView(LadspaControls* ctl); + +private: + /** + * @brief Checks if a link column is needed for the current effect controls. + * @return true if a link column is needed. + */ + bool needsLinkColumn() const; + + /** + * @brief Arranges widgets for the current controls in a grid/matrix layout. + * @param parent The parent of all created widgets + * @param gridLayout The layout into which the controls are organized + */ + void arrangeControls(QWidget * parent, QGridLayout* gridLayout); + + /** + * @brief Creates a widget that holds the widgets of the current controls in a matrix arrangement. + * @param ladspaControls + * @return + */ + QWidget * createMatrixWidget(); + + LadspaControls * getLadspaControls() const; + ch_cnt_t getChannelCount() const; + +private: + QScrollArea* m_scrollArea; + LedCheckBox* m_stereoLink; +}; + +} // namespace gui + +} // namespace lmms + +#endif diff --git a/plugins/LadspaEffect/LadspaWidgetFactory.cpp b/plugins/LadspaEffect/LadspaWidgetFactory.cpp new file mode 100644 index 000000000..0491fd661 --- /dev/null +++ b/plugins/LadspaEffect/LadspaWidgetFactory.cpp @@ -0,0 +1,83 @@ +/* + * LadspaWidgetFactory.cpp - Factory that creates widgets for LADSPA ports + * + * Copyright (c) 2006-2008 Danny McRae + * Copyright (c) 2009 Tobias Doerffel + * Copyright (c) 2015-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 "LadspaWidgetFactory.h" + +#include "LadspaControl.h" +#include "LadspaBase.h" + +#include "BarModelEditor.h" +#include "LedCheckBox.h" +#include "TempoSyncBarModelEditor.h" + +#include +#include + + +namespace lmms::gui +{ + +QWidget * LadspaWidgetFactory::createWidget(LadspaControl * ladspaControl, QWidget * parent) +{ + auto const * port = ladspaControl->port(); + + QString const name = port->name; + + switch (port->data_type) + { + case BufferDataType::Toggled: + { + // The actual check box is put into a widget because LedCheckBox does not play nice with layouts. + // Putting it directly into a grid layout disables the resizing behavior of all columns where it + // appears. Hence we put it into the layout of a widget that knows how to play nice with layouts. + QWidget * widgetWithLayout = new QWidget(parent); + QHBoxLayout * layout = new QHBoxLayout(widgetWithLayout); + layout->setContentsMargins(0, 0, 0, 0); + LedCheckBox * toggle = new LedCheckBox( + name, parent, QString(), LedCheckBox::LedColor::Green, false); + toggle->setModel(ladspaControl->toggledModel()); + layout->addWidget(toggle, 0, Qt::AlignLeft); + + return widgetWithLayout; + } + + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: + return new BarModelEditor(name, ladspaControl->knobModel(), parent); + + case BufferDataType::Time: + return new TempoSyncBarModelEditor(name, ladspaControl->tempoSyncKnobModel(), parent); + + default: + return new QLabel(QObject::tr("%1 (unsupported)").arg(name), parent); + } + + return nullptr; +} + +} // namespace lmms::gui diff --git a/plugins/LadspaEffect/LadspaWidgetFactory.h b/plugins/LadspaEffect/LadspaWidgetFactory.h new file mode 100644 index 000000000..807334d32 --- /dev/null +++ b/plugins/LadspaEffect/LadspaWidgetFactory.h @@ -0,0 +1,49 @@ +/* + * LadspaWidgetFactory.h - Factory that creates widgets for LADSPA ports + * + * 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_LADSPA_WIDGET_FACTORY_H +#define LMMS_GUI_LADSPA_WIDGET_FACTORY_H + + +class QWidget; + +namespace lmms +{ + +class LadspaControl; + +namespace gui +{ + +class LadspaWidgetFactory +{ +public: + static QWidget * createWidget(LadspaControl * ladspaControl, QWidget * parent); +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_LADSPA_WIDGET_FACTORY_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index afed153f9..1e809e9d7 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -94,11 +94,13 @@ SET(LMMS_SRCS gui/widgets/AutomatableButton.cpp gui/widgets/AutomatableSlider.cpp + gui/widgets/BarModelEditor.cpp gui/widgets/CPULoadWidget.cpp gui/widgets/CaptionMenu.cpp 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 @@ -115,6 +117,7 @@ SET(LMMS_SRCS gui/widgets/SimpleTextFloat.cpp gui/widgets/TabBar.cpp gui/widgets/TabWidget.cpp + gui/widgets/TempoSyncBarModelEditor.cpp gui/widgets/TempoSyncKnob.cpp gui/widgets/TextFloat.cpp gui/widgets/TimeDisplayWidget.cpp diff --git a/src/gui/widgets/BarModelEditor.cpp b/src/gui/widgets/BarModelEditor.cpp new file mode 100644 index 000000000..4b02c9634 --- /dev/null +++ b/src/gui/widgets/BarModelEditor.cpp @@ -0,0 +1,117 @@ +#include + +#include +#include + + +namespace lmms::gui +{ + +BarModelEditor::BarModelEditor(QString text, FloatModel * floatModel, QWidget * parent) : + FloatModelEditorBase(DirectionOfManipulation::Horizontal, parent), + m_text(text), + m_backgroundBrush(palette().base()), + m_barBrush(palette().button()), + m_textColor(palette().text().color()) +{ + setModel(floatModel); +} + +QSizePolicy BarModelEditor::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize BarModelEditor::minimumSizeHint() const +{ + auto const fm = fontMetrics(); + return QSize(50, fm.height() + 6); +} + +QSize BarModelEditor::sizeHint() const +{ + return minimumSizeHint(); +} + +QBrush const & BarModelEditor::getBackgroundBrush() const +{ + return m_backgroundBrush; +} + +void BarModelEditor::setBackgroundBrush(QBrush const & backgroundBrush) +{ + m_backgroundBrush = backgroundBrush; +} + +QBrush const & BarModelEditor::getBarBrush() const +{ + return m_barBrush; +} + +void BarModelEditor::setBarBrush(QBrush const & barBrush) +{ + m_barBrush = barBrush; +} + +QColor const & BarModelEditor::getTextColor() const +{ + return m_textColor; +} + +void BarModelEditor::setTextColor(QColor const & textColor) +{ + m_textColor = textColor; +} + +void BarModelEditor::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + auto const * mod = model(); + auto const minValue = mod->minValue(); + auto const maxValue = mod->maxValue(); + auto const range = maxValue - minValue; + + QRect const r = rect(); + + QPainter painter(this); + + // Paint the base rectangle into which the bar and the text go + QBrush const & backgroundBrush = getBackgroundBrush(); + painter.setPen(backgroundBrush.color()); + painter.setBrush(backgroundBrush); + painter.drawRect(r); + + + // Paint the bar + // Compute the percentage as: + // min + x * (max - min) = v <=> x = (v - min) / (max - min) + auto const percentage = range == 0 ? 1. : (mod->value() - minValue) / range; + + int const margin = 3; + QMargins const margins(margin, margin, margin, margin); + QRect const valueRect = r.marginsRemoved(margins); + + QBrush const & barBrush = getBarBrush(); + painter.setPen(barBrush.color()); + painter.setBrush(barBrush); + QPoint const startPoint = valueRect.topLeft(); + QPoint endPoint = valueRect.bottomRight(); + endPoint.setX(startPoint.x() + percentage * (endPoint.x() - startPoint.x())); + + painter.drawRect(QRect(startPoint, endPoint)); + + + // Draw the text into the value rectangle but move it slightly to the right + QRect const textRect = valueRect.marginsRemoved(QMargins(3, 0, 0, 0)); + + // Elide the text if needed + auto const fm = fontMetrics(); + QString const elidedText = fm.elidedText(m_text, Qt::ElideRight, textRect.width()); + + // Now draw the text + painter.setPen(getTextColor()); + painter.drawText(textRect, elidedText); +} + +} // namespace lmms::gui diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp new file mode 100644 index 000000000..7421908e2 --- /dev/null +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -0,0 +1,464 @@ +/* + * 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 + +#ifndef __USE_XOPEN +#define __USE_XOPEN +#endif + +#include "lmms_math.h" +#include "CaptionMenu.h" +#include "ControllerConnection.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(DirectionOfManipulation directionOfManipulation, QWidget * parent, const QString & name) : + QWidget(parent), + FloatModelView(new FloatModel(0, 0, 0, 1, nullptr, name, true), this), + m_volumeKnob(false), + m_volumeRatio(100.0, 0.0, 1000000.0), + m_buttonPressed(false), + m_directionOfManipulation(directionOfManipulation) +{ + initUi(name); +} + + +void FloatModelEditorBase::initUi(const QString & name) +{ + if (s_textFloat == nullptr) + { + s_textFloat = new SimpleTextFloat; + } + + setWindowTitle(name); + + setFocusPolicy(Qt::ClickFocus); + + doConnections(); +} + + +void FloatModelEditorBase::showTextFloat(int msecBeforeDisplay, int msecDisplayTime) +{ + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime); +} + + +float FloatModelEditorBase::getValue(const QPoint & p) +{ + // Find out which direction/coordinate is relevant for this control + int const coordinate = m_directionOfManipulation == DirectionOfManipulation::Vertical ? p.y() : -p.x(); + + // knob value increase is linear to mouse movement + float value = .4f * coordinate; + + // 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(); + + showTextFloat(0, 0); + + 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()); + s_textFloat->show(); +} + + +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::enterEvent(QEvent *event) +{ + showTextFloat(700, 2000); +} + + +void FloatModelEditorBase::leaveEvent(QEvent *event) +{ + 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 *) +{ + QPainter p(this); + + QColor const foreground(3, 94, 97); + + auto const * mod = model(); + auto const minValue = mod->minValue(); + auto const maxValue = mod->maxValue(); + auto const range = maxValue - minValue; + + // Compute the percentage + // min + x * (max - min) = v <=> x = (v - min) / (max - min) + auto const percentage = range == 0 ? 1. : (mod->value() - minValue) / range; + + QRect r = rect(); + p.setPen(foreground); + p.setBrush(foreground); + p.drawRect(QRect(r.topLeft(), QPoint(r.width() * percentage, r.height()))); +} + + +void FloatModelEditorBase::wheelEvent(QWheelEvent * we) +{ + we->accept(); + const int deltaY = we->angleDelta().y(); + float direction = deltaY > 0 ? 1 : -1; + + auto * m = model(); + float const step = m->step(); + float const range = m->range(); + + // This is the default number of steps or mouse wheel events that it takes to sweep + // from the lowest value to the highest value. + // It might be modified if the user presses modifier keys. See below. + float numberOfStepsForFullSweep = 100.; + + auto const modKeys = we->modifiers(); + if (modKeys == Qt::ShiftModifier) + { + // The shift is intended to go through the values in very coarse steps as in: + // "Shift into overdrive" + numberOfStepsForFullSweep = 10; + } + else if (modKeys == Qt::ControlModifier) + { + // The control key gives more control, i.e. it enables more fine-grained adjustments + numberOfStepsForFullSweep = 1000; + } + else if (modKeys == Qt::AltModifier) + { + // The alt key enables even finer adjustments + numberOfStepsForFullSweep = 2000; + + // It seems that on some systems pressing Alt with mess with the directions, + // i.e. scrolling the mouse wheel is interpreted as pressing the mouse wheel + // left and right. Account for this quirk. + if (deltaY == 0) + { + int const deltaX = we->angleDelta().x(); + if (deltaX != 0) + { + direction = deltaX > 0 ? 1 : -1; + } + } + } + + // Compute the number of steps but make sure that we always do at least one step + const float stepMult = std::max(range / numberOfStepsForFullSweep / step, 1.f); + const int inc = direction * 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())); + } +} + +} // namespace lmms::gui diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 56cf29345..00a9363c8 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -24,11 +24,6 @@ #include "Knob.h" -#include -#include -#include -#include -#include #include #ifndef __USE_XOPEN @@ -36,36 +31,19 @@ #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 * Knob::s_textFloat = nullptr; - - - - Knob::Knob( KnobType _knob_num, QWidget * _parent, const QString & _name ) : - QWidget( _parent ), - FloatModelView( new FloatModel( 0, 0, 0, 1, nullptr, _name, true ), this ), + FloatModelEditorBase(DirectionOfManipulation::Vertical, _parent, _name), 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 ), @@ -84,18 +62,10 @@ Knob::Knob( QWidget * _parent, const QString & _name ) : void Knob::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. // @@ -123,13 +93,9 @@ void Knob::initUi( const QString & _name ) default: break; } - - doConnections(); } - - void Knob::onKnobNumUpdated() { if( m_knobNum != KnobType::Styled ) @@ -484,195 +450,6 @@ void Knob::drawKnob( QPainter * _p ) _p->drawImage( 0, 0, m_cache ); } -void Knob::showTextFloat(int msecBeforeDisplay, int msecDisplayTime) -{ - s_textFloat->setText(displayValue()); - s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); - s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime); -} - -float Knob::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 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( 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 Knob::toggleScale() -{ - model()->setScaleLogarithmic( ! model()->isScaleLogarithmic() ); - update(); -} - - - -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( 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 Knob::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(); - - showTextFloat(0, 0); - - 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 Knob::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() ); - s_textFloat->show(); -} - - - - -void Knob::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 Knob::enterEvent(QEvent *event) -{ - showTextFloat(700, 2000); -} - -void Knob::leaveEvent(QEvent *event) -{ - s_textFloat->hide(); -} - - -void Knob::focusOutEvent( QFocusEvent * _fe ) -{ - // make sure we don't loose mouse release event - mouseReleaseEvent( nullptr ); - QWidget::focusOutEvent( _fe ); -} - - - - -void Knob::mouseDoubleClickEvent( QMouseEvent * ) -{ - enterValue(); -} - - - - void Knob::paintEvent( QPaintEvent * _me ) { QPainter p( this ); @@ -697,201 +474,6 @@ void Knob::paintEvent( QPaintEvent * _me ) } } - - - -void Knob::wheelEvent(QWheelEvent * we) -{ - we->accept(); - const int deltaY = we->angleDelta().y(); - float direction = deltaY > 0 ? 1 : -1; - - auto * m = model(); - float const step = m->step(); - float const range = m->range(); - - // This is the default number of steps or mouse wheel events that it takes to sweep - // from the lowest value to the highest value. - // It might be modified if the user presses modifier keys. See below. - float numberOfStepsForFullSweep = 100.; - - auto const modKeys = we->modifiers(); - if (modKeys == Qt::ShiftModifier) - { - // The shift is intended to go through the values in very coarse steps as in: - // "Shift into overdrive" - numberOfStepsForFullSweep = 10; - } - else if (modKeys == Qt::ControlModifier) - { - // The control key gives more control, i.e. it enables more fine-grained adjustments - numberOfStepsForFullSweep = 1000; - } - else if (modKeys == Qt::AltModifier) - { - // The alt key enables even finer adjustments - numberOfStepsForFullSweep = 2000; - - // It seems that on some systems pressing Alt with mess with the directions, - // i.e. scrolling the mouse wheel is interpreted as pressing the mouse wheel - // left and right. Account for this quirk. - if (deltaY == 0) - { - int const deltaX = we->angleDelta().x(); - if (deltaX != 0) - { - direction = deltaX > 0 ? 1 : -1; - } - } - } - - // Compute the number of steps but make sure that we always do at least one step - const float stepMult = std::max(range / numberOfStepsForFullSweep / step, 1.f); - const int inc = direction * 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 Knob::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 Knob::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 Knob::friendlyUpdate() -{ - if (model() && (model()->controllerConnection() == nullptr || - model()->controllerConnection()->getController()->frequentUpdates() == false || - Controller::runningFrames() % (256*4) == 0)) - { - update(); - } -} - - - - -QString Knob::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 Knob::doConnections() -{ - if( model() != nullptr ) - { - QObject::connect( model(), SIGNAL(dataChanged()), - this, SLOT(friendlyUpdate())); - - QObject::connect( model(), SIGNAL(propertiesChanged()), - this, SLOT(update())); - } -} - - void Knob::changeEvent(QEvent * ev) { if (ev->type() == QEvent::EnabledChange) diff --git a/src/gui/widgets/LedCheckBox.cpp b/src/gui/widgets/LedCheckBox.cpp index 0c16bf391..75e73328f 100644 --- a/src/gui/widgets/LedCheckBox.cpp +++ b/src/gui/widgets/LedCheckBox.cpp @@ -44,9 +44,10 @@ static const auto names = std::array LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, - const QString & _name, LedColor _color ) : + const QString & _name, LedColor _color, bool legacyMode ) : AutomatableButton( _parent, _name ), - m_text( _text ) + m_text( _text ), + m_legacyMode(legacyMode) { initUi( _color ); } @@ -55,8 +56,8 @@ LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, LedCheckBox::LedCheckBox( QWidget * _parent, - const QString & _name, LedColor _color ) : - LedCheckBox( QString(), _parent, _name, _color ) + const QString & _name, LedColor _color, bool legacyMode ) : + LedCheckBox( QString(), _parent, _name, _color, legacyMode ) { } @@ -80,24 +81,16 @@ void LedCheckBox::setText( const QString &s ) -void LedCheckBox::paintEvent( QPaintEvent * ) +void LedCheckBox::paintEvent( QPaintEvent * pe ) { - QPainter p( this ); - p.setFont( pointSize<7>( font() ) ); - - if( model()->value() == true ) - { - p.drawPixmap( 0, 0, *m_ledOnPixmap ); + if (!m_legacyMode) + { + paintNonLegacy(pe); } else { - p.drawPixmap( 0, 0, *m_ledOffPixmap ); + paintLegacy(pe); } - - p.setPen( QColor( 64, 64, 64 ) ); - p.drawText( m_ledOffPixmap->width() + 4, 11, text() ); - p.setPen( QColor( 255, 255, 255 ) ); - p.drawText( m_ledOffPixmap->width() + 3, 10, text() ); } @@ -111,7 +104,11 @@ void LedCheckBox::initUi( LedColor _color ) names[static_cast(_color)].toUtf8().constData() ) ); m_ledOffPixmap = new QPixmap( embed::getIconPixmap( "led_off" ) ); - setFont( pointSize<7>( font() ) ); + if (m_legacyMode) + { + setFont( pointSize<7>( font() ) ); + } + setText( m_text ); } @@ -120,9 +117,45 @@ void LedCheckBox::initUi( LedColor _color ) void LedCheckBox::onTextUpdated() { - setFixedSize(m_ledOffPixmap->width() + 5 + horizontalAdvance(QFontMetrics(font()), - text()), - m_ledOffPixmap->height()); + QFontMetrics const fm = fontMetrics(); + + int const width = m_ledOffPixmap->width() + 5 + horizontalAdvance(fm, text()); + int const height = m_legacyMode ? m_ledOffPixmap->height() : qMax(m_ledOffPixmap->height(), fm.height()); + + setFixedSize(width, height); +} + +void LedCheckBox::paintLegacy(QPaintEvent * pe) +{ + QPainter p( this ); + p.setFont( pointSize<7>( font() ) ); + + if( model()->value() == true ) + { + p.drawPixmap( 0, 0, *m_ledOnPixmap ); + } + else + { + p.drawPixmap( 0, 0, *m_ledOffPixmap ); + } + + p.setPen( QColor( 64, 64, 64 ) ); + p.drawText( m_ledOffPixmap->width() + 4, 11, text() ); + p.setPen( QColor( 255, 255, 255 ) ); + p.drawText( m_ledOffPixmap->width() + 3, 10, text() ); +} + +void LedCheckBox::paintNonLegacy(QPaintEvent * pe) +{ + QPainter p(this); + + QPixmap * drawnPixmap = model()->value() ? m_ledOnPixmap : m_ledOffPixmap; + + p.drawPixmap( 0, rect().height() / 2 - drawnPixmap->height() / 2, *drawnPixmap); + + QRect r = rect(); + r -= QMargins(m_ledOffPixmap->width() + 5, 0, 0, 0); + p.drawText(r, text()); } diff --git a/src/gui/widgets/TempoSyncBarModelEditor.cpp b/src/gui/widgets/TempoSyncBarModelEditor.cpp new file mode 100644 index 000000000..5ff2332e0 --- /dev/null +++ b/src/gui/widgets/TempoSyncBarModelEditor.cpp @@ -0,0 +1,302 @@ +/* + * TempoSyncBarModelEditor.cpp - adds bpm to ms conversion for the bar editor class + * + * Copyright (c) 2005-2007 Danny McRae + * Copyright (c) 2005-2009 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 + +#include "TempoSyncBarModelEditor.h" +#include "Engine.h" +#include "CaptionMenu.h" +#include "embed.h" +#include "GuiApplication.h" +#include "MainWindow.h" +#include "MeterDialog.h" +#include "Song.h" +#include "SubWindow.h" + + +namespace lmms::gui +{ + +TempoSyncBarModelEditor::TempoSyncBarModelEditor(QString text, FloatModel * floatModel, QWidget * parent) : + BarModelEditor(text, floatModel, parent), + m_tempoSyncIcon(embed::getIconPixmap("tempo_sync")), + m_tempoSyncDescription(tr("Tempo Sync")), + m_custom(nullptr) +{ + modelChanged(); +} + + +TempoSyncBarModelEditor::~TempoSyncBarModelEditor() +{ + if(m_custom) + { + delete m_custom->parentWidget(); + } +} + + +void TempoSyncBarModelEditor::modelChanged() +{ + TempoSyncKnobModel * tempoSyncModel = model(); + + if(tempoSyncModel == nullptr) + { + qWarning("no TempoSyncKnobModel has been set!"); + } + + if(m_custom != nullptr) + { + m_custom->setModel(&tempoSyncModel->getCustomMeterModel()); + } + + connect(tempoSyncModel, &TempoSyncKnobModel::syncModeChanged, this, &TempoSyncBarModelEditor::updateDescAndIcon); + connect(this, SIGNAL(sliderMoved(float)), tempoSyncModel, SLOT(disableSync())); + + updateDescAndIcon(); +} + + +void TempoSyncBarModelEditor::contextMenuEvent(QContextMenuEvent *) +{ + mouseReleaseEvent(nullptr); + + TempoSyncKnobModel * tempoSyncModel = model(); + + CaptionMenu contextMenu(tempoSyncModel->displayName(), this); + addDefaultActions(&contextMenu); + + contextMenu.addSeparator(); + + float limit = 60000.0f / (Engine::getSong()->getTempo() * tempoSyncModel->scale()); + + QMenu * syncMenu = contextMenu.addMenu(m_tempoSyncIcon, m_tempoSyncDescription); + + float const maxValue = tempoSyncModel->maxValue(); + + if(limit / 8.0f <= maxValue) + { + connect(syncMenu, SIGNAL(triggered(QAction*)), tempoSyncModel, SLOT(setTempoSync(QAction*))); + + syncMenu->addAction(embed::getIconPixmap("note_none"), + tr("No Sync"))->setData((int) TempoSyncKnobModel::SyncMode::None); + + if(limit / 0.125f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_double_whole"), + tr("Eight beats"))->setData((int) TempoSyncKnobModel::SyncMode::DoubleWholeNote); + } + + if(limit / 0.25f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_whole"), + tr("Whole note"))->setData((int) TempoSyncKnobModel::SyncMode::WholeNote); + } + + if(limit / 0.5f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_half"), + tr("Half note"))->setData((int) TempoSyncKnobModel::SyncMode::HalfNote); + } + + if(limit <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_quarter"), + tr("Quarter note"))->setData((int) TempoSyncKnobModel::SyncMode::QuarterNote); + } + + if(limit / 2.0f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_eighth"), + tr("8th note"))->setData((int) TempoSyncKnobModel::SyncMode::EighthNote); + } + + if(limit / 4.0f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_sixteenth"), + tr("16th note"))->setData((int) TempoSyncKnobModel::SyncMode::SixteenthNote); + } + + syncMenu->addAction(embed::getIconPixmap("note_thirtysecond"), + tr("32nd note"))->setData((int) TempoSyncKnobModel::SyncMode::ThirtysecondNote); + + syncMenu->addAction(embed::getIconPixmap("dont_know"), + tr("Custom..."), this, SLOT(showCustom()))->setData((int) TempoSyncKnobModel::SyncMode::Custom); + + contextMenu.addSeparator(); + } + + contextMenu.exec(QCursor::pos()); + + delete syncMenu; +} + +void TempoSyncBarModelEditor::updateDescAndIcon() +{ + updateTextDescription(); + + if(m_custom != nullptr && model()->syncMode() != TempoSyncKnobModel::SyncMode::Custom) + { + m_custom->parentWidget()->hide(); + } + + updateIcon(); + + emit syncDescriptionChanged(m_tempoSyncDescription); + emit syncIconChanged(); +} + + +const QString & TempoSyncBarModelEditor::syncDescription() +{ + return m_tempoSyncDescription; +} + + +void TempoSyncBarModelEditor::setSyncDescription(const QString & new_description) +{ + m_tempoSyncDescription = new_description; + emit syncDescriptionChanged(new_description); +} + + +const QPixmap & TempoSyncBarModelEditor::syncIcon() +{ + return m_tempoSyncIcon; +} + + +void TempoSyncBarModelEditor::setSyncIcon(const QPixmap & new_icon) +{ + m_tempoSyncIcon = new_icon; + emit syncIconChanged(); +} + + +void TempoSyncBarModelEditor::showCustom() +{ + if(m_custom == nullptr) + { + m_custom = new MeterDialog(getGUI()->mainWindow()->workspace()); + QMdiSubWindow * subWindow = getGUI()->mainWindow()->addWindowedWidget(m_custom); + Qt::WindowFlags flags = subWindow->windowFlags(); + flags &= ~Qt::WindowMaximizeButtonHint; + subWindow->setWindowFlags(flags); + subWindow->setFixedSize(subWindow->size()); + m_custom->setWindowTitle("Meter"); + m_custom->setModel(&model()->getCustomMeterModel()); + } + + m_custom->parentWidget()->show(); + model()->setTempoSync(TempoSyncKnobModel::SyncMode::Custom); +} + + +void TempoSyncBarModelEditor::updateTextDescription() +{ + TempoSyncKnobModel * tempoSyncModel = model(); + + auto const syncMode = tempoSyncModel->syncMode(); + + switch(syncMode) + { + case TempoSyncKnobModel::SyncMode::None: + m_tempoSyncDescription = tr("Tempo Sync"); + break; + case TempoSyncKnobModel::SyncMode::Custom: + m_tempoSyncDescription = tr("Custom ") + + "(" + + QString::number(tempoSyncModel->getCustomMeterModel().numeratorModel().value()) + + "/" + + QString::number(tempoSyncModel->getCustomMeterModel().denominatorModel().value()) + + ")"; + break; + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: + m_tempoSyncDescription = tr("Synced to Eight Beats"); + break; + case TempoSyncKnobModel::SyncMode::WholeNote: + m_tempoSyncDescription = tr("Synced to Whole Note"); + break; + case TempoSyncKnobModel::SyncMode::HalfNote: + m_tempoSyncDescription = tr("Synced to Half Note"); + break; + case TempoSyncKnobModel::SyncMode::QuarterNote: + m_tempoSyncDescription = tr("Synced to Quarter Note"); + break; + case TempoSyncKnobModel::SyncMode::EighthNote: + m_tempoSyncDescription = tr("Synced to 8th Note"); + break; + case TempoSyncKnobModel::SyncMode::SixteenthNote: + m_tempoSyncDescription = tr("Synced to 16th Note"); + break; + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: + m_tempoSyncDescription = tr("Synced to 32nd Note"); + break; + default: ; + } +} + +void TempoSyncBarModelEditor::updateIcon() +{ + switch(model()->syncMode()) + { + case TempoSyncKnobModel::SyncMode::None: + m_tempoSyncIcon = embed::getIconPixmap("tempo_sync"); + break; + case TempoSyncKnobModel::SyncMode::Custom: + m_tempoSyncIcon = embed::getIconPixmap("dont_know"); + break; + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: + m_tempoSyncIcon = embed::getIconPixmap("note_double_whole"); + break; + case TempoSyncKnobModel::SyncMode::WholeNote: + m_tempoSyncIcon = embed::getIconPixmap("note_whole"); + break; + case TempoSyncKnobModel::SyncMode::HalfNote: + m_tempoSyncIcon = embed::getIconPixmap("note_half"); + break; + case TempoSyncKnobModel::SyncMode::QuarterNote: + m_tempoSyncIcon = embed::getIconPixmap("note_quarter"); + break; + case TempoSyncKnobModel::SyncMode::EighthNote: + m_tempoSyncIcon = embed::getIconPixmap("note_eighth"); + break; + case TempoSyncKnobModel::SyncMode::SixteenthNote: + m_tempoSyncIcon = embed::getIconPixmap("note_sixteenth"); + break; + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: + m_tempoSyncIcon = embed::getIconPixmap("note_thirtysecond"); + break; + default: + qWarning("TempoSyncKnob::calculateTempoSyncTime:" + "invalid TempoSyncMode"); + break; + } +} + + +} // namespace lmms::gui