File indexing completed on 2024-04-21 12:16:12

0001 /* This file is part of the KDE project
0002    Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
0003    Copyright (C) 2004 Alexander Dymo <cloudtemple@mskat.net>
0004    Copyright (C) 2008-2017 Jarosław Staniek <staniek@kde.org>
0005 
0006    This library is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU Library General Public
0008    License as published by the Free Software Foundation; either
0009    version 2 of the License, or (at your option) any later version.
0010 
0011    This library 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 GNU
0014    Library General Public License for more details.
0015 
0016    You should have received a copy of the GNU Library General Public License
0017    along with this library; see the file COPYING.LIB.  If not, write to
0018    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019  * Boston, MA 02110-1301, USA.
0020 */
0021 
0022 #include "spinbox.h"
0023 #include "KProperty.h"
0024 #include "KProperty_p.h"
0025 #include "KPropertyEditorView.h"
0026 #include "KPropertyUtils.h"
0027 #include "KPropertyUtils_p.h"
0028 #include "KPropertyWidgetsFactory.h"
0029 #include "kproperty_debug.h"
0030 
0031 #include <QVariant>
0032 #include <QLineEdit>
0033 #include <QLocale>
0034 
0035 //! @return font size expressed in points (pt)
0036 //! or if points are not available - in pixels (px) for @a font
0037 static QString fontSizeForCSS(const QFont& font)
0038 {
0039     return font.pointSize() > 0
0040         ? QString::fromLatin1("%1pt").arg(font.pointSize())
0041         : QString::fromLatin1("%1px").arg(font.pixelSize());
0042 }
0043 
0044 static QString cssForSpinBox(const char *_class, const QFont& font, int itemHeight)
0045 {
0046         return QString::fromLatin1(
0047             "%5 { border-left: 0; border-right: 0; font-size: %3; } "
0048             "%5::down-button { height: %1px; %4 } "
0049             "%5::up-button { height: %2px; } "
0050             "QLineEdit { border-width:0px;  } "
0051         )
0052         .arg(itemHeight/2 - 1).arg(itemHeight - itemHeight/2 - 1)
0053         .arg(fontSizeForCSS(font))
0054         .arg(QLatin1String((itemHeight/2 <= 9) ? "bottom: 2px;" : "bottom: 0px;"))
0055         .arg(QLatin1String(_class));
0056 }
0057 
0058 namespace {
0059 
0060 void intRangeValue(const KProperty &property, QVariant *min, QVariant *max)
0061 {
0062     Q_ASSERT(min);
0063     Q_ASSERT(max);
0064     *min = property.option("min");
0065     *max = property.option("max");
0066     if (!min->canConvert(QMetaType::Int) || min->toInt() < -INT_MAX) {
0067         min->clear();
0068     }
0069     if (!max->canConvert(QMetaType::Int) || max->toInt() > INT_MAX) {
0070         max->clear();
0071     }
0072     if (min->canConvert(QMetaType::Int) && max->canConvert(QMetaType::Int)
0073         && min->toInt() > max->toInt())
0074     {
0075         min->clear();
0076         max->clear();
0077     }
0078     if (min->isNull()) {
0079         switch (property.type()) {
0080         case KProperty::UInt:
0081             *min = 0;
0082             break;
0083         default:
0084             *min = -INT_MAX;
0085         }
0086     }
0087     if (max->isNull()) {
0088         *max = INT_MAX;
0089     }
0090 }
0091 
0092 //! Fixes @a value to fit in range @a min to @a max.
0093 //! Displays qWarning if @a warn is @c true.
0094 int fixIntValue(const QVariant &value, int min, int max, bool warn)
0095 {
0096     if (value.toInt() < min) {
0097         if (warn) {
0098             kprWarning() << "Could not assign value" << value.toInt() << "smaller than minimum" << min
0099                          << "-- setting to" << min;
0100         }
0101         return min;
0102     }
0103     if (value.toInt() > max) {
0104         if (warn) {
0105             kprWarning() << "Could not assign value" << value.toInt() << "larger than maximum" << max
0106                          << "-- setting to" << max;
0107         }
0108         return max;
0109     }
0110     return value.toInt();
0111 }
0112 
0113 } // namespace
0114 
0115 class Q_DECL_HIDDEN KPropertyIntSpinBox::Private
0116 {
0117 public:
0118     explicit Private(const KProperty& prop) : property(&prop)
0119     {
0120     }
0121 
0122     const KProperty * const property;
0123 };
0124 
0125 KPropertyIntSpinBox::KPropertyIntSpinBox(const KProperty& prop, QWidget *parent, int itemHeight)
0126         : QSpinBox(parent)
0127         , d(new Private(prop))
0128 {
0129     QLineEdit* le = findChild<QLineEdit*>();
0130     setContentsMargins(0,0,0,0);
0131     if (le) {
0132         le->setAlignment(Qt::AlignLeft);
0133         le->setContentsMargins(0,0,0,0);
0134     }
0135     setFrame(true);
0136     QString css = cssForSpinBox("QSpinBox", font(), itemHeight);
0137     KPropertyWidgetsFactory::setTopAndBottomBordersUsingStyleSheet(this, css);
0138     setStyleSheet(css);
0139 
0140     QVariant minVal;
0141     QVariant maxVal;
0142     intRangeValue(prop, &minVal, &maxVal);
0143     setRange(minVal.toInt(), maxVal.toInt());
0144     const KPropertyUtilsPrivate::ValueOptionsHandler options(prop);
0145     if (!options.minValueText.isNull()) {
0146         setSpecialValueText(options.minValueText.toString());
0147     }
0148     if (!options.prefix.isEmpty()) {
0149         setPrefix(options.prefix + QLatin1Char(' '));
0150     }
0151     if (!options.suffix.isEmpty()) {
0152         setSuffix(QLatin1Char(' ') + options.suffix);
0153     }
0154     connect(this, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged(int)));
0155 }
0156 
0157 KPropertyIntSpinBox::~KPropertyIntSpinBox()
0158 {
0159     delete d;
0160 }
0161 
0162 QVariant KPropertyIntSpinBox::value() const
0163 {
0164     if (d->property->type() == KProperty::UInt) {
0165         return uint(QSpinBox::value());
0166     }
0167     return QSpinBox::value();
0168 }
0169 
0170 void KPropertyIntSpinBox::setValue(const QVariant& value)
0171 {
0172     QVariant minVal;
0173     QVariant maxVal;
0174     intRangeValue(*d->property, &minVal, &maxVal);
0175     QSpinBox::setValue(fixIntValue(value, minVal.toInt(), maxVal.toInt(), true));
0176 }
0177 
0178 void KPropertyIntSpinBox::slotValueChanged(int value)
0179 {
0180     Q_UNUSED(value);
0181     emit commitData(this);
0182 }
0183 
0184 //-----------------------
0185 
0186 class Q_DECL_HIDDEN KPropertyDoubleSpinBox::Private
0187 {
0188 public:
0189     explicit Private(const KProperty& prop) : property(&prop)
0190     {
0191     }
0192 
0193     const KProperty * const property;
0194 };
0195 
0196 namespace {
0197 
0198 void doubleRangeValue(const KProperty &property, QVariant *min, QVariant *max)
0199 {
0200     Q_ASSERT(min);
0201     Q_ASSERT(max);
0202     *min = property.option("min");
0203     *max = property.option("max");
0204     if (!min->canConvert(QMetaType::Double) || min->toDouble() < KPROPERTY_MIN_PRECISE_DOUBLE) {
0205         min->clear();
0206     }
0207     if (!max->canConvert(QMetaType::Double) || max->toDouble() > KPROPERTY_MAX_PRECISE_DOUBLE) {
0208         max->clear();
0209     }
0210     if (min->canConvert(QMetaType::Double) && max->canConvert(QMetaType::Double)
0211         && min->toDouble() > max->toDouble())
0212     {
0213         min->clear();
0214         max->clear();
0215     }
0216     if (min->isNull()) {
0217         *min = 0.0;
0218     }
0219     if (max->isNull()) {
0220         *max = KPROPERTY_MAX_PRECISE_DOUBLE;
0221     }
0222 }
0223 
0224 //! Fixes @a value to fit in range @a min to @a max.
0225 //! Displays qWarning if @a warn is @c true.
0226 double fixDoubleValue(const QVariant &value, double min, double max, bool warn)
0227 {
0228     if (value.toDouble() < min) {
0229         if (warn) {
0230             kprWarning() << "Could not assign value" << value.toDouble() << "smaller than minimum" << min
0231                          << "-- setting to" << min;
0232         }
0233         return min;
0234     }
0235     if (value.toDouble() > max) {
0236         if (warn) {
0237             kprWarning() << "Could not assign value" << value.toDouble() << "larger than maximum" << max
0238                          << "-- setting to" << max;
0239         }
0240         return max;
0241     }
0242     return value.toDouble();
0243 }
0244 
0245 QVariant precisionValue(const KProperty &property)
0246 {
0247     QVariant result = property.option("precision", KPROPERTY_DEFAULT_DOUBLE_VALUE_PRECISION);
0248     if (result.canConvert(QMetaType::Int) && result.toInt() >= 0) {
0249         return result;
0250     }
0251     return QVariant();
0252 }
0253 
0254 } // namespace
0255 
0256 KPropertyDoubleSpinBox::KPropertyDoubleSpinBox(const KProperty &prop, QWidget *parent, int itemHeight)
0257         : QDoubleSpinBox(parent)
0258         , d(new Private(prop))
0259 {
0260     setFrame(false);
0261     QLineEdit* le = findChild<QLineEdit*>();
0262     if (le) {
0263         le->setAlignment(Qt::AlignLeft);
0264         le->setContentsMargins(0,0,0,0);
0265         le->setFrame(false);
0266     }
0267 /*    KPropertyFactory::setTopAndBottomBordersUsingStyleSheet(sb,
0268         QString::fromLatin1(
0269             "QDoubleSpinBox { border-left: 0; border-right: 0; } "
0270             "QDoubleSpinBox::down-button { height: %1px; } "
0271             "QDoubleSpinBox::up-button { height: %2px; }"
0272         ).arg(itemHeight/2).arg(itemHeight - itemHeight/2)
0273     );*/
0274     QString css = cssForSpinBox("QDoubleSpinBox", font(), itemHeight);
0275     KPropertyWidgetsFactory::setTopAndBottomBordersUsingStyleSheet(this, css);
0276     setStyleSheet(css);
0277 
0278     QVariant minVal;
0279     QVariant maxVal;
0280     doubleRangeValue(prop, &minVal, &maxVal);
0281     setRange(minVal.toDouble(), maxVal.toDouble());
0282     QVariant step = prop.option("step", KPROPERTY_DEFAULT_DOUBLE_VALUE_STEP);
0283     if (step.canConvert(QMetaType::Double) && step.toDouble() > 0.0) {
0284         setSingleStep(step.toDouble());
0285     }
0286     const QVariant precision = precisionValue(prop);
0287     if (precision.isValid()) {
0288         setDecimals(precision.toInt());
0289     }
0290     //! @todo implement slider
0291     // bool slider = prop->option("slider", false).toBool();
0292     const KPropertyUtilsPrivate::ValueOptionsHandler options(prop);
0293     if (!options.minValueText.isNull()) {
0294         setSpecialValueText(options.minValueText.toString());
0295     }
0296     if (!options.prefix.isEmpty()) {
0297         setPrefix(options.prefix + QLatin1Char(' '));
0298     }
0299     if (!options.suffix.isEmpty()) {
0300         setSuffix(QLatin1Char(' ') + options.suffix);
0301     }
0302     connect(this, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged(double)));
0303 }
0304 
0305 KPropertyDoubleSpinBox::~KPropertyDoubleSpinBox()
0306 {
0307     delete d;
0308 }
0309 
0310 void KPropertyDoubleSpinBox::resizeEvent( QResizeEvent * event )
0311 {
0312     QDoubleSpinBox::resizeEvent(event);
0313 }
0314 
0315 void KPropertyDoubleSpinBox::setValue(const QVariant& value)
0316 {
0317     QVariant minVal;
0318     QVariant maxVal;
0319     doubleRangeValue(*d->property, &minVal, &maxVal);
0320     QDoubleSpinBox::setValue(fixDoubleValue(value, minVal.toDouble(), maxVal.toDouble(), true));
0321 }
0322 
0323 void KPropertyDoubleSpinBox::slotValueChanged(double value)
0324 {
0325     Q_UNUSED(value);
0326     emit commitData(this);
0327 }
0328 
0329 //-----------------------
0330 
0331 KPropertyIntSpinBoxDelegate::KPropertyIntSpinBoxDelegate()
0332 {
0333 }
0334 
0335 QString KPropertyIntSpinBoxDelegate::propertyValueToString(const KProperty* prop,
0336                                                            const QLocale &locale) const
0337 {
0338     //replace min value with minValueText if defined
0339     const KPropertyUtilsPrivate::ValueOptionsHandler options(*prop);
0340     QVariant minVal;
0341     QVariant maxVal;
0342     intRangeValue(*prop, &minVal, &maxVal);
0343     const int fixedValue = fixIntValue(prop->value(), minVal.toInt(), maxVal.toInt(), false);
0344     if (minVal.isValid() && minVal.toInt() == fixedValue && !options.minValueText.isNull())
0345     {
0346         return options.minValueText.toString();
0347     }
0348     return options.valueWithPrefixAndSuffix(valueToString(fixedValue, locale), locale);
0349 }
0350 
0351 QString KPropertyIntSpinBoxDelegate::valueToString(const QVariant& value, const QLocale &locale) const
0352 {
0353     return locale.toString(value.toReal(), 'f', 0);
0354 }
0355 
0356 QWidget* KPropertyIntSpinBoxDelegate::createEditor( int type, QWidget *parent,
0357     const QStyleOptionViewItem & option, const QModelIndex & index ) const
0358 {
0359     Q_UNUSED(type);
0360 
0361     KProperty *prop = KPropertyUtils::propertyForIndex(index);
0362     if (!prop) {
0363         return nullptr;
0364     }
0365     return new KPropertyIntSpinBox(*prop, parent, option.rect.height() - 2);
0366 }
0367 
0368 //-----------------------
0369 
0370 KPropertyDoubleSpinBoxDelegate::KPropertyDoubleSpinBoxDelegate()
0371 {
0372 }
0373 
0374 QString KPropertyDoubleSpinBoxDelegate::propertyValueToString(const KProperty* prop,
0375                                                               const QLocale &locale) const
0376 {
0377     //replace min value with minValueText if defined
0378     QVariant minVal;
0379     QVariant maxVal;
0380     const KPropertyUtilsPrivate::ValueOptionsHandler options(*prop);
0381     doubleRangeValue(*prop, &minVal, &maxVal);
0382     const double fixedValue = fixDoubleValue(prop->value(), minVal.toDouble(), maxVal.toDouble(), false);
0383     if (minVal.isValid() && minVal.toDouble() == fixedValue && !options.minValueText.isNull())
0384     {
0385         return options.minValueText.toString();
0386     }
0387     QString valueString;
0388     const QVariant precision = precisionValue(*prop);
0389     if (precision.isValid()) {
0390         valueString = locale.toString(fixedValue, 'f', precision.toInt());
0391     } else {
0392         valueString = valueToString(fixedValue, locale);
0393     }
0394     return options.valueWithPrefixAndSuffix(valueString, locale);
0395 }
0396 
0397 QString KPropertyDoubleSpinBoxDelegate::valueToString(const QVariant& value, const QLocale &locale) const
0398 {
0399     return locale.toString(value.toReal());
0400 }
0401 
0402 QWidget* KPropertyDoubleSpinBoxDelegate::createEditor( int type, QWidget *parent,
0403     const QStyleOptionViewItem & option, const QModelIndex & index ) const
0404 {
0405     Q_UNUSED(type);
0406 
0407     KProperty *prop = KPropertyUtils::propertyForIndex(index);
0408     if (!prop) {
0409         return nullptr;
0410     }
0411     return new KPropertyDoubleSpinBox(*prop, parent, option.rect.height() - 2 - 1);
0412 }