Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_slider_spin_box_p.h
Go to the documentation of this file.
1/* This file is part of the KDE project
2 *
3 * SPDX-FileCopyrightText: 2010 Justin Noel <justin@ics.com>
4 * SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
5 * SPDX-FileCopyrightText: 2015 Moritz Molch <kde@moritzmolch.de>
6 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
7 *
8 * SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10
11#ifndef KISSLIDERSPINBOXPRIVATE_H
12#define KISSLIDERSPINBOXPRIVATE_H
13
14#include <QObject>
15#include <QLineEdit>
16#include <QStyleOptionSpinBox>
17#include <QPainter>
18#include <QEvent>
19#include <QApplication>
20#include <QMouseEvent>
21#include <QTimer>
22#include <QStyleHints>
23#include <QMenu>
24#include <QVariantAnimation>
25#include <QPointer>
26
27#include <cmath>
28#include <utility>
29#include <type_traits>
30
31#include <ksharedconfig.h>
32#include <kconfiggroup.h>
33#include <kis_cursor.h>
34#include <kis_num_parser.h>
35#include <klocalizedstring.h>
36#include <kis_painting_tweaks.h>
39#include <kis_algebra_2d.h>
41
42template <typename SpinBoxTypeTP, typename BaseSpinBoxTypeTP>
43class Q_DECL_HIDDEN KisSliderSpinBoxPrivate : public QObject
44{
45public:
46 using SpinBoxType = SpinBoxTypeTP;
47 using BaseSpinBoxType = BaseSpinBoxTypeTP;
48 using ValueType = decltype(std::declval<SpinBoxType>().value());
49
51 {
54 ValueUpdateMode_UseValueBeforeEditing
55 };
56
58 : m_q(q)
59 , m_lineEdit(m_q->lineEdit())
60 , m_startEditingSignalProxy(std::bind(&KisSliderSpinBoxPrivate::startEditing, this))
61 {
62 m_q->installEventFilter(this);
63
64 m_lineEdit->setReadOnly(true);
65 m_lineEdit->setAlignment(Qt::AlignCenter);
66 m_lineEdit->setAutoFillBackground(false);
67 m_lineEdit->setCursor(KisCursor::splitHCursor());
68 m_lineEdit->installEventFilter(this);
69
70 m_widgetRangeToggle = new QWidget(m_q);
71 m_widgetRangeToggle->hide();
72 m_widgetRangeToggle->installEventFilter(this);
73
74 m_sliderAnimation.setStartValue(0.0);
75 m_sliderAnimation.setEndValue(1.0);
76 m_sliderAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InOutCubic));
77 connect(&m_sliderAnimation, &QVariantAnimation::valueChanged, m_lineEdit, QOverload<>::of(&QLineEdit::update));
78 connect(&m_sliderAnimation, &QVariantAnimation::valueChanged, m_widgetRangeToggle, QOverload<>::of(&QLineEdit::update));
79
80 m_rangeToggleHoverAnimation.setStartValue(0.0);
81 m_rangeToggleHoverAnimation.setEndValue(1.0);
82 m_rangeToggleHoverAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InOutCubic));
83 connect(&m_rangeToggleHoverAnimation, &QVariantAnimation::valueChanged, m_widgetRangeToggle, QOverload<>::of(&QLineEdit::update));
84 }
85
87 {
88 if (isEditModeActive()) {
89 return;
90 }
91 // Store the current value
92 m_valueBeforeEditing = m_q->value();
93 m_lineEdit->setReadOnly(false);
94 m_q->selectAll();
95 m_lineEdit->setFocus(Qt::OtherFocusReason);
96 m_lineEdit->setCursor(KisCursor::ibeamCursor());
97 }
98
99 void endEditing(ValueUpdateMode updateMode = ValueUpdateMode_UseLastValidValue)
100 {
101 if (!isEditModeActive()) {
102 return;
103 }
104 if (updateMode == ValueUpdateMode_UseLastValidValue) {
105 setValue(m_q->value(), false, false, true);
106 } else if (updateMode == ValueUpdateMode_UseValueBeforeEditing) {
107 setValue(m_valueBeforeEditing, false, false, true);
108 }
109 // Restore palette colors
110 QPalette pal = m_lineEdit->palette();
111 pal.setBrush(QPalette::Text, m_q->palette().text());
112 m_lineEdit->setPalette(pal);
113 m_rightClickCounter = 0;
114 m_lineEdit->setReadOnly(true);
115 m_lineEdit->setCursor(KisCursor::splitHCursor());
116 m_lineEdit->update();
117 m_q->update();
118 }
119
120 bool isEditModeActive() const
121 {
122 return !m_lineEdit->isReadOnly();
123 }
124
125 // Compute a new value as a function of the x and y coordinates relative
126 // to the lineedit, and some combination of modifiers
127 ValueType valueForPoint(const QPoint &p, Qt::KeyboardModifiers modifiers) const
128 {
129 const QRectF rect(m_lineEdit->rect());
130 const QPointF center(
131 static_cast<double>(
132 m_lastMousePressPosition.x() + (m_useRelativeDragging ? m_relativeDraggingOffset : 0)
133 ),
134 rect.height() / 2.0
135 );
136 const bool useSoftRange = isSoftRangeValid() && (m_softRangeViewMode == SoftRangeViewMode_AlwaysShowSoftRange || m_isSoftRangeActive);
137 const double minimum = static_cast<double>(useSoftRange ? m_softMinimum : m_q->minimum());
138 const double maximum = static_cast<double>(useSoftRange ? m_softMaximum : m_q->maximum());
139 const double rangeSize = maximum - minimum;
140 // Get the distance relative to the line edit center and transformed
141 // so that it starts counting 32px away from the widget. If the position
142 // is inside the widget or that 32px area the distance will be 0 so that
143 // the value change will be the same near the widget
144 const double distanceY =
145 std::max(
146 0.0,
147 std::abs(static_cast<double>(p.y()) - center.y()) - center.y() - constantDraggingMargin
148 );
149 // Get the scale
150 double scale;
151 if (modifiers & Qt::ShiftModifier) {
152 // If the shift key is pressed we scale the distanceY value to make the scale
153 // have a stronger effect and also offset it so that the minimum
154 // scale will be 5x (1x + 4x).
155 // function
156 scale = (rect.width() + 2.0 * distanceY * 10.0) / rect.width() + 4.0;
157 } else {
158 // Get the scale as a function of the vertical position
159 scale = (rect.width() + 2.0 * distanceY * 2.0) / rect.width();
160 }
161 // Scale the horizontal coordinates around where we first clicked
162 // as a function of the y coordinate
163 const double scaledRectLeft = (0.0 - center.x()) * scale + center.x();
164 const double scaledRectRight = (rect.width() - center.x()) * scale + center.x();
165 // Map the current horizontal position to the new rect
166 const double scaledRectWidth = scaledRectRight - scaledRectLeft;
167 const double posX = static_cast<double>(p.x()) - scaledRectLeft;
168 // Normalize
169 const double normalizedPosX = qBound(0.0, posX / scaledRectWidth, 1.0);
170 // Final value
171 const double normalizedValue = std::pow(normalizedPosX, m_exponentRatio);
172 double value = normalizedValue * rangeSize + minimum;
173 // If key CTRL is pressed, round to the closest step.
174 if (modifiers & Qt::ControlModifier) {
175 value = std::round(value / m_fastSliderStep) * m_fastSliderStep;
176 }
177 //Return the value
178 if (std::is_same<ValueType, double>::value) {
179 return value;
180 } else {
181 return static_cast<ValueType>(std::round(value));
182 }
183 }
184
186 {
187 ValueType min, max;
188 if (isSoftRangeValid()) {
189 if (m_softRangeViewMode == SoftRangeViewMode_ShowBothRanges) {
190 if (m_isSoftRangeActive) {
191 min = softMinimum();
192 max = softMaximum();
193 } else {
194 min = m_q->minimum();
195 max = m_q->maximum();
196 }
197 } else {
198 min = softMinimum();
199 max = softMaximum();
200 }
201 } else {
202 min = m_q->minimum();
203 max = m_q->maximum();
204 }
205 return QPoint(static_cast<int>(qRound(computeSliderWidth(min, max, value))), 0);
206 }
207
208 // Custom setValue that allows disabling signal emission
209 void setValue(ValueType newValue,
210 bool blockSignals = false,
211 bool emitSignalsEvenWhenValueNotChanged = false,
212 bool overwriteExpression = false)
213 {
214 if (blockSignals) {
215 m_q->blockSignals(true);
216 m_q->BaseSpinBoxType::setValue(newValue, overwriteExpression);
217 m_q->blockSignals(false);
218 } else {
219 ValueType v = m_q->value();
220 m_q->BaseSpinBoxType::setValue(newValue, overwriteExpression);
221 if (v == m_q->value() && emitSignalsEvenWhenValueNotChanged) {
222 emitSignals();
223 }
224 }
225 if (!m_q->hasFocus()) {
226 endEditing(ValueUpdateMode_NoChange);
227 }
228 }
229
231 {
232 if (isSoftRangeValid() && m_softRangeViewMode == SoftRangeViewMode_ShowBothRanges) {
233 if (m_isSoftRangeActive) {
234 makeSoftRangeActive();
235 } else {
236 makeHardRangeActive();
237 }
238 updateWidgetRangeToggleTooltip();
239 m_widgetRangeToggle->show();
240 } else {
241 m_sliderAnimation.stop();
242 m_widgetRangeToggle->hide();
243 }
244 qResizeEvent(nullptr);
245 }
246
247 template <typename U = SpinBoxTypeTP, typename = typename std::enable_if<std::is_same<ValueType, int>::value, U>::type>
248 void setRange(int newMinimum, int newMaximum, bool computeNewFastSliderStep)
249 {
250 m_q->BaseSpinBoxType::setRange(newMinimum, newMaximum);
251 if (computeNewFastSliderStep) {
252 // Behavior taken from the old slider spinbox. Kind of arbitrary
253 m_fastSliderStep = (m_q->maximum() - m_q->minimum()) / 20;
254 if (m_fastSliderStep == 0) {
255 m_fastSliderStep = 1;
256 }
257 }
258 m_softMinimum = qBound(m_q->minimum(), m_softMinimum, m_q->maximum());
259 m_softMaximum = qBound(m_q->minimum(), m_softMaximum, m_q->maximum());
260 resetRangeMode();
261 m_q->update();
262 }
263
264 template <typename U = SpinBoxTypeTP, typename = typename std::enable_if<std::is_same<ValueType, double>::value, U>::type>
265 void setRange(double newMinimum, double newMaximum, int newNumberOfDecimals, bool computeNewFastSliderStep)
266 {
267 m_q->setDecimals(newNumberOfDecimals);
268 m_q->BaseSpinBoxType::setRange(newMinimum, newMaximum);
269 if (computeNewFastSliderStep) {
270 // Behavior taken from the old slider. Kind of arbitrary
271 const double rangeSize = m_q->maximum() - m_q->minimum();
272 if (rangeSize >= 2.0 || newNumberOfDecimals <= 0) {
273 m_fastSliderStep = 1.0;
274 } else if (newNumberOfDecimals == 1) {
275 m_fastSliderStep = rangeSize / 10.0;
276 } else {
277 m_fastSliderStep = rangeSize / 20.0;
278 }
279 }
280 m_softMinimum = qBound(m_q->minimum(), m_softMinimum, m_q->maximum());
281 m_softMaximum = qBound(m_q->minimum(), m_softMaximum, m_q->maximum());
282 resetRangeMode();
283 m_lineEdit->update();
284 }
285
286 void setBlockUpdateSignalOnDrag(bool newBlockUpdateSignalOnDrag)
287 {
288 m_blockUpdateSignalOnDrag = newBlockUpdateSignalOnDrag;
289 }
290
291 void setFastSliderStep(ValueType newFastSliderStep)
292 {
293 m_fastSliderStep = newFastSliderStep;
294 }
295
296 // Set the soft range. Set newSoftMinimum = newSoftMaximum to signal that
297 // the soft range must not be used
298 void setSoftRange(ValueType newSoftMinimum, ValueType newSoftMaximum)
299 {
300 if ((newSoftMinimum != newSoftMaximum) &&
301 (newSoftMinimum > newSoftMaximum || newSoftMinimum < m_q->minimum() || newSoftMaximum > m_q->maximum())) {
302 return;
303 }
304 m_softMinimum = newSoftMinimum;
305 m_softMaximum = newSoftMaximum;
306 resetRangeMode();
307 m_lineEdit->update();
308 }
309
310 bool isSoftRangeValid() const
311 {
312 return m_softMaximum > m_softMinimum;
313 }
314
316 {
317 return m_fastSliderStep;
318 }
319
321 {
322 return m_softMinimum;
323 }
324
326 {
327 return m_softMaximum;
328 }
329
330 bool isDragging() const
331 {
332 return m_isDragging;
333 }
334
336 {
337 m_sliderAnimation.stop();
338 m_isSoftRangeActive = true;
339 // scale the animation duration in case the animation is in the middle
340 const int animationDuration =
341 static_cast<int>(std::round(m_sliderAnimation.currentValue().toReal() * fullAnimationDuration));
342 m_sliderAnimation.setStartValue(m_sliderAnimation.currentValue());
343 m_sliderAnimation.setEndValue(0.0);
344 m_sliderAnimation.setDuration(animationDuration);
345 m_sliderAnimation.start();
346 }
347
349 {
350 m_sliderAnimation.stop();
351 m_isSoftRangeActive = false;
352 // scale the duration in case the animation is in the middle
353 const int animationDuration =
354 static_cast<int>(std::round((1.0 - m_sliderAnimation.currentValue().toReal()) * fullAnimationDuration));
355 m_sliderAnimation.setStartValue(m_sliderAnimation.currentValue());
356 m_sliderAnimation.setEndValue(1.0);
357 m_sliderAnimation.setDuration(animationDuration);
358 m_sliderAnimation.start();
359 }
360
361 void setExponentRatio(double newExponentRatio)
362 {
363 m_exponentRatio = newExponentRatio;
364 m_lineEdit->update();
365 }
366
368 {
369 m_widgetRangeToggle->setToolTip(
370 i18nc(
371 "@info:tooltip toggle between soft and hard range in the slider spin box",
372 "Toggle between full range and subrange.\nFull range: [%1, %2]\nSubrange: [%3, %4]",
373 QString::number(m_q->minimum()),
374 QString::number(m_q->maximum()),
375 QString::number(m_softMinimum),
376 QString::number(m_softMaximum)
377 )
378 );
379 }
380
381 QSize sizeHint() const
382 {
383 QSize hint = m_q->BaseSpinBoxType::sizeHint();
384 return
385 (isSoftRangeValid() && m_softRangeViewMode == SoftRangeViewMode_ShowBothRanges)
386 ? QSize(hint.width() + widthOfRangeModeToggle, hint.height())
387 : hint;
388 }
389
390 QSize minimumSizeHint() const
391 {
392 QSize hint = m_q->BaseSpinBoxType::minimumSizeHint();
393 return
394 (isSoftRangeValid() && m_softRangeViewMode == SoftRangeViewMode_ShowBothRanges)
395 ? QSize(hint.width() + widthOfRangeModeToggle, hint.height())
396 : hint;
397 }
398
399 void emitSignals() const
400 {
401#if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
402 Q_EMIT m_q->textChanged(m_q->text());
403#else
404 Q_EMIT m_q->valueChanged(m_q->text());
405#endif
406 Q_EMIT m_q->valueChanged(m_q->value());
407 }
408
409 bool qResizeEvent(QResizeEvent*)
410 {
411 // When resizing the spinbox, perform style specific positioning
412 // of the lineedit
413
414 // Get the default rect for the lineedit widget
415 QStyleOptionSpinBox spinBoxOptions;
416 m_q->initStyleOption(&spinBoxOptions);
417 QRect rect = m_q->style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField);
418 // Offset the rect to make it take all the available space inside the
419 // spinbox, without overlapping the buttons
420 QString style = qApp->property(currentUnderlyingStyleNameProperty).toString().toLower();
421 if (style == "breeze") {
422 rect.adjust(-4, -4, 0, 4);
423 } else if (style == "fusion") {
424 rect.adjust(-2, -1, 2, 1);
425 }
426 // Set the rect
427 if (isSoftRangeValid() && m_softRangeViewMode == SoftRangeViewMode_ShowBothRanges) {
428 m_lineEdit->setGeometry(rect.adjusted(0, 0, -widthOfRangeModeToggle, 0));
429 m_widgetRangeToggle->setGeometry(rect.adjusted(rect.width() - widthOfRangeModeToggle, 0, 0, 0));
430 } else {
431 m_lineEdit->setGeometry(rect);
432 }
433
434 return true;
435 }
436
437 bool qFocusOutEvent(QFocusEvent*)
438 {
439 // If the focus is lost then the edition stops, unless the focus
440 // was lost because the menu was shown
441 if (m_focusLostDueToMenu) {
442 m_focusLostDueToMenu = false;
443 } else {
444 if (m_q->isLastValid()) {
445 endEditing();
446 }
447 }
448 return false;
449 }
450
451 bool qMousePressEvent(QMouseEvent*)
452 {
453 // If we click in any part of the spinbox outside the lineedit
454 // then the edition stops
455 endEditing();
456 return false;
457 }
458
459 bool qKeyPressEvent(QKeyEvent *e)
460 {
461 switch (e->key()) {
462 // If the lineedit is not in edition mode, make the left and right
463 // keys have the same effect as the down and up keys. This replicates
464 // the behaviour of the old slider spinbox
465 case Qt::Key_Right:
466 if (!isEditModeActive()) {
467 qApp->postEvent(m_q, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, e->modifiers()));
468 return true;
469 }
470 break;
471 case Qt::Key_Left:
472 if (!isEditModeActive()) {
473 qApp->postEvent(m_q, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, e->modifiers()));
474 return true;
475 }
476 break;
477 // The enter key can be used to enter the edition mode if the
478 // lineedit is not in it or to commit the entered value and leave
479 // the edition mode if we are in it
480 case Qt::Key_Enter:
481 case Qt::Key_Return:
482 if (!e->isAutoRepeat()) {
483 if (!isEditModeActive()) {
484 startEditing();
485 } else {
486 if (m_q->isLastValid()) {
487 endEditing();
488 } else {
489 return false;
490 }
491 }
492 }
493 return true;
494 // The escape key can be used to leave the edition mode rejecting
495 // the written value
496 case Qt::Key_Escape:
497 if (isEditModeActive()) {
498 endEditing(ValueUpdateMode_UseValueBeforeEditing);
499 return true;
500 }
501 break;
502 // If we press a number key when in slider mode, then start the edit
503 // mode and resend the key event so that the key is processed again
504 // in edit mode. Since entering edit mode selects all text, the new
505 // event will replace the text by the new key text
506 default:
507 if (!isEditModeActive() && e->key() >= Qt::Key_0 && e->key() <= Qt::Key_9) {
508 startEditing();
509 qApp->postEvent(m_q, new QKeyEvent(QEvent::KeyPress, e->key(), e->modifiers(), e->text(), e->isAutoRepeat()));
510 return true;
511 }
512 break;
513 }
514 return false;
515 }
516
517 bool qContextMenuEvent(QContextMenuEvent *e)
518 {
519 // Shows a menu. Code inspired by the QAbstractSpinBox
520 // contextMenuEvent function
521
522 // Return if we are in slider mode and the mouse is in the line edit
523 if (!isEditModeActive() && m_lineEdit->rect().contains(e->pos())) {
524 return true;
525 }
526 // Create the menu
527 QPointer<QMenu> menu;
528 // If we are in edit mode, then add the line edit
529 // actions
530 if (isEditModeActive()) {
531 menu = m_lineEdit->createStandardContextMenu();
532 m_focusLostDueToMenu = true;
533 } else {
534 menu = new QMenu;
535 }
536 if (!menu) {
537 return true;
538 }
539 // Override select all action
540 QAction *selectAllAction = nullptr;
541 if (isEditModeActive()) {
542 selectAllAction = new QAction(i18nc("Menu action to select all text in the slider spin box", "&Select All"), menu);
543#if QT_CONFIG(shortcut)
544 selectAllAction->setShortcut(QKeySequence::SelectAll);
545#endif
546 menu->removeAction(menu->actions().last());
547 menu->addAction(selectAllAction);
548 menu->addSeparator();
549 }
550 // Add step up and step down actions
551 const uint stepEnabled = m_q->stepEnabled();
552 QAction *stepUpAction = menu->addAction(i18nc("Menu action to step up in the slider spin box", "&Step up"));
553 stepUpAction->setEnabled(stepEnabled & SpinBoxType::StepUpEnabled);
554 QAction *stepDown = menu->addAction(i18nc("Menu action to step down in the slider spin box", "Step &down"));
555 stepDown->setEnabled(stepEnabled & SpinBoxType::StepDownEnabled);
556 menu->addSeparator();
557 // This code is taken from QAbstractSpinBox. Use a QPointer in case the
558 // spin box is destroyed while the menu is shown??
559 const QPointer<SpinBoxType> spinbox = m_q;
560 const QPoint pos =
561 (e->reason() == QContextMenuEvent::Mouse)
562 ? e->globalPos()
563 : m_q->mapToGlobal(QPoint(e->pos().x(), 0)) + QPoint(m_q->width() / 2, m_q->height() / 2);
564 const QAction *action = menu->exec(pos);
565 delete static_cast<QMenu *>(menu);
566 if (spinbox && action) {
567 if (action == stepUpAction) {
568 m_q->stepBy(static_cast<ValueType>(1));
569 } else if (action == stepDown) {
570 m_q->stepBy(static_cast<ValueType>(-1));
571 } else if (action == selectAllAction) {
572 m_q->selectAll();
573 }
574 }
575 e->accept();
576 return true;
577 }
578
579 // Generic "style aware" helper function to draw a rect
580 void paintSliderRect(QPainter &painter, const QRectF &rect, const QBrush &brush)
581 {
582 painter.save();
583 painter.setBrush(brush);
584 painter.setPen(Qt::NoPen);
585 if (qApp->property(currentUnderlyingStyleNameProperty).toString().toLower() == "fusion") {
586 painter.drawRoundedRect(rect, 1, 1);
587 } else {
588 painter.drawRoundedRect(rect, 0, 0);
589 }
590 painter.restore();
591 }
592
593 void paintSliderText(QPainter &painter, const QString &text, const QRectF &rect, const QRectF &clipRect, const QColor &color, const QTextOption &textOption)
594 {
595 painter.setBrush(Qt::NoBrush);
596 painter.setPen(color);
597 painter.setClipping(true);
598 painter.setClipRect(clipRect);
599 painter.drawText(rect, text, textOption);
600 painter.setClipping(false);
601 };
602
603 void paintGenericSliderText(QPainter &painter, const QString &text, const QRectF &rect, const QRectF &sliderRect)
604 {
605 QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
606 textOption.setWrapMode(QTextOption::NoWrap);
607 // Draw portion of the text that is over the background
608 paintSliderText(painter, text, rect, rect.adjusted(sliderRect.width(), 0, 0, 0), m_lineEdit->palette().text().color(), textOption);
609 // Draw portion of the text that is over the progress bar
610 paintSliderText(painter, text, rect, sliderRect, m_lineEdit->palette().highlightedText().color(), textOption);
611 };
612
613 void paintSlider(QPainter &painter, const QString &text, double slider01Width, double slider02Width = -1.0)
614 {
615 const QRectF rect = m_lineEdit->rect();
616 const QColor highlightColor = m_q->palette().highlight().color();
617 if (slider02Width < 0.0) {
618 const QRectF sliderRect = rect.adjusted(0, 0, -(rect.width() - slider01Width), 0);
619 paintSliderRect(painter, sliderRect, highlightColor);
620 if (!text.isNull()) {
621 paintGenericSliderText(painter, text, rect, sliderRect);
622 }
623 } else {
624 static constexpr double heightOfCollapsedSliderPlusSpace = heightOfCollapsedSlider + heightOfSpaceBetweenSliders;
625 const double heightBetween = rect.height() - 2.0 * heightOfCollapsedSlider - heightOfSpaceBetweenSliders;
626 const double animationPos = m_sliderAnimation.currentValue().toReal();
627 const double a = heightOfCollapsedSliderPlusSpace;
628 const double b = heightOfCollapsedSliderPlusSpace + heightBetween;
629 // Paint background text
630 QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
631 textOption.setWrapMode(QTextOption::NoWrap);
632 paintSliderText(painter, text, rect, rect, m_lineEdit->palette().text().color(), textOption);
633 // Paint soft range slider
634 const QColor softSliderColor = KisPaintingTweaks::blendColors(highlightColor, m_q->palette().base().color(), 1.0 - 0.25);
635 const double softSliderAdjustment = -KisAlgebra2D::lerp(a, b, animationPos);
636 paintSliderRect(
637 painter,
638 rect.adjusted(0, 0, -(rect.width() - slider02Width), softSliderAdjustment),
639 softSliderColor
640 );
641 // Paint hard range slider
642 const double hardSliderAdjustment = KisAlgebra2D::lerp(a, b, 1.0 - animationPos);
643 paintSliderRect(
644 painter,
645 rect.adjusted(0, hardSliderAdjustment, -(rect.width() - slider01Width), 0),
646 highlightColor
647 );
648 // Paint text in the sliders
649 paintSliderText(
650 painter, text, rect,
651 rect.adjusted(0, 0, -(rect.width() - slider02Width), softSliderAdjustment),
652 m_lineEdit->palette().highlightedText().color(),
653 textOption
654 );
655 paintSliderText(
656 painter, text, rect,
657 rect.adjusted(0, hardSliderAdjustment, -(rect.width() - slider01Width), 0),
658 m_lineEdit->palette().highlightedText().color(),
659 textOption
660 );
661 }
662 }
663
664 double computeSliderWidth(double min, double max, double value) const
665 {
666 const double rangeSize = max - min;
667 const double localPosition = value - min;
668 const double normalizedValue = std::pow(localPosition / rangeSize, 1.0 / m_exponentRatio);
669 const double width = static_cast<double>(m_lineEdit->width());
670 return qBound(0.0, std::round(normalizedValue * width), width);
671 }
672
673 bool lineEditPaintEvent(QPaintEvent*)
674 {
675 QPainter painter(m_lineEdit);
676 painter.setRenderHint(QPainter::Antialiasing, true);
677
678 const double value = m_q->value();
679
680 // If we are not editing, just draw the text, otherwise draw a
681 // semi-transparent rect to dim the background and let the QLineEdit
682 // draw the rest (text, selection, cursor, etc.)
683 const double hardSliderWidth = computeSliderWidth(static_cast<double>(m_q->minimum()), static_cast<double>(m_q->maximum()), value);
684 const double softSliderWidth = computeSliderWidth(m_softMinimum, m_softMaximum, value);
685 if (!isEditModeActive()) {
686 QString text = m_q->text();
687 if (isSoftRangeValid()) {
688 if (m_softRangeViewMode == SoftRangeViewMode_AlwaysShowSoftRange) {
689 paintSlider(painter, text, softSliderWidth);
690 } else {
691 paintSlider(painter, text, hardSliderWidth, softSliderWidth);
692 }
693 } else {
694 // Draw the slider
695 paintSlider(painter, text, hardSliderWidth);
696 }
697 } else {
698 // Draw the slider
699 if (isSoftRangeValid()) {
700 if (m_softRangeViewMode == SoftRangeViewMode_AlwaysShowSoftRange) {
701 paintSlider(painter, QString(), softSliderWidth);
702 } else {
703 paintSlider(painter, QString(), hardSliderWidth, softSliderWidth);
704 }
705 } else {
706 paintSlider(painter, QString(), hardSliderWidth);
707 }
708 // Paint the overlay with the base color
709 QColor color = m_q->palette().base().color();
710 color.setAlpha(128);
711 paintSliderRect(painter, m_lineEdit->rect(), color);
712 }
713 // If we are editing the text then return false and let the QLineEdit
714 // paint all the edit related stuff (e.g. selection)
715 return !isEditModeActive();
716 }
717
718 bool lineEditMousePressEvent(QMouseEvent *e, bool edit)
719 {
720 if (!m_q->isEnabled()) {
721 return false;
722 }
723 if (!isEditModeActive()) {
724 if (e->button() == Qt::LeftButton) {
725 m_lastMousePressPosition = e->pos();
726 const QPoint currentValuePosition = pointForValue(m_q->value());
727 m_relativeDraggingOffset = currentValuePosition.x() - e->x();
728 m_useRelativeDragging = (e->modifiers() & Qt::ShiftModifier);
729 if (edit) {
730 QTimer::singleShot(0, &m_startEditingSignalProxy, SLOT(start()));
731 } else {
732 lineEditMouseMoveEvent(e);
733 }
734 }
735 return true;
736 }
737 return false;
738 }
739
740 bool lineEditMouseReleaseEvent(QMouseEvent *e)
741 {
742 if (!m_q->isEnabled()) {
743 return false;
744 }
745 if (!isEditModeActive()) {
746 // Releasing the right mouse button makes the lineedit enter
747 // the edition mode if we are not editing
748 if (e->button() == Qt::RightButton) {
749 // If we call startEditing() right from the eventFilter(),
750 // then the mouse release event will be somehow be passed
751 // to Qt further and generate ContextEvent on Windows.
752 // Therefore we should call it from a normal timer event.
753 QTimer::singleShot(0, &m_startEditingSignalProxy, SLOT(start()));
754 // Releasing the left mouse button stops the dragging. If signals
755 // must be blocked when dragging then we set the value here and
756 // Q_EMIT a signal
757 } else if (e->button() == Qt::LeftButton) {
758 if (m_blockUpdateSignalOnDrag) {
759 const QPoint p(m_useRelativeDragging ? e->pos().x() + m_relativeDraggingOffset : e->pos().x(),
760 e->pos().y());
761 setValue(valueForPoint(p, e->modifiers()), false, true);
762 } else {
763 if (!m_isDragging) {
764 setValue(valueForPoint(e->pos(), e->modifiers()), false, true);
765 }
766 }
767
768 m_isDragging = false;
769 Q_EMIT m_q->draggingFinished();
770 }
771 return true;
772 }
773 return false;
774 }
775
776 bool lineEditMouseMoveEvent(QMouseEvent *e)
777 {
778 if (!m_q->isEnabled()) {
779 return false;
780 }
781 if (!isEditModeActive()) {
782 if (e->buttons() & Qt::LeftButton) {
783 m_isDragging = true;
784 // At this point we are dragging so record the position and set
785 // the value
786 const QPoint p(m_useRelativeDragging ? e->pos().x() + m_relativeDraggingOffset : e->pos().x(),
787 e->pos().y());
788 setValue(valueForPoint(p, e->modifiers()), m_blockUpdateSignalOnDrag);
789 return true;
790 }
791 }
792 return false;
793 }
794
796 {
797 QPainter painter(m_widgetRangeToggle);
798 painter.setRenderHint(QPainter::Antialiasing, true);
799 // Compute sizes and positions
800 const double width = static_cast<double>(m_widgetRangeToggle->width());
801 const double height = static_cast<double>(m_widgetRangeToggle->height());
802 constexpr double marginX = 4.0;
803 const double toggleWidth = width - 2.0 * marginX;
804 const double centerX = width * 0.5;
805 const double centerY = height * 0.5;
806 const double bigRadius = centerX - std::floor(centerX - (toggleWidth * 0.5)) + 0.5;
807 const double smallRadius = bigRadius * 0.5;
808 const double sliderAnimationPos = m_sliderAnimation.currentValue().toReal();
809 const double radius = smallRadius + sliderAnimationPos * (bigRadius - smallRadius);
810 // Compute color
811 const double rangeToggleHoverAnimationPos = m_rangeToggleHoverAnimation.currentValue().toReal();
812 const QColor baseColor = m_q->palette().base().color();
813 const QColor textColor = m_q->palette().text().color();
814 const QColor color = KisPaintingTweaks::blendColors(baseColor, textColor, 1.0 - (0.60 + 0.40 * rangeToggleHoverAnimationPos));
815 // Paint outer circle
816 painter.setPen(color);
817 painter.setBrush(Qt::NoBrush);
818 painter.drawEllipse(QPointF(centerX, centerY), bigRadius, bigRadius);
819 // Paint dot
820 painter.setPen(Qt::NoPen);
821 painter.setBrush(color);
822 painter.drawEllipse(QPointF(centerX, centerY), radius, radius);
823 return true;
824 }
825
827 {
828 if (!m_q->isEnabled()) {
829 return false;
830 }
831 if (e->button() == Qt::LeftButton) {
832 if (!m_isSoftRangeActive) {
833 makeSoftRangeActive();
834 } else {
835 makeHardRangeActive();
836 }
837 return true;
838 }
839 return false;
840 }
841
843 {
844 m_rangeToggleHoverAnimation.stop();
845 // scale the animation duration in case the animation is in the middle
846 const int animationDuration =
847 static_cast<int>(std::round(m_rangeToggleHoverAnimation.currentValue().toReal() * fullAnimationDuration));
848 m_rangeToggleHoverAnimation.setStartValue(m_rangeToggleHoverAnimation.currentValue());
849 m_rangeToggleHoverAnimation.setEndValue(1.0);
850 m_rangeToggleHoverAnimation.setDuration(animationDuration);
851 m_rangeToggleHoverAnimation.start();
852 return false;
853 }
854
856 {
857 m_rangeToggleHoverAnimation.stop();
858 // scale the animation duration in case the animation is in the middle
859 const int animationDuration =
860 static_cast<int>(std::round(m_rangeToggleHoverAnimation.currentValue().toReal() * fullAnimationDuration));
861 m_rangeToggleHoverAnimation.setStartValue(m_rangeToggleHoverAnimation.currentValue());
862 m_rangeToggleHoverAnimation.setEndValue(0.0);
863 m_rangeToggleHoverAnimation.setDuration(animationDuration);
864 m_rangeToggleHoverAnimation.start();
865 return false;
866 }
867
868 bool eventFilter(QObject * o, QEvent * e) override
869 {
870 if (!o || !e) {
871 return false;
872 }
873 if (o == m_q) {
874 switch (e->type()) {
875 case QEvent::Resize : return qResizeEvent(static_cast<QResizeEvent*>(e));
876 case QEvent::FocusOut : return qFocusOutEvent(static_cast<QFocusEvent*>(e));
877 case QEvent::MouseButtonPress : return qMousePressEvent(static_cast<QMouseEvent*>(e));
878 case QEvent::KeyPress : return qKeyPressEvent(static_cast<QKeyEvent*>(e));
879 case QEvent::ContextMenu : return qContextMenuEvent(static_cast<QContextMenuEvent*>(e));
880 default: break;
881 }
882 } else if (o == m_lineEdit) {
883 switch (e->type()) {
884 case QEvent::Paint : return lineEditPaintEvent(static_cast<QPaintEvent*>(e));
885 case QEvent::MouseButtonPress : return lineEditMousePressEvent(static_cast<QMouseEvent*>(e), false);
886 case QEvent::MouseButtonDblClick : return lineEditMousePressEvent(static_cast<QMouseEvent*>(e), true);
887 case QEvent::MouseButtonRelease : return lineEditMouseReleaseEvent(static_cast<QMouseEvent*>(e));
888 case QEvent::MouseMove : return lineEditMouseMoveEvent(static_cast<QMouseEvent*>(e));
889 default: break;
890 }
891 } else if (o == m_widgetRangeToggle) {
892 switch (e->type()) {
893 case QEvent::Paint : return widgetRangeTogglePaintEvent(static_cast<QPaintEvent*>(e));
894 case QEvent::MouseButtonRelease: return widgetRangeToggleMouseReleaseEvent(static_cast<QMouseEvent*>(e));
895 case QEvent::Enter: return widgetRangeToggleEnterEvent(e);
896 case QEvent::Leave: return widgetRangeToggleLeaveEvent(e);
897 default: break;
898 }
899 }
900 return false;
901 }
902
903private:
904 // Margin around the spinbox for which the dragging gives same results,
905 // regardless of the vertical distance
906 static constexpr double constantDraggingMargin{32.0};
907 // Height of the collapsed slider bar
908 static constexpr double heightOfCollapsedSlider{3.0};
909 // Height of the space between the soft and hard range sliders
910 static constexpr double heightOfSpaceBetweenSliders{0.0};
911 // Width of the area to activate soft/hard range
912 static constexpr double widthOfRangeModeToggle{16.0};
913 // The duration of the animation
914 static constexpr double fullAnimationDuration{200.0};
915
916 SpinBoxType *m_q {nullptr};
917 QLineEdit *m_lineEdit {nullptr};
918 QWidget *m_widgetRangeToggle {nullptr};
919 ValueType m_softMinimum {static_cast<ValueType>(0)};
920 ValueType m_softMaximum {static_cast<ValueType>(0)};
921 double m_exponentRatio {1.0};
922 bool m_blockUpdateSignalOnDrag {false};
923 ValueType m_fastSliderStep {static_cast<ValueType>(5)};
924 mutable ValueType m_valueBeforeEditing {static_cast<ValueType>(0)};
925 bool m_isDragging {false};
926 bool m_useRelativeDragging {false};
927 int m_relativeDraggingOffset {0};
929 int m_rightClickCounter {0};
930 bool m_focusLostDueToMenu {false};
931 bool m_isSoftRangeActive {true};
932 QVariantAnimation m_sliderAnimation;
935
937 {
939 SoftRangeViewMode_ShowBothRanges
940 } m_softRangeViewMode{SoftRangeViewMode_ShowBothRanges};
941};
942
943#endif // KISSLIDERSPINBOXPRIVATE_H
float value(const T *src, size_t ch)
const Params2D p
qreal v
unsigned int uint
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static QCursor ibeamCursor()
Definition kis_cursor.cc:59
static QCursor splitHCursor()
Definition kis_cursor.cc:99
void setRange(int newMinimum, int newMaximum, bool computeNewFastSliderStep)
bool lineEditMousePressEvent(QMouseEvent *e, bool edit)
void paintSliderText(QPainter &painter, const QString &text, const QRectF &rect, const QRectF &clipRect, const QColor &color, const QTextOption &textOption)
QVariantAnimation m_sliderAnimation
QVariantAnimation m_rangeToggleHoverAnimation
bool lineEditMouseMoveEvent(QMouseEvent *e)
void paintGenericSliderText(QPainter &painter, const QString &text, const QRectF &rect, const QRectF &sliderRect)
bool qMousePressEvent(QMouseEvent *)
QPoint pointForValue(ValueType value) const
bool eventFilter(QObject *o, QEvent *e) override
bool widgetRangeTogglePaintEvent(QPaintEvent *)
decltype(std::declval< SpinBoxType >().value()) ValueType
void setValue(ValueType newValue, bool blockSignals=false, bool emitSignalsEvenWhenValueNotChanged=false, bool overwriteExpression=false)
bool qContextMenuEvent(QContextMenuEvent *e)
bool widgetRangeToggleMouseReleaseEvent(QMouseEvent *e)
bool widgetRangeToggleLeaveEvent(QEvent *)
BaseSpinBoxTypeTP BaseSpinBoxType
void setSoftRange(ValueType newSoftMinimum, ValueType newSoftMaximum)
ValueType valueForPoint(const QPoint &p, Qt::KeyboardModifiers modifiers) const
bool qFocusOutEvent(QFocusEvent *)
bool lineEditMouseReleaseEvent(QMouseEvent *e)
KisSliderSpinBoxPrivate(SpinBoxType *q)
void endEditing(ValueUpdateMode updateMode=ValueUpdateMode_UseLastValidValue)
bool lineEditPaintEvent(QPaintEvent *)
void setFastSliderStep(ValueType newFastSliderStep)
bool qResizeEvent(QResizeEvent *)
double computeSliderWidth(double min, double max, double value) const
void paintSlider(QPainter &painter, const QString &text, double slider01Width, double slider02Width=-1.0)
void setExponentRatio(double newExponentRatio)
void paintSliderRect(QPainter &painter, const QRectF &rect, const QBrush &brush)
bool qKeyPressEvent(QKeyEvent *e)
bool widgetRangeToggleEnterEvent(QEvent *)
void setRange(double newMinimum, double newMaximum, int newNumberOfDecimals, bool computeNewFastSliderStep)
SignalToFunctionProxy m_startEditingSignalProxy
void setBlockUpdateSignalOnDrag(bool newBlockUpdateSignalOnDrag)
constexpr const char * currentUnderlyingStyleNameProperty
Definition kis_global.h:116
Point lerp(const Point &pt1, const Point &pt2, qreal t)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)