First version of a new dynamic LADSPA control dialog (#2068)

Introduce a new version of the LADSPA control dialog which uses "bar controllers" and arranges them in a grid layout. Long parameter names are elided long if needed. The new dialog is implemented in the class `LadspaMatrixControlDialog`.

Note: it is still possible to reactivate the old dialog as it has not been removed yet. You can do so in `plugins/LadspaEffect/LadspaControls.h` by replacing the implementation of `createView()` with the following:
```
gui::EffectControlDialog* createView() override
{
	return new gui::LadspaControlDialog( this );
}
```

Introduce the new base class `FloatModelEditorBase`. It serves as the base widget for widgets that manipulate a float model and provides some base functionalities like context menus, edit dialogs, mouse handling, etc. Currently `BarModelEditor` and `Knob` inherit from `FloatModelEditorBase`. Therefore lots of code was moved from `Knob` to `FloatModelEditorBase`.

`BarModelEditor` supports style sheets. See the changes in style.css for the available options and their usage.
 
Extend `LedCheckBox` so that it adds support to render the text with the default font in the default size. The class now supports two modes:
* Legacy mode
* Non-legacy mode

Legacy mode is the default and renders the LedCheckBox as before. The font is set to 7 pixels and all the text is rendered with a shadow.

Non-legacy mode uses the current font to render the text. The text is rendered without a shadow and the pixmap is centered vertically to the left side of the text. Non-legacy mode is currently only used in the context of the matrix LADSPA dialog. Toggle options are rendered using it as well as the "Link Channels" button.

Add `TempoSyncBarModelEditor` which adds a tempo sync option to a `BarModelEditor`. Please note that the code was mostly copied and adjusted from the `TempoSyncKnob` class. It was not attempted yet to unify some of the code because with the current design it seems to be a road to "inheritance hell". A better solution might be to refactor the code so that it becomes more composable.

What follows are the individual commit messages of the 64 commits that have been squashed into a single merge commit.

* First version of a new dynamic LADSPA control dialog

The new dialog shows the LADSPA controls in a matrix layout. Each row
corresponds to a LADSPA parameter. Each parameter can have several
channels which can be linked. Each channel has its own row of controls.

The class LadspaMatrixControlView was added by copying and modifying
LadspaControlView to get rid of the link buttons which are associated
with some controls and some labels.

* Remove trailing whitespaces

* Merge fix

* Use the new connect syntax

* Remove empty destructors

* Use override keyword

* Reformat a bit

* Use nullptr

* Use range-based loops

* Add BarModelEditor and improve layouts

Add the new class `BarModelEditor` which is intended to become a new way
to adjust values of float models.

Simplify the layout in `LadspaMatrixControlDialog` by removing some
nested layouts. Remove the "Parameters" column.

Adjust `LadspaMatrixControlView` to implement the following changes:
* Show the name of the control next to toggle buttons (`LedCheckBox`).
* Use the new `BarModelEditor` for integer and float types.
* SHow the name of the control next to time based parameters that use
`TempoSyncKnob`.

The names are shown so that the "Parameters" column can be removed.

Technical details
------------------
The class `LadspaMatrixControlDialog` now creates a widget that contains
the matrix layout with the controls. This widget is then added to a
scroll area. The layout is populated in the new method
`arrangeControls`.

Add some helper methods to `LadspaMatrixControlDialog` which retrieve
the `LadspaControls` instance and the number of channels.

Add the implementation of `BarModelEditor` to `src/gui/CMakeLists.txt`.

TODOs
------
Extract common code out of the `Knob` class so that it can be reused by
`BarModelEditor`.

* Use factory to create LADSPA control widgets

Replace the class `LadspaMatrixControlView` with the factory class
`LadspaWidgetFactory`. The former was a widget that wrapped the widget
representation of a LADSPA control in yet another widget with a layout.
The factory simply returns the configured widget so that it can be
incorporated directly in layouts or other widgets.

Adjust `LadspaMatrixControlDialog` so that it uses the
`LadspaWidgetFactory` instead of the `LadspaMatrixControlView`.

* 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`.

* Remove Knob related code from FloatModelEditorBase

First removal of Knob related code from FloatModelEditorBase.

* Remove setHtmlLabel

* Remove enum for knob types

* Remove label related code

Remove setLabel and the members m_label and m_isHtmlLabel.

* Remove several Qt properties

* Remove angle related members and methods

* Remove unused members and includes

* Clean up includes

* Make BarModelEditor inherit from FloatModelEditorBase

Make `BarModelEditor` inherit from `FloatModelEditorBase` so that it
inherits all shared functionality. Currently the class mostly implements
size related methods and overrides the paint method.

* Move LadspaWidgetFactory to LadspaEffect

Move the class `LadspaWidgetFactory` to the project LadspaEffect to make
it hopefully compile with mingw32 and mingw64.

Interestingly the code compiled and worked under Linux and MacOS.

* Code adjustments after scripted checks

Add an include guard and an additional `#pragme once`. Add comments to
closing namespace scopes.

* Export BarModelEditor

Export BarModelEditor so that for example the LadspaEffect project/
plugin can use it.

* Improve rendering of bar model editor

Increase the margin from 2 to 3 so that we have a more prominent border
around the filled area.

Fix a problem in the rendering code which led to situations where the
bar was drawn to the left of the start position for small values.

Return a more exact minimum size hint.

* Elide long parameter names

Elide long parameter names if needed.

* Enable horizontal direction of manipulation

Extend `FloatModelEditorBase` so that it also allows manipulation of the
values when the mouse is moved in horizontal directions. The default is
to use the vertical direction.

Make use of this new feature in `BarModelEditor` which now reacts to
horizontal movements when changing values.

* Represent enum parameters using the bar widget

The implementation of the current LADSPA dialog in master uses knobs to
represent enum parameters. Therefore it should also work with the new
bar widget.

This gets rid of many labels with the parameter name followed by
"(unsupported)".

* Remove TODO in LadspaWidgetFactory

* Render text of LedCheckBox with default font

Extend LedCheckBox so that it adds support to render the text with the
default font in the default size. The class now supports two modes:
* Legacy mode
* Non-legacy mode

Legacy mode is the default and renders the LedCheckBox as before. The
font is set to 7 pixels and all the text is rendered with a shadow.

Non-legacy mode uses the current font to render the text. The text is
rendered without a shadow and the pixmap is centered vertically to the
left side of the text.

The non-legacy mode is currently only used in the context of the matrix
LADSPA dialog. Toggle options are rendered using it as well as the "Link
Channels" button.

* Use "yellow" LEDs for bool parameters

Use "yellow" LEDs (which are actually blue) for dynamically added bool
parameters so that the dialog is consistent with regards to the LED
colors. The "Link Channels" button also uses a "yellow" LED.

* Fix Qt5 builds

Fix the Qt5 builds which do not know horizontalAdvance as a member of
FontMetrics.

Also refactor LedCheckBox::onTextUpdated to make it more compact.

* Style sheets for BarModelEditor

Add style sheets options for BarModelEditor. See the changes in
style.css for the available options and their usage.

The members that can be manipulated by the style sheet options are
initialized accoriding to the palette so that the BarModelEditor should
also render something useful if no style is set.

Adjust the paintEvent method to use the style sheet brushes and colors.

* Layout optimizations

Set the vertical scroll bar to always show so that the controls do not
move around if the scroll bar is hidden or shown.

Set the margin of the grid layout to 0 so that the whole dialog becomes
tighter.

* Get rid of initial scroll bars

Make sure that the widget is shown without a scrollbar whenever possible
using the following rules.

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. In that case the minimum height is set to 90 % of
the workspace.

* Switch to a green theme

Switch to a green theme to better match the default theme.

* Adjust classic theme

Adjust the classic theme so that it renders the bars in a legible way.

* Fixes for CodeFactor

Remove virtual keyword from methods that are marked as override.

Remove whitespaces that make CodeFactor unhappy. One of these fixes
includes moving the implementation of
LadspaMatrixControlDialog::isResizable into the cpp file.

* CodeFactor is still unhappy

Remove a blank line after the constructor.

* Center align time based controls

Align time based controls, i.e. knobs, in the center.

The implementation defeats the purpose of the widget factory a bit but
it makes the design look nicer.

* Fix build

Fix the build by adjusting the enums. I added the changes while writing
the commit message for the merge commit but they did not make it.

* Attempt at CodeFactor warning

Try to make the last CodeFactor warning disappear.

* Add bar controller with tempo sync option

Add `TempoSyncBarModelEditor` which adds a tempo sync option to a `BarModelEditor`. Please note that the code was mostly copied and adjusted from the `TempoSyncKnob` class. It was not attempted yet to unify some of the code because with the current design it seems to be a road to "inheritance hell". A better solution might be to refactor the code so that it is more composable.

Another option might be to move the tempo sync functionality into a class like `FloatModelEditorBase`. Although this would not be a 100% right place either because `TempoSyncKnobModel` inherits from `FloatModel`. In that case we'd have specialized code in a generic base class.

Adjust `LadspaWidgetFactory` so that it now returns an instance of a `TempoSyncBarModelEditor` instead of a `TempoSyncKnob`.

Remove the extra layout code from `LadspaMatrixControlDialog.cpp` as most things are bar editors now.

Adjust `TempoSyncKnobModel` so we do not have to make `TempoSyncBarModelEditor` a friend of it.

* Another attempt to please CodeFactor

Have another attempt to fix CodeFactor's complaints about `LadspaMatrixControlDialog.h`.

* Coding conventions, blanks and blank lines

Remove lots of unnecessary white space. Remove blank lines. Remove leading underscores from parameters.

Remove line breaks in `TempoSyncBarModelEditor.cpp` to make the code more readable. Remove repeated method calls by introducing local variables.

* Break down complex method

Break down the complex method `TempoSyncBarModelEditor::updateDescAndIcon` by delegating to the new methods `updateTextDescription` and `updateIcon`.

* Another attempt to please CodeFactor

Another attempt to fix `LadspaMatrixControlDialog.h` to please CodeFactor.

* Work on TODOs

The TODO "Assumes that all processors are equal..." has been turned into a comment when we start iterating over the channels. If the channels are not of the same structure this would likely also have been a problem in the old implementation.

The TODO "Use a factory..." has been removed as this is already done.

The include of `FloatModelEditorBase.h` in LadspaWidgetFactory has been removed.

* Modifier keys for mouse wheel adjustments

Integrate the changes of commit 7000afb2ea into `FloatModelEditorBase`.

This commit added modifier keys for mouse wheel adjustments to the `Knob` class. The port to `FloatModelEditorBase` results in the bar control now also supporting different scales of adjustments that can be switched with the Shift, Ctrl and Alt keys.

* Show current value on mouse over

Integrate the changes of commit fcdf4c0568 into `FloatModelEditorBase`. This commit lets users show the current value of a float model when the mouse is moved over the control.

* Make Knob inherit from FloatModelEditorBase

Make `Knob` inherit from `FloatModelEditorBase`. This is mostly a continuation for the changes introduced with commit c63d86f.

The idea is that `FloatModelEditorBase` contains the underlying functionality and logic to deal with float models. `Knob` and other classes then only override the presentation aspects. This way `Knob` and `BarModelEditor` can share the same functionality but can differ in how they present the data.

Technical details
------------------
Remove all methods that are already defined in `FloatModelEditorBase` from `Knob`. These are all methods that are defined in the same way in `FloatModelEditorBase`. The method `paintEvent` is not removed because it is overridden by `Knob` as it has its own presentation.

Remove forward declaration of `QPixmap` from `FloatModelEditorBase` as it was not used. Remove unused function `convertPixmapToGrayScaleTemp` from `FloatModelEditorBase`.

* Cleanup includes in Knob class

* Code style changes in FloatModelEditorBase

`FloatModelEditorBase` is a new class/file. Therefore we can do some extensive code cleanup on the code that was initially copied from `Knob`.

Adjust whitespace for methods and some if-statements. Remove underscores from parameter names. Use only two blank lines between method definitions.

* Cleanup include for FloatModelEditorBase

* Show effect name as title of dialog

Add the effect name as the title in the content of the window. This improves the structure of the dialog as it is now clearer from the content itself to which effect the controls belong to.

This duplicates the information from the window title. However, the window title is rather small and the larger font in the content makes it easier to find an effect in a set of opened dialogs.

* Revert "Show effect name as title of dialog"

This reverts commit 8ce0d366d0.

* Fix problem with LedCheckBox in grid layout

The LedCheckBox does not play nice when being used in a grid layout as it disables the resizing behavior of every column it appears in.

The solution is to wrap it into the layout of a parent widget that knows how to behave.

* Reduce minimum width of BarModelEditor

Reduce the minimum width of `BarModelEditor` from 200 to 50.

---------

Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
This commit is contained in:
Michael Gregorius
2023-10-29 11:16:39 +01:00
committed by GitHub
parent d5e6ac6dc5
commit c6ed4a274a
21 changed files with 1727 additions and 508 deletions

View File

@@ -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 {

View File

@@ -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 {

76
include/BarModelEditor.h Normal file
View File

@@ -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

View File

@@ -0,0 +1,121 @@
/*
* FloatModelEditorBase.h - Base editor for float models
*
* Copyright (c) 2004-2008 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 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 <QWidget>
#include <QPoint>
#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

View File

@@ -26,12 +26,9 @@
#define LMMS_GUI_KNOB_H
#include <memory>
#include <QPixmap>
#include <QWidget>
#include <QPoint>
#include <QTextDocument>
#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<int>( ( 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<QPixmap> 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

View File

@@ -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;
} ;

View File

@@ -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);
} ;

View File

@@ -0,0 +1,90 @@
/*
* TempoSyncBarModelEditor.h - adds bpm to ms conversion for the bar editor class
*
* Copyright (c) 2005-2008 Danny McRae <khjklujn/at/yahoo.com>
* Copyright (c) 2009-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 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 <QPixmap>
#include <QPointer>
#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<TempoSyncKnobModel>();
}
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<MeterDialog> m_custom;
};
} // namespace lmms::gui
#endif // LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H

View File

@@ -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 );

View File

@@ -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")

View File

@@ -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;

View File

@@ -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 <michaelgregorius/at/web[dot]de>
*
* 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 <QGroupBox>
#include <QLayout>
#include <QLabel>
#include <QScrollArea>
#include <QScrollBar>
#include <QSpacerItem>
#include <QMdiArea>
#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("<b>" + tr("Link") + "</b>", 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("<b>" + tr("Channel %1").arg(QString::number(i + 1)) + "</b>", 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<int>(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<LadspaControls *>(m_effectControls);
}
ch_cnt_t LadspaMatrixControlDialog::getChannelCount() const
{
return getLadspaControls()->m_processors;
}
} // namespace lmms::gui

View File

@@ -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 <michaelgregorius/at/web[dot]de>
*
* 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

View File

@@ -0,0 +1,83 @@
/*
* LadspaWidgetFactory.cpp - Factory that creates widgets for LADSPA ports
*
* Copyright (c) 2006-2008 Danny McRae <khjklujn/at/users.sourceforge.net>
* Copyright (c) 2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 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 <QHBoxLayout>
#include <QLabel>
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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,117 @@
#include <BarModelEditor.h>
#include <QPainter>
#include <QStyle>
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

View File

@@ -0,0 +1,464 @@
/*
* FloatModelEditorBase.cpp - Base editor for float models
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 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 <QApplication>
#include <QInputDialog>
#include <QPainter>
#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<AutomatableModel*>(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>();
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<float>();
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

View File

@@ -24,11 +24,6 @@
#include "Knob.h"
#include <memory>
#include <QApplication>
#include <QFontMetrics>
#include <QInputDialog>
#include <QMouseEvent>
#include <QPainter>
#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<AutomatableModel*>(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>();
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<float>();
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)

View File

@@ -44,9 +44,10 @@ static const auto names = std::array<QString, 3>
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<std::size_t>(_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());
}

View File

@@ -0,0 +1,302 @@
/*
* TempoSyncBarModelEditor.cpp - adds bpm to ms conversion for the bar editor class
*
* Copyright (c) 2005-2007 Danny McRae <khjklujn/at/yahoo.com>
* Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* 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 <QMdiArea>
#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