File indexing completed on 2024-04-21 04:52:31

0001 /*
0002     SPDX-FileCopyrightText: 2011 Till Theato <root@ttill.de>
0003     SPDX-FileCopyrightText: 2011 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "dragvalue.h"
0008 
0009 #include "kdenlivesettings.h"
0010 
0011 #include <cmath>
0012 
0013 #include <QAction>
0014 #include <QApplication>
0015 #include <QFocusEvent>
0016 #include <QHBoxLayout>
0017 #include <QMenu>
0018 #include <QMouseEvent>
0019 #include <QWheelEvent>
0020 
0021 #include <KLocalizedString>
0022 #include <QFontDatabase>
0023 #include <QStyle>
0024 #include <kwidgetsaddons_version.h>
0025 
0026 DragValue::DragValue(const QString &label, double defaultValue, int decimals, double min, double max, int id, const QString &suffix, bool showSlider,
0027                      bool oddOnly, QWidget *parent)
0028     : QWidget(parent)
0029     , m_maximum(max)
0030     , m_minimum(min)
0031     , m_decimals(decimals)
0032     , m_default(defaultValue)
0033     , m_id(id)
0034     , m_intEdit(nullptr)
0035     , m_doubleEdit(nullptr)
0036 {
0037     if (showSlider) {
0038         setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0039     } else {
0040         setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
0041     }
0042     setFocusPolicy(Qt::StrongFocus);
0043     setContextMenuPolicy(Qt::CustomContextMenu);
0044     setFocusPolicy(Qt::StrongFocus);
0045 
0046     auto *l = new QHBoxLayout;
0047     l->setSpacing(0);
0048     l->setContentsMargins(0, 0, 0, 0);
0049     m_label = new CustomLabel(label, showSlider, m_maximum - m_minimum, this);
0050     m_label->setObjectName("draggLabel");
0051     l->addWidget(m_label);
0052     setMinimumHeight(m_label->sizeHint().height());
0053     if (decimals == 0) {
0054         m_label->setMaximum(max - min);
0055         m_label->setStep(oddOnly ? 2 : 1);
0056         m_intEdit = new QSpinBox(this);
0057         m_intEdit->setObjectName(QStringLiteral("dragBox"));
0058         m_intEdit->setFocusPolicy(Qt::StrongFocus);
0059         if (!suffix.isEmpty()) {
0060             m_intEdit->setSuffix(QLatin1Char(' ') + suffix);
0061         }
0062         m_intEdit->setKeyboardTracking(false);
0063         m_intEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
0064         m_intEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0065         m_intEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
0066         m_intEdit->setRange((int)m_minimum, (int)m_maximum);
0067         m_intEdit->setValue((int)m_default);
0068         if (oddOnly) {
0069             m_intEdit->setSingleStep(2);
0070         }
0071         l->addWidget(m_intEdit);
0072         connect(m_intEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
0073                 static_cast<void (DragValue::*)(int)>(&DragValue::slotSetValue));
0074         connect(m_intEdit, &QAbstractSpinBox::editingFinished, this, &DragValue::slotEditingFinished);
0075         m_intEdit->installEventFilter(this);
0076     } else {
0077         m_doubleEdit = new QDoubleSpinBox(this);
0078         m_doubleEdit->setDecimals(decimals);
0079         m_doubleEdit->setFocusPolicy(Qt::StrongFocus);
0080         m_doubleEdit->setObjectName(QStringLiteral("dragBox"));
0081         if (!suffix.isEmpty()) {
0082             m_doubleEdit->setSuffix(QLatin1Char(' ') + suffix);
0083         }
0084         m_doubleEdit->setKeyboardTracking(false);
0085         m_doubleEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
0086         m_doubleEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0087         m_doubleEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
0088         m_doubleEdit->setRange(m_minimum, m_maximum);
0089         double factor = 100;
0090         if (m_maximum - m_minimum > 10000) {
0091             factor = 1000;
0092         }
0093         double steps = (m_maximum - m_minimum) / factor;
0094         m_doubleEdit->setSingleStep(steps);
0095         m_label->setStep(steps);
0096         // m_label->setStep(1);
0097         l->addWidget(m_doubleEdit);
0098         m_doubleEdit->setValue(m_default);
0099         m_doubleEdit->installEventFilter(this);
0100         connect(m_doubleEdit, SIGNAL(valueChanged(double)), this, SLOT(slotSetValue(double)));
0101         connect(m_doubleEdit, &QAbstractSpinBox::editingFinished, this, &DragValue::slotEditingFinished);
0102     }
0103     connect(m_label, SIGNAL(valueChanged(double, bool)), this, SLOT(setValueFromProgress(double, bool)));
0104     connect(m_label, &CustomLabel::resetValue, this, &DragValue::slotReset);
0105     setLayout(l);
0106     if (m_intEdit) {
0107         m_label->setMaximumHeight(m_intEdit->sizeHint().height());
0108     } else {
0109         m_label->setMaximumHeight(m_doubleEdit->sizeHint().height());
0110     }
0111 
0112     m_menu = new QMenu(this);
0113 
0114     m_scale = new KSelectAction(i18n("Scaling"), this);
0115     m_scale->addAction(i18n("Normal scale"));
0116     m_scale->addAction(i18n("Pixel scale"));
0117     m_scale->addAction(i18n("Nonlinear scale"));
0118     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
0119     m_menu->addAction(m_scale);
0120 
0121     m_directUpdate = new QAction(i18n("Direct update"), this);
0122     m_directUpdate->setCheckable(true);
0123     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
0124     m_menu->addAction(m_directUpdate);
0125 
0126     QAction *reset = new QAction(QIcon::fromTheme(QStringLiteral("edit-undo")), i18n("Reset value"), this);
0127     connect(reset, &QAction::triggered, this, &DragValue::slotReset);
0128     m_menu->addAction(reset);
0129 
0130     if (m_id > -1) {
0131         QAction *timeline = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("Show %1 in timeline", label), this);
0132         connect(timeline, &QAction::triggered, this, &DragValue::slotSetInTimeline);
0133         connect(m_label, &CustomLabel::setInTimeline, this, &DragValue::slotSetInTimeline);
0134         m_menu->addAction(timeline);
0135     }
0136     connect(this, &QWidget::customContextMenuRequested, this, &DragValue::slotShowContextMenu);
0137     connect(m_scale, &KSelectAction::indexTriggered, this, &DragValue::slotSetScaleMode);
0138     connect(m_directUpdate, &QAction::triggered, this, &DragValue::slotSetDirectUpdate);
0139 }
0140 
0141 DragValue::~DragValue()
0142 {
0143     delete m_intEdit;
0144     delete m_doubleEdit;
0145     delete m_menu;
0146     delete m_label;
0147     // delete m_scale;
0148     // delete m_directUpdate;
0149 }
0150 
0151 bool DragValue::eventFilter(QObject *watched, QEvent *event)
0152 {
0153     if (event->type() == QEvent::Wheel) {
0154         // Check if we should ignore the event
0155         bool useEvent = false;
0156         if (m_intEdit) {
0157             useEvent = m_intEdit->hasFocus();
0158         } else if (m_doubleEdit) {
0159             useEvent = m_doubleEdit->hasFocus();
0160         }
0161         if (!useEvent) {
0162             return true;
0163         }
0164 
0165         auto *we = static_cast<QWheelEvent *>(event);
0166         if (we->angleDelta().y() > 0) {
0167             m_label->slotValueInc();
0168         } else {
0169             m_label->slotValueDec();
0170         }
0171         // Stop processing, event accepted
0172         event->accept();
0173         return true;
0174     }
0175     return QObject::eventFilter(watched, event);
0176 }
0177 
0178 bool DragValue::hasEditFocus() const
0179 {
0180     QWidget *fWidget = QApplication::focusWidget();
0181     return ((fWidget != nullptr) && fWidget->parentWidget() == this);
0182 }
0183 
0184 int DragValue::spinSize()
0185 {
0186     if (m_intEdit) {
0187         return m_intEdit->sizeHint().width();
0188     }
0189     return m_doubleEdit->sizeHint().width();
0190 }
0191 
0192 void DragValue::setSpinSize(int width)
0193 {
0194     if (m_intEdit) {
0195         m_intEdit->setMinimumWidth(width);
0196     } else {
0197         m_doubleEdit->setMinimumWidth(width);
0198     }
0199 }
0200 
0201 void DragValue::slotSetInTimeline()
0202 {
0203     Q_EMIT inTimeline(m_id);
0204 }
0205 
0206 int DragValue::precision() const
0207 {
0208     return m_decimals;
0209 }
0210 
0211 qreal DragValue::maximum() const
0212 {
0213     return m_maximum;
0214 }
0215 
0216 qreal DragValue::minimum() const
0217 {
0218     return m_minimum;
0219 }
0220 
0221 qreal DragValue::value() const
0222 {
0223     if (m_intEdit) {
0224         return m_intEdit->value();
0225     }
0226     return m_doubleEdit->value();
0227 }
0228 
0229 void DragValue::setMaximum(qreal max)
0230 {
0231     if (!qFuzzyCompare(m_maximum, max)) {
0232         m_maximum = max;
0233         if (m_intEdit) {
0234             m_intEdit->setRange(m_minimum, m_maximum);
0235         } else {
0236             m_doubleEdit->setRange(m_minimum, m_maximum);
0237         }
0238     }
0239 }
0240 
0241 void DragValue::setMinimum(qreal min)
0242 {
0243     if (!qFuzzyCompare(m_minimum, min)) {
0244         m_minimum = min;
0245         if (m_intEdit) {
0246             m_intEdit->setRange(m_minimum, m_maximum);
0247         } else {
0248             m_doubleEdit->setRange(m_minimum, m_maximum);
0249         }
0250     }
0251 }
0252 
0253 void DragValue::setRange(qreal min, qreal max)
0254 {
0255     m_maximum = max;
0256     m_minimum = min;
0257     if (m_intEdit) {
0258         m_intEdit->setRange(m_minimum, m_maximum);
0259     } else {
0260         m_doubleEdit->setRange(m_minimum, m_maximum);
0261     }
0262     m_label->setMaximum(max - min);
0263 }
0264 
0265 void DragValue::setStep(qreal step)
0266 {
0267     if (m_intEdit) {
0268         m_intEdit->setSingleStep(step);
0269     } else {
0270         m_doubleEdit->setSingleStep(step);
0271     }
0272     m_label->setStep(step);
0273 }
0274 
0275 void DragValue::slotReset()
0276 {
0277     if (m_intEdit) {
0278         m_intEdit->blockSignals(true);
0279         m_intEdit->setValue(m_default);
0280         m_intEdit->blockSignals(false);
0281         Q_EMIT valueChanged((int)m_default, true);
0282     } else {
0283         m_doubleEdit->blockSignals(true);
0284         m_doubleEdit->setValue(m_default);
0285         m_doubleEdit->blockSignals(false);
0286         Q_EMIT valueChanged(m_default, true);
0287     }
0288     m_label->setProgressValue((m_default - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
0289 }
0290 
0291 void DragValue::slotSetValue(int value)
0292 {
0293     setValue(value, true);
0294 }
0295 
0296 void DragValue::slotSetValue(double value)
0297 {
0298     setValue(value, true);
0299 }
0300 
0301 void DragValue::setValueFromProgress(double value, bool final)
0302 {
0303     value = m_minimum + value * (m_maximum - m_minimum) / m_label->maximum();
0304     if (m_decimals == 0) {
0305         setValue(qRound(value), final);
0306     } else {
0307         setValue(value, final);
0308     }
0309 }
0310 
0311 void DragValue::setValue(double value, bool final)
0312 {
0313     value = qBound(m_minimum, value, m_maximum);
0314     if (m_intEdit && m_intEdit->singleStep() > 1) {
0315         int div = (value - m_minimum) / m_intEdit->singleStep();
0316         value = m_minimum + (div * m_intEdit->singleStep());
0317     }
0318     m_label->setProgressValue((value - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
0319     if (m_intEdit) {
0320         m_intEdit->blockSignals(true);
0321         m_intEdit->setValue((int)value);
0322         m_intEdit->blockSignals(false);
0323         Q_EMIT valueChanged((int)value, final);
0324     } else {
0325         m_doubleEdit->blockSignals(true);
0326         m_doubleEdit->setValue(value);
0327         m_doubleEdit->blockSignals(false);
0328         Q_EMIT valueChanged(value, final);
0329     }
0330 }
0331 
0332 void DragValue::slotEditingFinished()
0333 {
0334     qDebug() << "::: EDITING FINISHED...";
0335     if (m_intEdit) {
0336         int newValue = m_intEdit->value();
0337         m_intEdit->blockSignals(true);
0338         if (m_intEdit->singleStep() > 1) {
0339             int div = (newValue - m_minimum) / m_intEdit->singleStep();
0340             newValue = m_minimum + (div * m_intEdit->singleStep());
0341             m_intEdit->setValue(newValue);
0342         }
0343         m_intEdit->clearFocus();
0344         m_intEdit->blockSignals(false);
0345         if (!KdenliveSettings::dragvalue_directupdate()) {
0346             Q_EMIT valueChanged((double)newValue, true);
0347         }
0348     } else {
0349         double value = m_doubleEdit->value();
0350         m_doubleEdit->blockSignals(true);
0351         m_doubleEdit->clearFocus();
0352         m_doubleEdit->blockSignals(false);
0353         if (!KdenliveSettings::dragvalue_directupdate()) {
0354             Q_EMIT valueChanged(value, true);
0355         }
0356     }
0357 }
0358 
0359 void DragValue::slotShowContextMenu(const QPoint &pos)
0360 {
0361     // values might have been changed by another object of this class
0362     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
0363     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
0364     m_menu->exec(mapToGlobal(pos));
0365 }
0366 
0367 void DragValue::slotSetScaleMode(int mode)
0368 {
0369     KdenliveSettings::setDragvalue_mode(mode);
0370 }
0371 
0372 void DragValue::slotSetDirectUpdate(bool directUpdate)
0373 {
0374     KdenliveSettings::setDragvalue_directupdate(directUpdate);
0375 }
0376 
0377 void DragValue::setInTimelineProperty(bool intimeline)
0378 {
0379     if (m_label->property("inTimeline").toBool() == intimeline) {
0380         return;
0381     }
0382     m_label->setProperty("inTimeline", intimeline);
0383     style()->unpolish(m_label);
0384     style()->polish(m_label);
0385     m_label->update();
0386     if (m_intEdit) {
0387         m_intEdit->setProperty("inTimeline", intimeline);
0388         style()->unpolish(m_intEdit);
0389         style()->polish(m_intEdit);
0390         m_intEdit->update();
0391     } else {
0392         m_doubleEdit->setProperty("inTimeline", intimeline);
0393         style()->unpolish(m_doubleEdit);
0394         style()->polish(m_doubleEdit);
0395         m_doubleEdit->update();
0396     }
0397 }
0398 
0399 CustomLabel::CustomLabel(const QString &label, bool showSlider, int range, QWidget *parent)
0400     : QProgressBar(parent)
0401     , m_dragMode(false)
0402     , m_showSlider(showSlider)
0403     , m_step(10.0)
0404     , m_value(0.)
0405 // m_precision(pow(10, precision)),
0406 {
0407     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0408     setFormat(QLatin1Char(' ') + label);
0409     setFocusPolicy(Qt::StrongFocus);
0410     setCursor(Qt::PointingHandCursor);
0411     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0412     if (m_showSlider) {
0413         setToolTip(xi18n("Shift + Drag to adjust value one by one."));
0414     }
0415     if (showSlider) {
0416         setRange(0, 1000);
0417     } else {
0418         setRange(0, range);
0419         QSize sh;
0420         const QFontMetrics &fm = fontMetrics();
0421         sh.setWidth(fm.horizontalAdvance(QLatin1Char(' ') + label + QLatin1Char(' ')));
0422         setMaximumWidth(sh.width());
0423         setObjectName(QStringLiteral("dragOnly"));
0424     }
0425     setValue(0);
0426 }
0427 
0428 void CustomLabel::mousePressEvent(QMouseEvent *e)
0429 {
0430     if (e->button() == Qt::LeftButton) {
0431         m_dragStartPosition = m_dragLastPosition = e->pos();
0432         e->accept();
0433     } else if (e->button() == Qt::MiddleButton) {
0434         Q_EMIT resetValue();
0435         m_dragStartPosition = QPoint(-1, -1);
0436     } else {
0437         QWidget::mousePressEvent(e);
0438     }
0439 }
0440 
0441 void CustomLabel::mouseMoveEvent(QMouseEvent *e)
0442 {
0443     if ((e->buttons() & Qt::LeftButton) && m_dragStartPosition != QPoint(-1, -1)) {
0444         if (!m_dragMode && (e->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
0445             m_dragMode = true;
0446             m_dragLastPosition = e->pos();
0447             e->accept();
0448             return;
0449         }
0450         if (m_dragMode) {
0451             if (KdenliveSettings::dragvalue_mode() > 0 || !m_showSlider) {
0452 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0453                 int diff = e->pos().x() - m_dragLastPosition.x();
0454 #else
0455                 int diff = e->position().x() - m_dragLastPosition.x();
0456 #endif
0457                 if (qApp->isRightToLeft()) {
0458                     diff = 0 - diff;
0459                 }
0460 
0461                 if (e->modifiers() == Qt::ControlModifier) {
0462                     diff *= 2;
0463                 } else if (e->modifiers() == Qt::ShiftModifier) {
0464                     diff /= 2;
0465                 }
0466                 if (KdenliveSettings::dragvalue_mode() == 2) {
0467                     diff = (diff > 0 ? 1 : -1) * pow(diff, 2);
0468                 }
0469                 double nv = m_value + diff * m_step;
0470                 if (!qFuzzyCompare(nv, m_value)) {
0471                     setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
0472                 }
0473             } else {
0474                 double nv;
0475                 if (qApp->isLeftToRight()) {
0476                     nv = minimum() + ((double)maximum() - minimum()) / width() * e->pos().x();
0477                 } else {
0478                     nv = maximum() - ((double)maximum() - minimum()) / width() * e->pos().x();
0479                 }
0480                 if (!qFuzzyCompare(nv, value())) {
0481                     if (m_step > 1) {
0482                         int current = (int)value();
0483                         int diff = (nv - current) / m_step;
0484                         setNewValue(current + diff * m_step, true);
0485                     } else {
0486                         if (e->modifiers() == Qt::ShiftModifier) {
0487                             double current = value();
0488                             if (e->pos().x() > m_dragLastPosition.x()) {
0489                                 nv = qMin(current + 1, (double)maximum());
0490                             } else {
0491                                 nv = qMax((double)minimum(), current - 1);
0492                             }
0493                         }
0494                         setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
0495                     }
0496                 }
0497             }
0498             m_dragLastPosition = e->pos();
0499             e->accept();
0500         }
0501     } else {
0502         QProgressBar::mouseMoveEvent(e);
0503     }
0504 }
0505 
0506 void CustomLabel::mouseReleaseEvent(QMouseEvent *e)
0507 {
0508     if (e->button() == Qt::MiddleButton) {
0509         e->accept();
0510         return;
0511     }
0512     if (e->modifiers() == Qt::ControlModifier) {
0513         Q_EMIT setInTimeline();
0514         e->accept();
0515         return;
0516     }
0517     if (m_dragMode) {
0518         setNewValue(m_value, true);
0519         m_dragLastPosition = m_dragStartPosition;
0520         e->accept();
0521     } else if (m_showSlider) {
0522         int newVal = (double)maximum() * e->pos().x() / width();
0523         if (qApp->isRightToLeft()) {
0524             newVal = maximum() - newVal;
0525         }
0526         if (m_step > 1) {
0527             int current = (int)value();
0528             int diff = (newVal - current) / m_step;
0529             setNewValue(current + diff * m_step, true);
0530         } else {
0531             setNewValue(newVal, true);
0532         }
0533         m_dragLastPosition = m_dragStartPosition;
0534         e->accept();
0535     }
0536     m_dragMode = false;
0537 }
0538 
0539 void CustomLabel::wheelEvent(QWheelEvent *e)
0540 {
0541     qDebug()<<":::: GOT WHEEL DELTA: "<<e->angleDelta().y();
0542     if (e->angleDelta().y() > 0) {
0543         if (e->modifiers() == Qt::ControlModifier) {
0544             slotValueInc(10);
0545         } else if (e->modifiers() == Qt::AltModifier) {
0546             slotValueInc(0.1);
0547         } else {
0548             slotValueInc();
0549         }
0550     } else {
0551         if (e->modifiers() == Qt::ControlModifier) {
0552             slotValueDec(10);
0553         } else if (e->modifiers() == Qt::AltModifier) {
0554             slotValueDec(0.1);
0555         } else {
0556             slotValueDec();
0557         }
0558     }
0559     e->accept();
0560 }
0561 
0562 void CustomLabel::slotValueInc(double factor)
0563 {
0564     setNewValue(m_value + m_step * factor, true);
0565 }
0566 
0567 void CustomLabel::slotValueDec(double factor)
0568 {
0569     setNewValue(m_value - m_step * factor, true);
0570 }
0571 
0572 void CustomLabel::setProgressValue(double value)
0573 {
0574     m_value = value;
0575     setValue(qRound(value));
0576 }
0577 
0578 void CustomLabel::setNewValue(double value, bool update)
0579 {
0580     m_value = value;
0581     setValue(qRound(value));
0582     Q_EMIT valueChanged(value, update);
0583 }
0584 
0585 void CustomLabel::setStep(double step)
0586 {
0587     m_step = step;
0588 }