diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 (patch) | |
tree | 5ac38a06f3dde268dc7927dc155896926aaf7012 /kdeui/knuminput.cpp | |
download | tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.tar.gz tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kdeui/knuminput.cpp')
-rw-r--r-- | kdeui/knuminput.cpp | 1180 |
1 files changed, 1180 insertions, 0 deletions
diff --git a/kdeui/knuminput.cpp b/kdeui/knuminput.cpp new file mode 100644 index 000000000..68587dcb4 --- /dev/null +++ b/kdeui/knuminput.cpp @@ -0,0 +1,1180 @@ +// -*- c-basic-offset: 4 -*- +/* + * knuminput.cpp + * + * Initial implementation: + * Copyright (c) 1997 Patrick Dowler <dowler@morgul.fsh.uvic.ca> + * Rewritten and maintained by: + * Copyright (c) 2000 Dirk A. Mueller <mueller@kde.org> + * KDoubleSpinBox: + * Copyright (c) 2002 Marc Mutz <mutz@kde.org> + * + * Requires the Qt widget libraries, available at no cost at + * http://www.troll.no/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <config.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <assert.h> +#include <math.h> +#include <algorithm> + +#include <qapplication.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qsize.h> +#include <qslider.h> +#include <qspinbox.h> +#include <qstyle.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> + +#include "kdialog.h" +#include "knumvalidator.h" +#include "knuminput.h" + +static inline int calcDiffByTen( int x, int y ) { + // calculate ( x - y ) / 10 without overflowing ints: + return ( x / 10 ) - ( y / 10 ) + ( x % 10 - y % 10 ) / 10; +} + +// ---------------------------------------------------------------------------- + +KNumInput::KNumInput(QWidget* parent, const char* name) + : QWidget(parent, name) +{ + init(); +} + +KNumInput::KNumInput(KNumInput* below, QWidget* parent, const char* name) + : QWidget(parent, name) +{ + init(); + + if(below) { + m_next = below->m_next; + m_prev = below; + below->m_next = this; + if(m_next) + m_next->m_prev = this; + } +} + +void KNumInput::init() +{ + m_prev = m_next = 0; + m_colw1 = m_colw2 = 0; + + m_label = 0; + m_slider = 0; + m_alignment = 0; +} + +KNumInput::~KNumInput() +{ + if(m_prev) + m_prev->m_next = m_next; + + if(m_next) + m_next->m_prev = m_prev; +} + +void KNumInput::setLabel(const QString & label, int a) +{ + if(label.isEmpty()) { + delete m_label; + m_label = 0; + m_alignment = 0; + } + else { + if (m_label) m_label->setText(label); + else m_label = new QLabel(label, this, "KNumInput::QLabel"); + m_label->setAlignment((a & (~(AlignTop|AlignBottom|AlignVCenter))) + | AlignVCenter); + // if no vertical alignment set, use Top alignment + if(!(a & (AlignTop|AlignBottom|AlignVCenter))) + a |= AlignTop; + m_alignment = a; + } + + layout(true); +} + +QString KNumInput::label() const +{ + if (m_label) return m_label->text(); + return QString::null; +} + +void KNumInput::layout(bool deep) +{ + int w1 = m_colw1; + int w2 = m_colw2; + + // label sizeHint + m_sizeLabel = (m_label ? m_label->sizeHint() : QSize(0,0)); + + if(m_label && (m_alignment & AlignVCenter)) + m_colw1 = m_sizeLabel.width() + 4; + else + m_colw1 = 0; + + // slider sizeHint + m_sizeSlider = (m_slider ? m_slider->sizeHint() : QSize(0, 0)); + + doLayout(); + + if(!deep) { + m_colw1 = w1; + m_colw2 = w2; + return; + } + + KNumInput* p = this; + while(p) { + p->doLayout(); + w1 = QMAX(w1, p->m_colw1); + w2 = QMAX(w2, p->m_colw2); + p = p->m_prev; + } + + p = m_next; + while(p) { + p->doLayout(); + w1 = QMAX(w1, p->m_colw1); + w2 = QMAX(w2, p->m_colw2); + p = p->m_next; + } + + p = this; + while(p) { + p->m_colw1 = w1; + p->m_colw2 = w2; + p = p->m_prev; + } + + p = m_next; + while(p) { + p->m_colw1 = w1; + p->m_colw2 = w2; + p = p->m_next; + } + +// kdDebug() << "w1 " << w1 << " w2 " << w2 << endl; +} + +QSizePolicy KNumInput::sizePolicy() const +{ + return QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed ); +} + +QSize KNumInput::sizeHint() const +{ + return minimumSizeHint(); +} + +void KNumInput::setSteps(int minor, int major) +{ + if(m_slider) + m_slider->setSteps( minor, major ); +} + + +// ---------------------------------------------------------------------------- + +KIntSpinBox::KIntSpinBox(QWidget *parent, const char *name) + : QSpinBox(0, 99, 1, parent, name) +{ + editor()->setAlignment(AlignRight); + val_base = 10; + setValue(0); +} + +KIntSpinBox::~KIntSpinBox() +{ +} + +KIntSpinBox::KIntSpinBox(int lower, int upper, int step, int value, int base, + QWidget* parent, const char* name) + : QSpinBox(lower, upper, step, parent, name) +{ + editor()->setAlignment(AlignRight); + val_base = base; + setValue(value); +} + +void KIntSpinBox::setBase(int base) +{ + val_base = base; +} + + +int KIntSpinBox::base() const +{ + return val_base; +} + +QString KIntSpinBox::mapValueToText(int v) +{ + return QString::number(v, val_base); +} + +int KIntSpinBox::mapTextToValue(bool* ok) +{ + return cleanText().toInt(ok, val_base); +} + +void KIntSpinBox::setEditFocus(bool mark) +{ + editor()->setFocus(); + if(mark) + editor()->selectAll(); +} + + +// ---------------------------------------------------------------------------- + +class KIntNumInput::KIntNumInputPrivate { +public: + int referencePoint; + short blockRelative; + KIntNumInputPrivate( int r ) + : referencePoint( r ), + blockRelative( 0 ) {} +}; + + +KIntNumInput::KIntNumInput(KNumInput* below, int val, QWidget* parent, + int _base, const char* name) + : KNumInput(below, parent, name) +{ + init(val, _base); +} + +KIntNumInput::KIntNumInput(QWidget *parent, const char *name) + : KNumInput(parent, name) +{ + init(0, 10); +} + +KIntNumInput::KIntNumInput(int val, QWidget *parent, int _base, const char *name) + : KNumInput(parent, name) +{ + init(val, _base); + +} + +void KIntNumInput::init(int val, int _base) +{ + d = new KIntNumInputPrivate( val ); + m_spin = new KIntSpinBox(INT_MIN, INT_MAX, 1, val, _base, this, "KIntNumInput::KIntSpinBox"); + // the KIntValidator is broken beyond believe for + // spinboxes which have suffix or prefix texts, so + // better don't use it unless absolutely necessary + if (_base != 10) + m_spin->setValidator(new KIntValidator(this, _base, "KNumInput::KIntValidtr")); + + connect(m_spin, SIGNAL(valueChanged(int)), SLOT(spinValueChanged(int))); + connect(this, SIGNAL(valueChanged(int)), + SLOT(slotEmitRelativeValueChanged(int))); + + setFocusProxy(m_spin); + layout(true); +} + +void KIntNumInput::setReferencePoint( int ref ) { + // clip to valid range: + ref = kMin( maxValue(), kMax( minValue(), ref ) ); + d->referencePoint = ref; +} + +int KIntNumInput::referencePoint() const { + return d->referencePoint; +} + +void KIntNumInput::spinValueChanged(int val) +{ + if(m_slider) + m_slider->setValue(val); + + emit valueChanged(val); +} + +void KIntNumInput::slotEmitRelativeValueChanged( int value ) { + if ( d->blockRelative || !d->referencePoint ) return; + emit relativeValueChanged( double( value ) / double( d->referencePoint ) ); +} + +void KIntNumInput::setRange(int lower, int upper, int step, bool slider) +{ + upper = kMax(upper, lower); + lower = kMin(upper, lower); + m_spin->setMinValue(lower); + m_spin->setMaxValue(upper); + m_spin->setLineStep(step); + + step = m_spin->lineStep(); // maybe QRangeControl didn't like out lineStep? + + if(slider) { + if (m_slider) + m_slider->setRange(lower, upper); + else { + m_slider = new QSlider(lower, upper, step, m_spin->value(), + QSlider::Horizontal, this); + m_slider->setTickmarks(QSlider::Below); + connect(m_slider, SIGNAL(valueChanged(int)), + m_spin, SLOT(setValue(int))); + } + + // calculate (upper-lower)/10 without overflowing int's: + int major = calcDiffByTen( upper, lower ); + if ( major==0 ) major = step; // #### workaround Qt bug in 2.1-beta4 + + m_slider->setSteps(step, major); + m_slider->setTickInterval(major); + } + else { + delete m_slider; + m_slider = 0; + } + + // check that reference point is still inside valid range: + setReferencePoint( referencePoint() ); + + layout(true); +} + +void KIntNumInput::setMinValue(int min) +{ + setRange(min, m_spin->maxValue(), m_spin->lineStep(), m_slider); +} + +int KIntNumInput::minValue() const +{ + return m_spin->minValue(); +} + +void KIntNumInput::setMaxValue(int max) +{ + setRange(m_spin->minValue(), max, m_spin->lineStep(), m_slider); +} + +int KIntNumInput::maxValue() const +{ + return m_spin->maxValue(); +} + +void KIntNumInput::setSuffix(const QString &suffix) +{ + m_spin->setSuffix(suffix); + + layout(true); +} + +QString KIntNumInput::suffix() const +{ + return m_spin->suffix(); +} + +void KIntNumInput::setPrefix(const QString &prefix) +{ + m_spin->setPrefix(prefix); + + layout(true); +} + +QString KIntNumInput::prefix() const +{ + return m_spin->prefix(); +} + +void KIntNumInput::setEditFocus(bool mark) +{ + m_spin->setEditFocus(mark); +} + +QSize KIntNumInput::minimumSizeHint() const +{ + constPolish(); + + int w; + int h; + + h = 2 + QMAX(m_sizeSpin.height(), m_sizeSlider.height()); + + // if in extra row, then count it here + if(m_label && (m_alignment & (AlignBottom|AlignTop))) + h += 4 + m_sizeLabel.height(); + else + // label is in the same row as the other widgets + h = QMAX(h, m_sizeLabel.height() + 2); + + w = m_slider ? m_slider->sizeHint().width() + 8 : 0; + w += m_colw1 + m_colw2; + + if(m_alignment & (AlignTop|AlignBottom)) + w = QMAX(w, m_sizeLabel.width() + 4); + + return QSize(w, h); +} + +void KIntNumInput::doLayout() +{ + m_sizeSpin = m_spin->sizeHint(); + m_colw2 = m_sizeSpin.width(); + + if (m_label) + m_label->setBuddy(m_spin); +} + +void KIntNumInput::resizeEvent(QResizeEvent* e) +{ + int w = m_colw1; + int h = 0; + + if(m_label && (m_alignment & AlignTop)) { + m_label->setGeometry(0, 0, e->size().width(), m_sizeLabel.height()); + h += m_sizeLabel.height() + KDialog::spacingHint(); + } + + if(m_label && (m_alignment & AlignVCenter)) + m_label->setGeometry(0, 0, w, m_sizeSpin.height()); + + if (qApp->reverseLayout()) + { + m_spin->setGeometry(w, h, m_slider ? m_colw2 : QMAX(m_colw2, e->size().width() - w), m_sizeSpin.height()); + w += m_colw2 + 8; + + if(m_slider) + m_slider->setGeometry(w, h, e->size().width() - w, m_sizeSpin.height()); + } + else if(m_slider) { + m_slider->setGeometry(w, h, e->size().width() - (w + m_colw2 + KDialog::spacingHint()), m_sizeSpin.height()); + m_spin->setGeometry(w + m_slider->size().width() + KDialog::spacingHint(), h, m_colw2, m_sizeSpin.height()); + } + else { + m_spin->setGeometry(w, h, QMAX(m_colw2, e->size().width() - w), m_sizeSpin.height()); + } + + h += m_sizeSpin.height() + 2; + + if(m_label && (m_alignment & AlignBottom)) + m_label->setGeometry(0, h, m_sizeLabel.width(), m_sizeLabel.height()); +} + +KIntNumInput::~KIntNumInput() +{ + delete d; +} + +void KIntNumInput::setValue(int val) +{ + m_spin->setValue(val); + // slider value is changed by spinValueChanged +} + +void KIntNumInput::setRelativeValue( double r ) { + if ( !d->referencePoint ) return; + ++d->blockRelative; + setValue( int( d->referencePoint * r + 0.5 ) ); + --d->blockRelative; +} + +double KIntNumInput::relativeValue() const { + if ( !d->referencePoint ) return 0; + return double( value() ) / double ( d->referencePoint ); +} + +int KIntNumInput::value() const +{ + return m_spin->value(); +} + +void KIntNumInput::setSpecialValueText(const QString& text) +{ + m_spin->setSpecialValueText(text); + layout(true); +} + +QString KIntNumInput::specialValueText() const +{ + return m_spin->specialValueText(); +} + +void KIntNumInput::setLabel(const QString & label, int a) +{ + KNumInput::setLabel(label, a); + + if(m_label) + m_label->setBuddy(m_spin); +} + +// ---------------------------------------------------------------------------- + +class KDoubleNumInput::KDoubleNumInputPrivate { +public: + KDoubleNumInputPrivate( double r ) + : spin( 0 ), + referencePoint( r ), + blockRelative ( 0 ) {} + KDoubleSpinBox * spin; + double referencePoint; + short blockRelative; +}; + +KDoubleNumInput::KDoubleNumInput(QWidget *parent, const char *name) + : KNumInput(parent, name) +{ + init(0.0, 0.0, 9999.0, 0.01, 2); +} + +KDoubleNumInput::KDoubleNumInput(double lower, double upper, double value, + double step, int precision, QWidget* parent, + const char *name) + : KNumInput(parent, name) +{ + init(value, lower, upper, step, precision); +} + +KDoubleNumInput::KDoubleNumInput(KNumInput *below, + double lower, double upper, double value, + double step, int precision, QWidget* parent, + const char *name) + : KNumInput(below, parent, name) +{ + init(value, lower, upper, step, precision); +} + +KDoubleNumInput::KDoubleNumInput(double value, QWidget *parent, const char *name) + : KNumInput(parent, name) +{ + init(value, kMin(0.0, value), kMax(0.0, value), 0.01, 2 ); +} + +KDoubleNumInput::KDoubleNumInput(KNumInput* below, double value, QWidget* parent, + const char* name) + : KNumInput(below, parent, name) +{ + init( value, kMin(0.0, value), kMax(0.0, value), 0.01, 2 ); +} + +KDoubleNumInput::~KDoubleNumInput() +{ + delete d; +} + +// ### remove when BIC changes are allowed again: + +bool KDoubleNumInput::eventFilter( QObject * o, QEvent * e ) { + return KNumInput::eventFilter( o, e ); +} + +void KDoubleNumInput::resetEditBox() { + +} + +// ### end stuff to remove when BIC changes are allowed again + + + +void KDoubleNumInput::init(double value, double lower, double upper, + double step, int precision ) +{ + // ### init no longer used members: + edit = 0; + m_range = true; + m_value = 0.0; + m_precision = 2; + // ### end + + d = new KDoubleNumInputPrivate( value ); + + d->spin = new KDoubleSpinBox( lower, upper, step, value, precision, + this, "KDoubleNumInput::d->spin" ); + setFocusProxy(d->spin); + connect( d->spin, SIGNAL(valueChanged(double)), + this, SIGNAL(valueChanged(double)) ); + connect( this, SIGNAL(valueChanged(double)), + this, SLOT(slotEmitRelativeValueChanged(double)) ); + + updateLegacyMembers(); + + layout(true); +} + +void KDoubleNumInput::updateLegacyMembers() { + // ### update legacy members that are either not private or for + // which an inlined getter exists: + m_lower = minValue(); + m_upper = maxValue(); + m_step = d->spin->lineStep(); + m_specialvalue = specialValueText(); +} + + +double KDoubleNumInput::mapSliderToSpin( int val ) const +{ + // map [slidemin,slidemax] to [spinmin,spinmax] + double spinmin = d->spin->minValue(); + double spinmax = d->spin->maxValue(); + double slidemin = m_slider->minValue(); // cast int to double to avoid + double slidemax = m_slider->maxValue(); // overflow in rel denominator + double rel = ( double(val) - slidemin ) / ( slidemax - slidemin ); + return spinmin + rel * ( spinmax - spinmin ); +} + +void KDoubleNumInput::sliderMoved(int val) +{ + d->spin->setValue( mapSliderToSpin( val ) ); +} + +void KDoubleNumInput::slotEmitRelativeValueChanged( double value ) +{ + if ( !d->referencePoint ) return; + emit relativeValueChanged( value / d->referencePoint ); +} + +QSize KDoubleNumInput::minimumSizeHint() const +{ + constPolish(); + + int w; + int h; + + h = 2 + QMAX(m_sizeEdit.height(), m_sizeSlider.height()); + + // if in extra row, then count it here + if(m_label && (m_alignment & (AlignBottom|AlignTop))) + h += 4 + m_sizeLabel.height(); + else + // label is in the same row as the other widgets + h = QMAX(h, m_sizeLabel.height() + 2); + + w = m_slider ? m_slider->sizeHint().width() + 8 : 0; + w += m_colw1 + m_colw2; + + if(m_alignment & (AlignTop|AlignBottom)) + w = QMAX(w, m_sizeLabel.width() + 4); + + return QSize(w, h); +} + +void KDoubleNumInput::resizeEvent(QResizeEvent* e) +{ + int w = m_colw1; + int h = 0; + + if(m_label && (m_alignment & AlignTop)) { + m_label->setGeometry(0, 0, e->size().width(), m_sizeLabel.height()); + h += m_sizeLabel.height() + 4; + } + + if(m_label && (m_alignment & AlignVCenter)) + m_label->setGeometry(0, 0, w, m_sizeEdit.height()); + + if (qApp->reverseLayout()) + { + d->spin->setGeometry(w, h, m_slider ? m_colw2 + : e->size().width() - w, m_sizeEdit.height()); + w += m_colw2 + KDialog::spacingHint(); + + if(m_slider) + m_slider->setGeometry(w, h, e->size().width() - w, m_sizeEdit.height()); + } + else if(m_slider) { + m_slider->setGeometry(w, h, e->size().width() - + (m_colw1 + m_colw2 + KDialog::spacingHint()), + m_sizeEdit.height()); + d->spin->setGeometry(w + m_slider->width() + KDialog::spacingHint(), h, + m_colw2, m_sizeEdit.height()); + } + else { + d->spin->setGeometry(w, h, e->size().width() - w, m_sizeEdit.height()); + } + + h += m_sizeEdit.height() + 2; + + if(m_label && (m_alignment & AlignBottom)) + m_label->setGeometry(0, h, m_sizeLabel.width(), m_sizeLabel.height()); +} + +void KDoubleNumInput::doLayout() +{ + m_sizeEdit = d->spin->sizeHint(); + m_colw2 = m_sizeEdit.width(); +} + +void KDoubleNumInput::setValue(double val) +{ + d->spin->setValue( val ); +} + +void KDoubleNumInput::setRelativeValue( double r ) +{ + if ( !d->referencePoint ) return; + ++d->blockRelative; + setValue( r * d->referencePoint ); + --d->blockRelative; +} + +void KDoubleNumInput::setReferencePoint( double ref ) +{ + // clip to valid range: + ref = kMin( maxValue(), kMax( minValue(), ref ) ); + d->referencePoint = ref; +} + +void KDoubleNumInput::setRange(double lower, double upper, double step, + bool slider) +{ + if( m_slider ) { + // don't update the slider to avoid an endless recursion + QSpinBox * spin = d->spin; + disconnect(spin, SIGNAL(valueChanged(int)), + m_slider, SLOT(setValue(int)) ); + } + d->spin->setRange( lower, upper, step, d->spin->precision() ); + + if(slider) { + // upcast to base type to get the min/maxValue in int form: + QSpinBox * spin = d->spin; + int slmax = spin->maxValue(); + int slmin = spin->minValue(); + int slvalue = spin->value(); + int slstep = spin->lineStep(); + if (m_slider) { + m_slider->setRange(slmin, slmax); + m_slider->setLineStep(slstep); + m_slider->setValue(slvalue); + } else { + m_slider = new QSlider(slmin, slmax, slstep, slvalue, + QSlider::Horizontal, this); + m_slider->setTickmarks(QSlider::Below); + // feedback line: when one moves, the other moves, too: + connect(m_slider, SIGNAL(valueChanged(int)), + SLOT(sliderMoved(int)) ); + } + connect(spin, SIGNAL(valueChanged(int)), + m_slider, SLOT(setValue(int)) ); + // calculate ( slmax - slmin ) / 10 without overflowing ints: + int major = calcDiffByTen( slmax, slmin ); + if ( !major ) major = slstep; // ### needed? + m_slider->setTickInterval(major); + } else { + delete m_slider; + m_slider = 0; + } + + setReferencePoint( referencePoint() ); + + layout(true); + updateLegacyMembers(); +} + +void KDoubleNumInput::setMinValue(double min) +{ + setRange(min, maxValue(), d->spin->lineStep(), m_slider); +} + +double KDoubleNumInput::minValue() const +{ + return d->spin->minValue(); +} + +void KDoubleNumInput::setMaxValue(double max) +{ + setRange(minValue(), max, d->spin->lineStep(), m_slider); +} + +double KDoubleNumInput::maxValue() const +{ + return d->spin->maxValue(); +} + +double KDoubleNumInput::value() const +{ + return d->spin->value(); +} + +double KDoubleNumInput::relativeValue() const +{ + if ( !d->referencePoint ) return 0; + return value() / d->referencePoint; +} + +double KDoubleNumInput::referencePoint() const +{ + return d->referencePoint; +} + +QString KDoubleNumInput::suffix() const +{ + return d->spin->suffix(); +} + +QString KDoubleNumInput::prefix() const +{ + return d->spin->prefix(); +} + +void KDoubleNumInput::setSuffix(const QString &suffix) +{ + d->spin->setSuffix( suffix ); + + layout(true); +} + +void KDoubleNumInput::setPrefix(const QString &prefix) +{ + d->spin->setPrefix( prefix ); + + layout(true); +} + +void KDoubleNumInput::setPrecision(int precision) +{ + d->spin->setPrecision( precision ); + + layout(true); +} + +int KDoubleNumInput::precision() const +{ + return d->spin->precision(); +} + +void KDoubleNumInput::setSpecialValueText(const QString& text) +{ + d->spin->setSpecialValueText( text ); + + layout(true); + updateLegacyMembers(); +} + +void KDoubleNumInput::setLabel(const QString & label, int a) +{ + KNumInput::setLabel(label, a); + + if(m_label) + m_label->setBuddy(d->spin); + +} + +// ---------------------------------------------------------------------------- + + +class KDoubleSpinBoxValidator : public KDoubleValidator +{ +public: + KDoubleSpinBoxValidator( double bottom, double top, int decimals, KDoubleSpinBox* sb, const char *name ) + : KDoubleValidator( bottom, top, decimals, sb, name ), spinBox( sb ) { } + + virtual State validate( QString& str, int& pos ) const; + +private: + KDoubleSpinBox *spinBox; +}; + +QValidator::State KDoubleSpinBoxValidator::validate( QString& str, int& pos ) const +{ + QString pref = spinBox->prefix(); + QString suff = spinBox->suffix(); + QString suffStriped = suff.stripWhiteSpace(); + uint overhead = pref.length() + suff.length(); + State state = Invalid; + + if ( overhead == 0 ) { + state = KDoubleValidator::validate( str, pos ); + } else { + bool stripedVersion = false; + if ( str.length() >= overhead && str.startsWith(pref) + && (str.endsWith(suff) + || (stripedVersion = str.endsWith(suffStriped))) ) { + if ( stripedVersion ) + overhead = pref.length() + suffStriped.length(); + QString core = str.mid( pref.length(), str.length() - overhead ); + int corePos = pos - pref.length(); + state = KDoubleValidator::validate( core, corePos ); + pos = corePos + pref.length(); + str.replace( pref.length(), str.length() - overhead, core ); + } else { + state = KDoubleValidator::validate( str, pos ); + if ( state == Invalid ) { + // stripWhiteSpace(), cf. QSpinBox::interpretText() + QString special = spinBox->specialValueText().stripWhiteSpace(); + QString candidate = str.stripWhiteSpace(); + + if ( special.startsWith(candidate) ) { + if ( candidate.length() == special.length() ) { + state = Acceptable; + } else { + state = Intermediate; + } + } + } + } + } + return state; +} + +// We use a kind of fixed-point arithmetic to represent the range of +// doubles [mLower,mUpper] in steps of 10^(-mPrecision). Thus, the +// following relations hold: +// +// 1. factor = 10^mPrecision +// 2. basicStep = 1/factor = 10^(-mPrecision); +// 3. lowerInt = lower * factor; +// 4. upperInt = upper * factor; +// 5. lower = lowerInt * basicStep; +// 6. upper = upperInt * basicStep; +class KDoubleSpinBox::Private { +public: + Private( int precision=1 ) + : mPrecision( precision ), + mValidator( 0 ) + { + } + + int factor() const { + int f = 1; + for ( int i = 0 ; i < mPrecision ; ++i ) f *= 10; + return f; + } + + double basicStep() const { + return 1.0/double(factor()); + } + + int mapToInt( double value, bool * ok ) const { + assert( ok ); + const double f = factor(); + if ( value > double(INT_MAX) / f ) { + kdWarning() << "KDoubleSpinBox: can't represent value " << value + << "in terms of fixed-point numbers with precision " + << mPrecision << endl; + *ok = false; + return INT_MAX; + } else if ( value < double(INT_MIN) / f ) { + kdWarning() << "KDoubleSpinBox: can't represent value " << value + << "in terms of fixed-point numbers with precision " + << mPrecision << endl; + *ok = false; + return INT_MIN; + } else { + *ok = true; + return int( value * f + ( value < 0 ? -0.5 : 0.5 ) ); + } + } + + double mapToDouble( int value ) const { + return double(value) * basicStep(); + } + + int mPrecision; + KDoubleSpinBoxValidator * mValidator; +}; + +KDoubleSpinBox::KDoubleSpinBox( QWidget * parent, const char * name ) + : QSpinBox( parent, name ) +{ + editor()->setAlignment( Qt::AlignRight ); + d = new Private(); + updateValidator(); + connect( this, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int)) ); +} + +KDoubleSpinBox::KDoubleSpinBox( double lower, double upper, double step, + double value, int precision, + QWidget * parent, const char * name ) + : QSpinBox( parent, name ) +{ + editor()->setAlignment( Qt::AlignRight ); + d = new Private(); + setRange( lower, upper, step, precision ); + setValue( value ); + connect( this, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int)) ); +} + +KDoubleSpinBox::~KDoubleSpinBox() { + delete d; d = 0; +} + +bool KDoubleSpinBox::acceptLocalizedNumbers() const { + if ( !d->mValidator ) return true; // we'll set one that does; + // can't do it now, since we're const + return d->mValidator->acceptLocalizedNumbers(); +} + +void KDoubleSpinBox::setAcceptLocalizedNumbers( bool accept ) { + if ( !d->mValidator ) updateValidator(); + d->mValidator->setAcceptLocalizedNumbers( accept ); +} + +void KDoubleSpinBox::setRange( double lower, double upper, double step, + int precision ) { + lower = kMin(upper, lower); + upper = kMax(upper, lower); + setPrecision( precision, true ); // disable bounds checking, since + setMinValue( lower ); // it's done in set{Min,Max}Value + setMaxValue( upper ); // anyway and we want lower, upper + setLineStep( step ); // and step to have the right precision +} + +int KDoubleSpinBox::precision() const { + return d->mPrecision; +} + +void KDoubleSpinBox::setPrecision( int precision ) { + setPrecision( precision, false ); +} + +void KDoubleSpinBox::setPrecision( int precision, bool force ) { + if ( precision < 1 ) return; + if ( !force ) { + int maxPrec = maxPrecision(); + if ( precision > maxPrec ) + precision = maxPrec; + } + d->mPrecision = precision; + updateValidator(); +} + +int KDoubleSpinBox::maxPrecision() const { + // INT_MAX must be > maxAbsValue * 10^precision + // ==> 10^precision < INT_MAX / maxAbsValue + // ==> precision < log10 ( INT_MAX / maxAbsValue ) + // ==> maxPrecision = floor( log10 ( INT_MAX / maxAbsValue ) ); + double maxAbsValue = kMax( fabs(minValue()), fabs(maxValue()) ); + if ( maxAbsValue == 0 ) return 6; // return arbitrary value to avoid dbz... + + return int( floor( log10( double(INT_MAX) / maxAbsValue ) ) ); +} + +double KDoubleSpinBox::value() const { + return d->mapToDouble( base::value() ); +} + +void KDoubleSpinBox::setValue( double value ) { + if ( value == this->value() ) return; + if ( value < minValue() ) + base::setValue( base::minValue() ); + else if ( value > maxValue() ) + base::setValue( base::maxValue() ); + else { + bool ok = false; + base::setValue( d->mapToInt( value, &ok ) ); + assert( ok ); + } +} + +double KDoubleSpinBox::minValue() const { + return d->mapToDouble( base::minValue() ); +} + +void KDoubleSpinBox::setMinValue( double value ) { + bool ok = false; + int min = d->mapToInt( value, &ok ); + if ( !ok ) return; + base::setMinValue( min ); + updateValidator(); +} + + +double KDoubleSpinBox::maxValue() const { + return d->mapToDouble( base::maxValue() ); +} + +void KDoubleSpinBox::setMaxValue( double value ) { + bool ok = false; + int max = d->mapToInt( value, &ok ); + if ( !ok ) return; + base::setMaxValue( max ); + updateValidator(); +} + +double KDoubleSpinBox::lineStep() const { + return d->mapToDouble( base::lineStep() ); +} + +void KDoubleSpinBox::setLineStep( double step ) { + bool ok = false; + if ( step > maxValue() - minValue() ) + base::setLineStep( 1 ); + else + base::setLineStep( kMax( d->mapToInt( step, &ok ), 1 ) ); +} + +QString KDoubleSpinBox::mapValueToText( int value ) { + if ( acceptLocalizedNumbers() ) + return KGlobal::locale() + ->formatNumber( d->mapToDouble( value ), d->mPrecision ); + else + return QString().setNum( d->mapToDouble( value ), 'f', d->mPrecision ); +} + +int KDoubleSpinBox::mapTextToValue( bool * ok ) { + double value; + if ( acceptLocalizedNumbers() ) + value = KGlobal::locale()->readNumber( cleanText(), ok ); + else + value = cleanText().toDouble( ok ); + if ( !*ok ) return 0; + if ( value > maxValue() ) + value = maxValue(); + else if ( value < minValue() ) + value = minValue(); + return d->mapToInt( value, ok ); +} + +void KDoubleSpinBox::setValidator( const QValidator * ) { + // silently discard the new validator. We don't want another one ;-) +} + +void KDoubleSpinBox::slotValueChanged( int value ) { + emit valueChanged( d->mapToDouble( value ) ); +} + +void KDoubleSpinBox::updateValidator() { + if ( !d->mValidator ) { + d->mValidator = new KDoubleSpinBoxValidator( minValue(), maxValue(), precision(), + this, "d->mValidator" ); + base::setValidator( d->mValidator ); + } else + d->mValidator->setRange( minValue(), maxValue(), precision() ); +} + +void KNumInput::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +void KIntNumInput::virtual_hook( int id, void* data ) +{ KNumInput::virtual_hook( id, data ); } + +void KDoubleNumInput::virtual_hook( int id, void* data ) +{ KNumInput::virtual_hook( id, data ); } + +void KIntSpinBox::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +void KDoubleSpinBox::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +#include "knuminput.moc" |