diff --git a/include/LcdFloatSpinBox.h b/include/LcdFloatSpinBox.h new file mode 100644 index 000000000..034168fbb --- /dev/null +++ b/include/LcdFloatSpinBox.h @@ -0,0 +1,83 @@ +/* + * LcdFloatSpinBox.h - class LcdFloatSpinBox (LcdSpinBox for floats) + * + * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2020 Martin Pavelek + * + * 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 LCD_FLOATSPINBOX_H +#define LCD_FLOATSPINBOX_H + +#include + +#include "LcdWidget.h" +#include "AutomatableModelView.h" + + +class LMMS_EXPORT LcdFloatSpinBox : public QWidget, public FloatModelView +{ + Q_OBJECT +public: + LcdFloatSpinBox(int numWhole, int numFrac, const QString& name = QString(), QWidget* parent = nullptr); + LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent = nullptr); + + void modelChanged() override + { + ModelView::modelChanged(); + update(); + } + + void setLabel(const QString &label) { m_label = label; } + +public slots: + virtual void update(); + +protected: + void contextMenuEvent(QContextMenuEvent *me) override; + void mousePressEvent(QMouseEvent *me) override; + void mouseMoveEvent(QMouseEvent *me) override; + void mouseReleaseEvent(QMouseEvent *me) override; + void wheelEvent(QWheelEvent *we) override; + void mouseDoubleClickEvent(QMouseEvent *me) override; + void paintEvent(QPaintEvent *pe) override; + +private: + void layoutSetup(const QString &style = QString("19green")); + void enterValue(); + float getStep() const; + + LcdWidget m_wholeDisplay; + LcdWidget m_fractionDisplay; + bool m_mouseMoving; + bool m_intStep; + QPoint m_origMousePos; + int m_displayOffset; + QString m_label; + +signals: + void manualChange(); + +}; + +using LcdFloatSpinBoxModel = FloatModel; + +#endif diff --git a/include/LcdWidget.h b/include/LcdWidget.h index bdad5cb30..7dc2e2a28 100644 --- a/include/LcdWidget.h +++ b/include/LcdWidget.h @@ -40,9 +40,10 @@ class LMMS_EXPORT LcdWidget : public QWidget Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) public: - LcdWidget( QWidget* parent, const QString& name = QString() ); - LcdWidget( int numDigits, QWidget* parent, const QString& name = QString() ); - LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name = QString() ); + explicit LcdWidget(QWidget* parent, const QString& name = QString(), bool leadingZero = false); + LcdWidget(int numDigits, QWidget* parent, const QString& name = QString(), bool leadingZero = false); + LcdWidget(int numDigits, const QString& style, QWidget* parent, const QString& name = QString(), + bool leadingZero = false); virtual ~LcdWidget(); @@ -66,6 +67,15 @@ public: QColor textShadowColor() const; void setTextShadowColor( const QColor & c ); + int cellHeight() const { return m_cellHeight; } + + void setSeamless(bool left, bool right) + { + m_seamlessLeft = left; + m_seamlessRight = right; + updateSize(); + } + public slots: virtual void setMarginWidth( int width ); @@ -75,11 +85,6 @@ protected: virtual void updateSize(); - int cellHeight() const - { - return m_cellHeight; - } - private: @@ -99,9 +104,12 @@ private: int m_cellHeight; int m_numDigits; int m_marginWidth; + bool m_seamlessLeft; + bool m_seamlessRight; + bool m_leadingZero; void initUi( const QString& name, const QString &style ); //!< to be called by ctors -} ; +}; #endif diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4b7018a55..44a41b011 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -75,6 +75,7 @@ SET(LMMS_SRCS gui/widgets/LeftRightNav.cpp gui/widgets/Knob.cpp gui/widgets/LadspaControlView.cpp + gui/widgets/LcdFloatSpinBox.cpp gui/widgets/LcdSpinBox.cpp gui/widgets/LcdWidget.cpp gui/widgets/LedCheckbox.cpp diff --git a/src/gui/widgets/LcdFloatSpinBox.cpp b/src/gui/widgets/LcdFloatSpinBox.cpp new file mode 100644 index 000000000..0e640e86c --- /dev/null +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -0,0 +1,244 @@ +/* + * LcdFloatSpinBox.cpp - class LcdFloatSpinBox (LcdSpinBox for floats) + * + * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2008 Paul Giblock + * Copyright (c) 2020 Martin Pavelek + * + * 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 "LcdFloatSpinBox.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CaptionMenu.h" +#include "embed.h" +#include "GuiApplication.h" +#include "gui_templates.h" +#include "MainWindow.h" + + +LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, QWidget* parent) : + FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), + m_wholeDisplay(numWhole, parent, name, false), + m_fractionDisplay(numFrac, parent, name, true), + m_mouseMoving(false), + m_intStep(false), + m_origMousePos(), + m_displayOffset(0) +{ + layoutSetup(); +} + + +LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent) : + FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), + m_wholeDisplay(numWhole, style, parent, name, false), + m_fractionDisplay(numFrac, style, parent, name, true), + m_mouseMoving(false), + m_intStep(false), + m_origMousePos(), + m_displayOffset(0) +{ + layoutSetup(style); +} + + +void LcdFloatSpinBox::layoutSetup(const QString &style) +{ + // Assemble the LCD parts + QHBoxLayout *lcdLayout = new QHBoxLayout(); + + m_wholeDisplay.setSeamless(false, true); + m_fractionDisplay.setSeamless(true, false); + + lcdLayout->addWidget(&m_wholeDisplay); + + QLabel *dotLabel = new QLabel("", this); + QPixmap dotPixmap(embed::getIconPixmap(QString("lcd_" + style + "_dot").toUtf8().constData())); + dotLabel->setPixmap(dotPixmap.copy(0, 0, dotPixmap.size().width(), dotPixmap.size().height() / 2)); + lcdLayout->addWidget(dotLabel); + + lcdLayout->addWidget(&m_fractionDisplay); + + lcdLayout->setContentsMargins(0, 0, 0, 0); + lcdLayout->setSpacing(0); + + // Add space for label + QVBoxLayout *outerLayout = new QVBoxLayout(); + outerLayout->addLayout(lcdLayout); + outerLayout->addSpacing(9); + outerLayout->setContentsMargins(0, 0, 0, 0); + outerLayout->setSizeConstraint(QLayout::SetFixedSize); + this->setLayout(outerLayout); +} + + +void LcdFloatSpinBox::update() +{ + const int whole = static_cast(model()->value()); + const float fraction = model()->value() - whole; + const int intFraction = fraction * std::pow(10.f, m_fractionDisplay.numDigits()); + m_wholeDisplay.setValue(whole); + m_fractionDisplay.setValue(intFraction); + + QWidget::update(); +} + + +void LcdFloatSpinBox::contextMenuEvent(QContextMenuEvent* event) +{ + CaptionMenu contextMenu(model()->displayName()); + addDefaultActions(&contextMenu); + contextMenu.exec(QCursor::pos()); +} + + +void LcdFloatSpinBox::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton && + !(event->modifiers() & Qt::ControlModifier) && + event->y() < m_wholeDisplay.cellHeight() + 2) + { + m_mouseMoving = true; + m_origMousePos = event->globalPos(); + + AutomatableModel *thisModel = model(); + if (thisModel) + { + thisModel->addJournalCheckPoint(); + thisModel->saveJournallingState(false); + } + } + else + { + FloatModelView::mousePressEvent(event); + } +} + + +void LcdFloatSpinBox::mouseMoveEvent(QMouseEvent* event) +{ + // switch between integer and fractional step based on cursor position + if (event->x() < m_wholeDisplay.width()) { m_intStep = true; } + else { m_intStep = false; } + + if (m_mouseMoving) + { + int dy = event->globalY() - m_origMousePos.y(); + if (gui->mainWindow()->isShiftPressed()) { dy = qBound(-4, dy/4, 4); } + if (dy > 1 || dy < -1) + { + model()->setValue(model()->value() - dy / 2 * getStep()); + emit manualChange(); + m_origMousePos = event->globalPos(); + } + } +} + + +void LcdFloatSpinBox::mouseReleaseEvent(QMouseEvent*) +{ + if (m_mouseMoving) + { + model()->restoreJournallingState(); + m_mouseMoving = false; + } +} + + +void LcdFloatSpinBox::wheelEvent(QWheelEvent *event) +{ + // switch between integer and fractional step based on cursor position + if (event->x() < m_wholeDisplay.width()) { m_intStep = true; } + else { m_intStep = false; } + + event->accept(); + model()->setValue(model()->value() + ((event->delta() > 0) ? 1 : -1) * getStep()); + emit manualChange(); +} + + +void LcdFloatSpinBox::mouseDoubleClickEvent(QMouseEvent *) +{ + enterValue(); +} + + +void LcdFloatSpinBox::enterValue() +{ + bool ok; + float newVal; + + newVal = QInputDialog::getDouble( + this, tr("Set value"), + tr("Please enter a new value between %1 and %2:"). + arg(model()->minValue()). + arg(model()->maxValue()), + model()->value(), + model()->minValue(), + model()->maxValue(), + m_fractionDisplay.numDigits(), &ok); + + if (ok) + { + model()->setValue(newVal); + } +} + + +float LcdFloatSpinBox::getStep() const +{ + if (m_intStep) { return 1; } + else { return model()->step(); } +} + + +void LcdFloatSpinBox::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + // Border + QStyleOptionFrame opt; + opt.initFrom(this); + opt.state = QStyle::State_Sunken; + opt.rect = QRect(0, 0, width() - 1, m_wholeDisplay.height()); + style()->drawPrimitive(QStyle::PE_Frame, &opt, &p, this); + + // Label + if (!m_label.isEmpty()) + { + p.setFont(pointSizeF(p.font(), 6.5)); + p.setPen(m_wholeDisplay.textShadowColor()); + p.drawText(width() / 2 - p.fontMetrics().width(m_label) / 2 + 1, height(), m_label); + p.setPen(m_wholeDisplay.textColor()); + p.drawText(width() / 2 - p.fontMetrics().width(m_label) / 2, height() - 1, m_label); + } +} diff --git a/src/gui/widgets/LcdSpinBox.cpp b/src/gui/widgets/LcdSpinBox.cpp index 6aacc01dd..f57ddca0b 100644 --- a/src/gui/widgets/LcdSpinBox.cpp +++ b/src/gui/widgets/LcdSpinBox.cpp @@ -70,17 +70,11 @@ void LcdSpinBox::update() -void LcdSpinBox::contextMenuEvent( QContextMenuEvent* event ) +void LcdSpinBox::contextMenuEvent(QContextMenuEvent* event) { - // for the case, the user clicked right while pressing left mouse- - // button, the context-menu appears while mouse-cursor is still hidden - // and it isn't shown again until user does something which causes - // an QApplication::restoreOverrideCursor()-call... - mouseReleaseEvent( NULL ); - - CaptionMenu contextMenu( model()->displayName() ); - addDefaultActions( &contextMenu ); - contextMenu.exec( QCursor::pos() ); + CaptionMenu contextMenu(model()->displayName()); + addDefaultActions(&contextMenu); + contextMenu.exec(QCursor::pos()); } @@ -136,12 +130,11 @@ void LcdSpinBox::mouseMoveEvent( QMouseEvent* event ) -void LcdSpinBox::mouseReleaseEvent( QMouseEvent* ) +void LcdSpinBox::mouseReleaseEvent(QMouseEvent*) { - if( m_mouseMoving ) + if (m_mouseMoving) { model()->restoreJournallingState(); - QApplication::restoreOverrideCursor(); m_mouseMoving = false; } } diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index 63e3b6ac0..1fefe3dd0 100644 --- a/src/gui/widgets/LcdWidget.cpp +++ b/src/gui/widgets/LcdWidget.cpp @@ -40,28 +40,31 @@ -LcdWidget::LcdWidget( QWidget* parent, const QString& name ) : - LcdWidget( 1, parent, name ) +LcdWidget::LcdWidget(QWidget* parent, const QString& name, bool leadingZero) : + LcdWidget(1, parent, name, leadingZero) { } -LcdWidget::LcdWidget( int numDigits, QWidget* parent, const QString& name ) : - LcdWidget( numDigits, QString("19green"), parent, name ) +LcdWidget::LcdWidget(int numDigits, QWidget* parent, const QString& name, bool leadingZero) : + LcdWidget(numDigits, QString("19green"), parent, name, leadingZero) { } -LcdWidget::LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name ) : +LcdWidget::LcdWidget(int numDigits, const QString& style, QWidget* parent, const QString& name, bool leadingZero) : QWidget( parent ), m_label(), m_textColor( 255, 255, 255 ), m_textShadowColor( 64, 64, 64 ), - m_numDigits( numDigits ) + m_numDigits(numDigits), + m_seamlessLeft(false), + m_seamlessRight(false), + m_leadingZero(leadingZero) { initUi( name, style ); } @@ -77,19 +80,16 @@ LcdWidget::~LcdWidget() -void LcdWidget::setValue( int value ) +void LcdWidget::setValue(int value) { QString s = m_textForValue[value]; - if( s.isEmpty() ) + if (s.isEmpty()) { - s = QString::number( value ); - // TODO: if pad == true - /* - while( (int) s.length() < m_numDigits ) + s = QString::number(value); + if (m_leadingZero) { - s = "0" + s; + s = s.rightJustified(m_numDigits, '0'); } - */ } m_display = s; @@ -140,15 +140,23 @@ void LcdWidget::paintEvent( QPaintEvent* ) // p.translate( width() / 2 - lcdWidth / 2, 0 ); p.save(); - p.translate( margin, margin ); + // Don't skip any space and don't draw margin on the left side in seamless mode + if (m_seamlessLeft) + { + p.translate(0, margin); + } + else + { + p.translate(margin, margin); + // Left Margin + p.drawPixmap( + cellRect, + *m_lcdPixmap, + QRect(QPoint(charsPerPixmap * m_cellWidth, isEnabled() ? 0 : m_cellHeight), cellSize) + ); - // Left Margin - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( charsPerPixmap*m_cellWidth, - isEnabled()?0:m_cellHeight ), - cellSize ) ); - - p.translate( m_marginWidth, 0 ); + p.translate(m_marginWidth, 0); + } // Padding for( int i=0; i < m_numDigits - m_display.length(); i++ ) @@ -177,21 +185,26 @@ void LcdWidget::paintEvent( QPaintEvent* ) } // Right Margin - p.drawPixmap( QRect( 0, 0, m_marginWidth-1, m_cellHeight ), *m_lcdPixmap, - QRect( charsPerPixmap*m_cellWidth, isEnabled()?0:m_cellHeight, - m_cellWidth / 2, m_cellHeight ) ); + p.drawPixmap(QRect(0, 0, m_seamlessRight ? 0 : m_marginWidth - 1, m_cellHeight), + *m_lcdPixmap, + QRect(charsPerPixmap * m_cellWidth, isEnabled() ? 0 : m_cellHeight, m_cellWidth / 2, m_cellHeight)); p.restore(); // Border - QStyleOptionFrame opt; - opt.initFrom( this ); - opt.state = QStyle::State_Sunken; - opt.rect = QRect( 0, 0, m_cellWidth * m_numDigits + (margin+m_marginWidth)*2 - 1, - m_cellHeight + (margin*2) ); + // When either the left or right edge is seamless, the border drawing must be done + // by the encapsulating class (usually LcdFloatSpinBox). + if (!m_seamlessLeft && !m_seamlessRight) + { + QStyleOptionFrame opt; + opt.initFrom(this); + opt.state = QStyle::State_Sunken; + opt.rect = QRect(0, 0, m_cellWidth * m_numDigits + (margin + m_marginWidth) * 2 - 1, + m_cellHeight + (margin * 2)); - style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); + style()->drawPrimitive(QStyle::PE_Frame, &opt, &p, this); + } p.resetTransform(); @@ -235,16 +248,25 @@ void LcdWidget::setMarginWidth( int width ) void LcdWidget::updateSize() { - int margin = 1; - if (m_label.isEmpty()) { - setFixedSize( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), - m_cellHeight + (2*margin) ); + const int marginX1 = m_seamlessLeft ? 0 : 1 + m_marginWidth; + const int marginX2 = m_seamlessRight ? 0 : 1 + m_marginWidth; + const int marginY = 1; + if (m_label.isEmpty()) + { + setFixedSize( + m_cellWidth * m_numDigits + marginX1 + marginX2, + m_cellHeight + (2 * marginY) + ); } - else { - setFixedSize(qMax( - m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), - horizontalAdvance(QFontMetrics(pointSizeF(font(), 6.5)), m_label)), - m_cellHeight + (2*margin) + 9); + else + { + setFixedSize( + qMax( + m_cellWidth * m_numDigits + marginX1 + marginX2, + horizontalAdvance(QFontMetrics(pointSizeF(font(), 6.5)), m_label) + ), + m_cellHeight + (2 * marginY) + 9 + ); } update(); @@ -272,7 +294,3 @@ void LcdWidget::initUi(const QString& name , const QString& style) updateSize(); } - - - -