6#include "multispinbox.h"
8#include "multispinbox_p.h"
10#include "constpropagatingrawpointer.h"
11#include "constpropagatinguniquepointer.h"
12#include "extendeddoublevalidator.h"
13#include "helpermath.h"
14#include "multispinboxsection.h"
16#include <qaccessible.h>
17#include <qaccessiblewidget.h>
18#include <qcoreevent.h>
21#include <qfontmetrics.h>
25#include <qnamespace.h>
28#include <qstringbuilder.h>
29#include <qstringliteral.h>
31#include <qstyleoption.h>
35#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
36#include <qobjectdefs.h>
52bool MultiSpinBoxPrivate::isCursorTouchingCurrentSectionValue()
const
54 const auto cursorPosition = q_pointer->lineEdit()->cursorPosition();
55 const bool highEnough = (cursorPosition >= m_textBeforeCurrentValue.
length());
56 const auto after = q_pointer->lineEdit()->text().length()
57 - m_textAfterCurrentValue.
length();
58 const bool lowEnough = (cursorPosition <= after);
59 return (highEnough && lowEnough);
88 , d_pointer(new MultiSpinBoxPrivate(this))
91 d_pointer->m_validator =
new ExtendedDoubleValidator(
this);
92 d_pointer->m_validator->setLocale(
locale());
99 d_pointer->m_currentIndex = -1;
103 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(0);
109 &MultiSpinBoxPrivate::updateCurrentValueFromText
114 &MultiSpinBoxPrivate::reactOnCursorPositionChange
119 &MultiSpinBoxPrivate::setCurrentIndexToZeroAndUpdateTextAndSelectValue
127 &AccessibleMultiSpinBox::factory);
139MultiSpinBoxPrivate::MultiSpinBoxPrivate(
MultiSpinBox *backLink)
140 : q_pointer(backLink)
174 for (
int i = 0; i < myConfiguration.
count(); ++i) {
176 completeString += myConfiguration.
at(i).prefix();
181 myConfiguration.
at(i).minimum(),
183 myConfiguration.
at(i).decimals()
186 myConfiguration.
at(i).maximum(),
188 myConfiguration.
at(i).decimals()
191 completeString += textOfMinimumValue;
193 completeString += textOfMaximumValue;
196 completeString += myConfiguration.
at(i).suffix();
200 completeString += QStringLiteral(u
" ");
218 &myStyleOptionsForSpinBoxes,
223 if (d_pointer->m_actionButtonCount > 0) {
231 const int actionButtonMargin = actionButtonIconSize / 4;
232 const int actionButtonWidth = actionButtonIconSize + 6;
234 const int actionButtonSpace = actionButtonWidth + actionButtonMargin;
235 result.
setWidth(result.
width() + d_pointer->m_actionButtonCount * actionButtonSpace);
287 d_pointer->m_actionButtonCount += 1;
297QString MultiSpinBoxPrivate::formattedValue(QListSizeType index)
const
299 return q_pointer->locale().toString(
301 q_pointer->sectionValues().at(index),
305 m_sectionConfigurations.at(index).decimals());
315void MultiSpinBoxPrivate::updatePrefixValueSuffixText()
320 m_textBeforeCurrentValue =
QString();
321 for (i = 0; i < m_currentIndex; ++i) {
322 m_textBeforeCurrentValue.
append(m_sectionConfigurations.at(i).prefix());
323 m_textBeforeCurrentValue.
append(formattedValue(i));
324 m_textBeforeCurrentValue.
append(m_sectionConfigurations.at(i).suffix());
326 m_textBeforeCurrentValue.
append(m_sectionConfigurations.at(m_currentIndex).prefix());
329 m_textOfCurrentValue = formattedValue(m_currentIndex);
332 m_textAfterCurrentValue =
QString();
333 m_textAfterCurrentValue.
append(m_sectionConfigurations.at(m_currentIndex).suffix());
334 for (i = m_currentIndex + 1; i < m_sectionConfigurations.count(); ++i) {
335 m_textAfterCurrentValue.
append(m_sectionConfigurations.at(i).prefix());
337 m_textAfterCurrentValue.
append(formattedValue(i));
338 m_textAfterCurrentValue.
append(m_sectionConfigurations.at(i).suffix());
347void MultiSpinBoxPrivate::setCurrentIndexToZeroAndUpdateTextAndSelectValue()
349 setCurrentIndexAndUpdateTextAndSelectValue(0);
363void MultiSpinBoxPrivate::setCurrentIndexAndUpdateTextAndSelectValue(QListSizeType newIndex)
366 setCurrentIndexWithoutUpdatingText(newIndex);
368 q_pointer->lineEdit()->setText(m_textBeforeCurrentValue
369 + m_textOfCurrentValue
370 + m_textAfterCurrentValue);
371 const int lengthOfTextBeforeCurrentValue =
372 static_cast<int>(m_textBeforeCurrentValue.
length());
373 const int lengthOfTextOfCurrentValue =
374 static_cast<int>(m_textOfCurrentValue.
length());
375 if (q_pointer->hasFocus()) {
376 q_pointer->lineEdit()->setSelection(
377 lengthOfTextBeforeCurrentValue,
378 lengthOfTextOfCurrentValue);
380 q_pointer->lineEdit()->setCursorPosition(
381 lengthOfTextBeforeCurrentValue + lengthOfTextOfCurrentValue);
395void MultiSpinBoxPrivate::setCurrentIndexWithoutUpdatingText(QListSizeType newIndex)
397 if (!isInRange<qsizetype>(0, newIndex, m_sectionConfigurations.count() - 1)) {
398 qWarning() <<
"The function" << __func__
399 <<
"in file" << __FILE__
400 <<
"near to line" << __LINE__
401 <<
"was called with an invalid “newIndex“ argument of" << newIndex
402 <<
"thought the valid range is currently [" << 0 <<
", " << m_sectionConfigurations.count() - 1 <<
"]. This is a bug.";
406 if (newIndex == m_currentIndex) {
412 m_currentIndex = newIndex;
413 updatePrefixValueSuffixText();
414 m_validator->setPrefix(m_textBeforeCurrentValue);
415 m_validator->setSuffix(m_textAfterCurrentValue);
416 m_validator->setRange(
418 m_sectionConfigurations.at(m_currentIndex).minimum(),
420 m_sectionConfigurations.at(m_currentIndex).maximum());
435 const MultiSpinBoxSection currentSectionConfiguration = d_pointer->m_sectionConfigurations.at(d_pointer->m_currentIndex);
436 const double currentSectionValue =
sectionValues().
at(d_pointer->m_currentIndex);
439 if (currentSectionConfiguration.
isWrapping()) {
447 if (currentSectionValue < currentSectionConfiguration.
maximum()) {
452 if (currentSectionValue > currentSectionConfiguration.
minimum()) {
471 if (newSectionConfigurations.
count() < 1) {
476 d_pointer->m_currentIndex = qBound(0, d_pointer->m_currentIndex, newSectionConfigurations.
count());
479 d_pointer->m_sectionConfigurations = newSectionConfigurations;
487 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(d_pointer->m_currentIndex);
504 return d_pointer->m_sectionConfigurations;
511 return d_pointer->m_sectionValues;
528void MultiSpinBoxPrivate::setSectionValuesWithoutFurtherUpdating(
const QList<double> &newSectionValues)
530 if (newSectionValues.
count() < 1) {
534 const QListSizeType sectionCount = m_sectionConfigurations.count();
539 while (fixedNewSectionValues.
count() < sectionCount) {
541 fixedNewSectionValues.
append(MultiSpinBoxPrivate::defaultSectionValue);
543 while (fixedNewSectionValues.
count() > sectionCount) {
550 MultiSpinBoxSection myConfig;
553 for (
int i = 0; i < sectionCount; ++i) {
554 myConfig = m_sectionConfigurations.at(i);
555 fixedNewSectionValues[i] =
557 roundToDigits(fixedNewSectionValues.
at(i), myConfig.decimals());
558 if (myConfig.isWrapping()) {
559 rangeWidth = myConfig.maximum() - myConfig.minimum();
560 if (rangeWidth <= 0) {
564 fixedNewSectionValues[i] = myConfig.minimum();
569 fixedNewSectionValues.
at(i) - myConfig.minimum(),
577 temp += myConfig.minimum();
578 fixedNewSectionValues[i] = temp;
581 fixedNewSectionValues[i] = qBound(
584 fixedNewSectionValues.
at(i),
589 if (m_sectionValues != fixedNewSectionValues) {
590 m_sectionValues = fixedNewSectionValues;
591 Q_EMIT q_pointer->sectionValuesChanged(fixedNewSectionValues);
607 d_pointer->setSectionValuesWithoutFurtherUpdating(newSectionValues);
610 d_pointer->updatePrefixValueSuffixText();
616 + d_pointer->m_textOfCurrentValue
617 + d_pointer->m_textAfterCurrentValue);
647 if (d_pointer->m_currentIndex < (d_pointer->m_sectionConfigurations.count() - 1)) {
648 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(d_pointer->m_currentIndex + 1);
655 if (d_pointer->m_currentIndex > 0) {
656 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(d_pointer->m_currentIndex - 1);
682 switch (
event->reason()) {
687 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(0);
714 switch (
event->reason()) {
717 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(0);
723 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(d_pointer->m_sectionConfigurations.count() - 1);
755 const QListSizeType currentIndex = d_pointer->m_currentIndex;
757 myValues[currentIndex] += steps * d_pointer->m_sectionConfigurations.at(currentIndex).singleStep();
767 d_pointer->setCurrentIndexAndUpdateTextAndSelectValue(currentIndex);
782void MultiSpinBoxPrivate::updateCurrentValueFromText(
const QString &lineEditText)
788 QString cleanText = lineEditText;
789 if (cleanText.
startsWith(m_textBeforeCurrentValue)) {
790 cleanText.
remove(0, m_textBeforeCurrentValue.
count());
794 qWarning() <<
"The function" << __func__
795 <<
"in file" << __FILE__
796 <<
"near to line" << __LINE__
797 <<
"was called with the invalid “lineEditText“ argument “" << lineEditText
798 <<
"” that does not start with the expected character sequence “" << m_textBeforeCurrentValue <<
". "
799 <<
"The call is ignored. This is a bug.";
802 if (cleanText.
endsWith(m_textAfterCurrentValue)) {
803 cleanText.
chop(m_textAfterCurrentValue.
count());
807 qWarning() <<
"The function" << __func__
808 <<
"in file" << __FILE__
809 <<
"near to line" << __LINE__
810 <<
"was called with the invalid “lineEditText“ argument “" << lineEditText
811 <<
"” that does not end with the expected character sequence “" << m_textAfterCurrentValue <<
". "
812 <<
"The call is ignored. This is a bug.";
819 myValues[m_currentIndex] = q_pointer->locale().toDouble(cleanText, &ok);
820 setSectionValuesWithoutFurtherUpdating(myValues);
840 if (
event->type() == QEvent::Type::LocaleChange) {
841 d_pointer->updatePrefixValueSuffixText();
842 d_pointer->m_validator->setPrefix(d_pointer->m_textBeforeCurrentValue);
843 d_pointer->m_validator->setSuffix(d_pointer->m_textAfterCurrentValue);
844 d_pointer->m_validator->setRange(
846 d_pointer->m_sectionConfigurations.at(d_pointer->m_currentIndex).minimum(),
848 d_pointer->m_sectionConfigurations.at(d_pointer->m_currentIndex).maximum());
849 lineEdit()->
setText(d_pointer->m_textBeforeCurrentValue + d_pointer->m_textOfCurrentValue + d_pointer->m_textAfterCurrentValue);
862void MultiSpinBoxPrivate::reactOnCursorPositionChange(
const int oldPos,
const int newPos)
873 if (isCursorTouchingCurrentSectionValue()) {
885 const QListSizeType oldTextLength = q_pointer->lineEdit()->text().length();
886 const bool mustAdjustCursorPosition =
887 (newPos > (oldTextLength - m_textAfterCurrentValue.
length()));
890 int sectionOfTheNewCursorPosition;
891 QStringLength reference = 0;
892 for (sectionOfTheNewCursorPosition = 0;
893 sectionOfTheNewCursorPosition < m_sectionConfigurations.count() - 1;
894 ++sectionOfTheNewCursorPosition
896 reference += m_sectionConfigurations
897 .at(sectionOfTheNewCursorPosition)
900 reference += formattedValue(sectionOfTheNewCursorPosition).
length();
901 reference += m_sectionConfigurations
902 .at(sectionOfTheNewCursorPosition)
905 if (newPos <= reference) {
910 updatePrefixValueSuffixText();
911 setCurrentIndexWithoutUpdatingText(sectionOfTheNewCursorPosition);
912 q_pointer->lineEdit()->setText(m_textBeforeCurrentValue
913 + m_textOfCurrentValue
914 + m_textAfterCurrentValue);
915 int correctedCursorPosition = newPos;
916 if (mustAdjustCursorPosition) {
917 correctedCursorPosition =
918 static_cast<int>(newPos
919 + q_pointer->lineEdit()->text().length()
922 q_pointer->lineEdit()->setCursorPosition(correctedCursorPosition);
931AccessibleMultiSpinBox::AccessibleMultiSpinBox(MultiSpinBox *w)
937AccessibleMultiSpinBox::~AccessibleMultiSpinBox()
960 MultiSpinBox::staticMetaObject.className());
961 MultiSpinBox *myMultiSpinBox = qobject_cast<MultiSpinBox *>(
object);
962 if ((classname == multiSpinBoxClassName) && myMultiSpinBox) {
963 interface = new AccessibleMultiSpinBox(myMultiSpinBox);
The configuration of a single section within a MultiSpinBox.
double maximum() const
The maximum possible value of the section.
double minimum() const
The minimum possible value of the section.
bool isWrapping() const
Holds whether or not MultiSpinBox::sectionValues wrap around when they reaches minimum or maximum.
A spin box that can hold multiple sections (each with its own value) at the same time.
Q_INVOKABLE MultiSpinBox(QWidget *parent=nullptr)
Constructor.
virtual bool event(QEvent *event) override
The main event handler.
virtual QSize minimumSizeHint() const override
The recommended minimum size for the widget.
void addActionButton(QAction *action, QLineEdit::ActionPosition position)
Adds to the widget a button associated with the given action.
virtual bool focusNextPrevChild(bool next) override
Focus handling for Tab respectively Shift+Tab.
virtual void focusInEvent(QFocusEvent *event) override
Handles a QEvent::FocusIn.
virtual QAbstractSpinBox::StepEnabled stepEnabled() const override
Virtual function that determines whether stepping up and down is legal at any given time.
void setSectionValues(const QList< double > &newSectionValues)
Setter for sectionValues property.
Q_INVOKABLE QList< PerceptualColor::MultiSpinBoxSection > sectionConfigurations() const
Returns the configuration of all sections.
virtual QSize sizeHint() const override
The recommended size for the widget.
virtual void clear() override
Current implementation does nothing.
virtual ~MultiSpinBox() noexcept override
Default destructor.
virtual void focusOutEvent(QFocusEvent *event) override
Handles a QEvent::FocusOut.
Q_INVOKABLE void setSectionConfigurations(const QList< PerceptualColor::MultiSpinBoxSection > &newSectionConfigurations)
Sets the configuration for the sections.
virtual void stepBy(int steps) override
Increase or decrease the current section’s value.
virtual void changeEvent(QEvent *event) override
Handle state changes.
QList< double > sectionValues
A list containing the values of all sections.
The namespace of this library.
virtual void changeEvent(QEvent *event) override
virtual bool event(QEvent *event) override
virtual void focusInEvent(QFocusEvent *event) override
virtual void focusOutEvent(QFocusEvent *event) override
virtual void initStyleOption(QStyleOptionSpinBox *option) const const
QLineEdit * lineEdit() const const
void installFactory(InterfaceFactory factory)
int horizontalAdvance(QChar ch) const const
QAction * addAction(const QIcon &icon, ActionPosition position)
void cursorPositionChanged(int oldPos, int newPos)
void setValidator(const QValidator *v)
virtual QSize sizeHint() const const override
void setText(const QString &)
void textChanged(const QString &text)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
qsizetype count() const const
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const const=0