File indexing completed on 2024-05-12 04:19:41

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2011 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "hud/hudslider.h"
0023 
0024 // Local
0025 #include "gwenview_lib_debug.h"
0026 #include <hud/hudtheme.h>
0027 
0028 // KF
0029 
0030 // Qt
0031 #include <QApplication>
0032 #include <QGraphicsSceneEvent>
0033 #include <QPainter>
0034 #include <QStyle>
0035 #include <QStyleOptionGraphicsItem>
0036 #include <QTimer>
0037 
0038 namespace Gwenview
0039 {
0040 static const int FIRST_REPEAT_DELAY = 500;
0041 
0042 struct HudSliderPrivate {
0043     HudSlider *q = nullptr;
0044     int mMin, mMax, mPageStep, mSingleStep;
0045     int mSliderPosition;
0046     int mRepeatX;
0047     QAbstractSlider::SliderAction mRepeatAction;
0048     int mValue;
0049     bool mIsDown;
0050 
0051     QRectF mHandleRect;
0052 
0053     bool hasValidRange() const
0054     {
0055         return mMax > mMin;
0056     }
0057 
0058     void updateHandleRect()
0059     {
0060         static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
0061         static const int radius = renderInfo.borderRadius;
0062 
0063         const QRectF sliderRect = q->boundingRect();
0064         const qreal posX = xForPosition(mSliderPosition) - radius;
0065         const qreal posY = sliderRect.height() / 2 - radius;
0066         mHandleRect = QRectF(posX, posY, radius * 2, radius * 2);
0067     }
0068 
0069     int positionForX(qreal x) const
0070     {
0071         static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
0072         static const int radius = renderInfo.borderRadius;
0073 
0074         const qreal sliderWidth = q->boundingRect().width();
0075 
0076         x -= radius;
0077         if (QApplication::isRightToLeft()) {
0078             x = sliderWidth - 2 * radius - x;
0079         }
0080         return mMin + int(x / (sliderWidth - 2 * radius) * (mMax - mMin));
0081     }
0082 
0083     qreal xForPosition(int pos) const
0084     {
0085         static const HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle);
0086         static const int radius = renderInfo.borderRadius;
0087 
0088         const qreal sliderWidth = q->boundingRect().width();
0089         qreal x = (qreal(pos - mMin) / (mMax - mMin)) * (sliderWidth - 2 * radius);
0090         if (QApplication::isRightToLeft()) {
0091             x = sliderWidth - 2 * radius - x;
0092         }
0093         return x + radius;
0094     }
0095 };
0096 
0097 HudSlider::HudSlider(QGraphicsItem *parent)
0098     : QGraphicsWidget(parent)
0099     , d(new HudSliderPrivate)
0100 {
0101     d->q = this;
0102     d->mMin = 0;
0103     d->mMax = 100;
0104     d->mPageStep = 10;
0105     d->mSingleStep = 1;
0106     d->mSliderPosition = d->mValue = 0;
0107     d->mIsDown = false;
0108     d->mRepeatAction = QAbstractSlider::SliderNoAction;
0109     setCursor(Qt::ArrowCursor);
0110     setAcceptHoverEvents(true);
0111     setFocusPolicy(Qt::WheelFocus);
0112 
0113     QTimer::singleShot(0, this, [this]() {
0114         d->updateHandleRect();
0115     });
0116 }
0117 
0118 HudSlider::~HudSlider()
0119 {
0120     delete d;
0121 }
0122 
0123 void HudSlider::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
0124 {
0125     bool drawHandle = d->hasValidRange();
0126     HudTheme::State state;
0127     if (drawHandle && option->state.testFlag(QStyle::State_MouseOver)) {
0128         state = d->mIsDown ? HudTheme::DownState : HudTheme::MouseOverState;
0129     } else {
0130         state = HudTheme::NormalState;
0131     }
0132     painter->setRenderHint(QPainter::Antialiasing);
0133 
0134     const QRectF sliderRect = boundingRect();
0135 
0136     // Groove
0137     HudTheme::RenderInfo renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetGroove, state);
0138     painter->setPen(renderInfo.borderPen);
0139     painter->setBrush(renderInfo.bgBrush);
0140     qreal centerY = d->mHandleRect.center().y();
0141     QRectF grooveRect = QRectF(0, centerY - renderInfo.borderRadius, sliderRect.width(), 2 * renderInfo.borderRadius);
0142 
0143     if (drawHandle) {
0144         // Clip out handle
0145         QPainterPath clipPath;
0146         clipPath.addRect(QRectF(QPointF(0, 0), d->mHandleRect.bottomLeft()).adjusted(0, 0, 1, 0));
0147         clipPath.addRect(QRectF(d->mHandleRect.topRight(), sliderRect.bottomRight()).adjusted(-1, 0, 0, 0));
0148         painter->setClipPath(clipPath);
0149     }
0150     painter->drawRoundedRect(grooveRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius);
0151     if (!drawHandle) {
0152         return;
0153     }
0154     painter->setClipping(false);
0155 
0156     // Handle
0157     renderInfo = HudTheme::renderInfo(HudTheme::SliderWidgetHandle, state);
0158     painter->setPen(renderInfo.borderPen);
0159     painter->setBrush(renderInfo.bgBrush);
0160     painter->drawRoundedRect(d->mHandleRect.adjusted(.5, .5, -.5, -.5), renderInfo.borderRadius, renderInfo.borderRadius);
0161 }
0162 
0163 void HudSlider::mousePressEvent(QGraphicsSceneMouseEvent *event)
0164 {
0165     if (!d->hasValidRange()) {
0166         return;
0167     }
0168     const int pos = d->positionForX(event->pos().x());
0169     if (d->mHandleRect.contains(event->pos())) {
0170         switch (event->button()) {
0171         case Qt::LeftButton:
0172             d->mIsDown = true;
0173             break;
0174         case Qt::MiddleButton:
0175             setSliderPosition(pos);
0176             triggerAction(QAbstractSlider::SliderMove);
0177             break;
0178         default:
0179             break;
0180         }
0181     } else {
0182         d->mRepeatX = event->pos().x();
0183         d->mRepeatAction = pos < d->mSliderPosition ? QAbstractSlider::SliderPageStepSub : QAbstractSlider::SliderPageStepAdd;
0184         doRepeatAction(FIRST_REPEAT_DELAY);
0185     }
0186     update();
0187 }
0188 
0189 void HudSlider::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
0190 {
0191     if (!d->hasValidRange()) {
0192         return;
0193     }
0194     if (d->mIsDown) {
0195         setSliderPosition(d->positionForX(event->pos().x()));
0196         triggerAction(QAbstractSlider::SliderMove);
0197         update();
0198     }
0199 }
0200 
0201 void HudSlider::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/)
0202 {
0203     if (!d->hasValidRange()) {
0204         return;
0205     }
0206     d->mIsDown = false;
0207     d->mRepeatAction = QAbstractSlider::SliderNoAction;
0208     update();
0209 }
0210 
0211 void HudSlider::wheelEvent(QGraphicsSceneWheelEvent *event)
0212 {
0213     if (!d->hasValidRange()) {
0214         return;
0215     }
0216     int step = qMin(QApplication::wheelScrollLines() * d->mSingleStep, d->mPageStep);
0217     if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
0218         step = d->mPageStep;
0219     }
0220     setSliderPosition(d->mSliderPosition + event->delta() * step / 120);
0221     triggerAction(QAbstractSlider::SliderMove);
0222 }
0223 
0224 void HudSlider::keyPressEvent(QKeyEvent *event)
0225 {
0226     if (!d->hasValidRange()) {
0227         return;
0228     }
0229     bool rtl = QApplication::isRightToLeft();
0230     switch (event->key()) {
0231     case Qt::Key_Left:
0232         triggerAction(rtl ? QAbstractSlider::SliderSingleStepAdd : QAbstractSlider::SliderSingleStepSub);
0233         break;
0234     case Qt::Key_Right:
0235         triggerAction(rtl ? QAbstractSlider::SliderSingleStepSub : QAbstractSlider::SliderSingleStepAdd);
0236         break;
0237     case Qt::Key_PageUp:
0238         triggerAction(QAbstractSlider::SliderPageStepSub);
0239         break;
0240     case Qt::Key_PageDown:
0241         triggerAction(QAbstractSlider::SliderPageStepAdd);
0242         break;
0243     case Qt::Key_Home:
0244         triggerAction(QAbstractSlider::SliderToMinimum);
0245         break;
0246     case Qt::Key_End:
0247         triggerAction(QAbstractSlider::SliderToMaximum);
0248         break;
0249     default:
0250         event->ignore();
0251         break;
0252     }
0253 }
0254 
0255 void HudSlider::keyReleaseEvent(QKeyEvent * /*event*/)
0256 {
0257     if (!d->hasValidRange()) {
0258         return;
0259     }
0260     d->mRepeatAction = QAbstractSlider::SliderNoAction;
0261 }
0262 
0263 void HudSlider::setRange(int min, int max)
0264 {
0265     if (min == d->mMin && max == d->mMax) {
0266         return;
0267     }
0268     d->mMin = min;
0269     d->mMax = max;
0270     setValue(d->mValue); // ensure value is within min and max
0271     d->updateHandleRect();
0272     update();
0273 }
0274 
0275 void HudSlider::setPageStep(int step)
0276 {
0277     d->mPageStep = step;
0278 }
0279 
0280 void HudSlider::setSingleStep(int step)
0281 {
0282     d->mSingleStep = step;
0283 }
0284 
0285 void HudSlider::setValue(int value)
0286 {
0287     value = qBound(d->mMin, value, d->mMax);
0288     if (value != d->mValue) {
0289         d->mValue = value;
0290         setSliderPosition(value);
0291         update();
0292         Q_EMIT valueChanged(d->mValue);
0293     }
0294 }
0295 
0296 int HudSlider::sliderPosition() const
0297 {
0298     return d->mSliderPosition;
0299 }
0300 
0301 void HudSlider::setSliderPosition(int pos)
0302 {
0303     pos = qBound(d->mMin, pos, d->mMax);
0304     if (pos != d->mSliderPosition) {
0305         d->mSliderPosition = pos;
0306         d->updateHandleRect();
0307         update();
0308     }
0309 }
0310 
0311 bool HudSlider::isSliderDown() const
0312 {
0313     return d->mIsDown;
0314 }
0315 
0316 void HudSlider::triggerAction(QAbstractSlider::SliderAction action)
0317 {
0318     switch (action) {
0319     case QAbstractSlider::SliderSingleStepAdd:
0320         setSliderPosition(d->mValue + d->mSingleStep);
0321         break;
0322     case QAbstractSlider::SliderSingleStepSub:
0323         setSliderPosition(d->mValue - d->mSingleStep);
0324         break;
0325     case QAbstractSlider::SliderPageStepAdd:
0326         setSliderPosition(d->mValue + d->mPageStep);
0327         break;
0328     case QAbstractSlider::SliderPageStepSub:
0329         setSliderPosition(d->mValue - d->mPageStep);
0330         break;
0331     case QAbstractSlider::SliderToMinimum:
0332         setSliderPosition(d->mMin);
0333         break;
0334     case QAbstractSlider::SliderToMaximum:
0335         setSliderPosition(d->mMax);
0336         break;
0337     case QAbstractSlider::SliderMove:
0338     case QAbstractSlider::SliderNoAction:
0339         break;
0340     };
0341     Q_EMIT actionTriggered(action);
0342     setValue(d->mSliderPosition);
0343 }
0344 
0345 void HudSlider::doRepeatAction(int time)
0346 {
0347     int step = 0;
0348     switch (d->mRepeatAction) {
0349     case QAbstractSlider::SliderSingleStepAdd:
0350     case QAbstractSlider::SliderSingleStepSub:
0351         step = d->mSingleStep;
0352         break;
0353     case QAbstractSlider::SliderPageStepAdd:
0354     case QAbstractSlider::SliderPageStepSub:
0355         step = d->mPageStep;
0356         break;
0357     case QAbstractSlider::SliderToMinimum:
0358     case QAbstractSlider::SliderToMaximum:
0359     case QAbstractSlider::SliderMove:
0360         qCWarning(GWENVIEW_LIB_LOG) << "Not much point in repeating action of type" << d->mRepeatAction;
0361         return;
0362     case QAbstractSlider::SliderNoAction:
0363         return;
0364     }
0365 
0366     int pos = d->positionForX(d->mRepeatX);
0367     if (qAbs(pos - d->mSliderPosition) >= step) {
0368         // We are far enough from the position where the mouse button was held
0369         // down to be able to repeat the action one more time
0370         triggerAction(d->mRepeatAction);
0371         QTimer::singleShot(time, this, SLOT(doRepeatAction()));
0372     } else {
0373         // We are too close to the held down position, reach the position and
0374         // don't repeat
0375         d->mRepeatAction = QAbstractSlider::SliderNoAction;
0376         setSliderPosition(pos);
0377         triggerAction(QAbstractSlider::SliderMove);
0378         return;
0379     }
0380 }
0381 
0382 } // namespace
0383 
0384 #include "moc_hudslider.cpp"