@ -9,63 +9,185 @@
# include "qvaluecombobox.h"
# include "qvaluecombobox.h"
# include <QApplication>
# include <QApplication>
# include <QDouble SpinBox>
# include <QAbstract SpinBox>
# include <QHBoxLayout>
# include <QHBoxLayout>
# include <QKeyEvent>
# include <QKeyEvent>
# include <qmath.h> // for qPow()
# include <QLineEdit>
// QDoubleSpinBox that shows SI-style thin space thousands separators
/** QSpinBox that uses fixed-point numbers internally and uses our own
class AmountSpinBox : public QDoubleSpinBox
* formatting / parsing functions .
*/
class AmountSpinBox : public QAbstractSpinBox
{
{
Q_OBJECT
public :
public :
explicit AmountSpinBox ( QWidget * parent ) :
explicit AmountSpinBox ( QWidget * parent ) :
QDoubleSpinBox ( parent )
QAbstractSpinBox ( parent ) ,
currentUnit ( BitcoinUnits : : BTC ) ,
singleStep ( 100000 ) // satoshis
{
{
setAlignment ( Qt : : AlignRight ) ;
connect ( lineEdit ( ) , SIGNAL ( textEdited ( QString ) ) , this , SIGNAL ( valueChanged ( ) ) ) ;
}
QValidator : : State validate ( QString & text , int & pos ) const
{
if ( text . isEmpty ( ) )
return QValidator : : Intermediate ;
bool valid = false ;
parse ( text , & valid ) ;
/* Make sure we return Intermediate so that fixup() is called on defocus */
return valid ? QValidator : : Intermediate : QValidator : : Invalid ;
}
void fixup ( QString & input ) const
{
bool valid = false ;
qint64 val = parse ( input , & valid ) ;
if ( valid )
{
input = BitcoinUnits : : format ( currentUnit , val , false , BitcoinUnits : : separatorAlways ) ;
lineEdit ( ) - > setText ( input ) ;
}
}
QString textFromValue ( double value ) const
}
qint64 value ( bool * valid_out = 0 ) const
{
return parse ( text ( ) , valid_out ) ;
}
void setValue ( qint64 value )
{
lineEdit ( ) - > setText ( BitcoinUnits : : format ( currentUnit , value , false , BitcoinUnits : : separatorAlways ) ) ;
emit valueChanged ( ) ;
}
void stepBy ( int steps )
{
bool valid = false ;
qint64 val = value ( & valid ) ;
val = val + steps * singleStep ;
val = qMin ( qMax ( val , Q_INT64_C ( 0 ) ) , BitcoinUnits : : maxMoney ( ) ) ;
setValue ( val ) ;
}
StepEnabled stepEnabled ( ) const
{
{
QStringList parts = QDoubleSpinBox : : textFromValue ( value ) . split ( " . " ) ;
StepEnabled rv = 0 ;
QString quotient_str = parts [ 0 ] ;
if ( text ( ) . isEmpty ( ) ) // Allow step-up with empty field
QString remainder_str ;
return StepUpEnabled ;
if ( parts . size ( ) > 1 )
bool valid = false ;
remainder_str = parts [ 1 ] ;
qint64 val = value ( & valid ) ;
if ( valid )
{
if ( val > 0 )
rv | = StepDownEnabled ;
if ( val < BitcoinUnits : : maxMoney ( ) )
rv | = StepUpEnabled ;
}
return rv ;
}
// Code duplication between here and BitcoinUnits::format
void setDisplayUnit ( int unit )
// TODO: Figure out how to share this code
{
QChar thin_sp ( THIN_SP_CP ) ;
bool valid = false ;
int q_size = quotient_str . size ( ) ;
qint64 val = value ( & valid ) ;
if ( q_size > 4 )
for ( int i = 3 ; i < q_size ; i + = 3 )
quotient_str . insert ( q_size - i , thin_sp ) ;
int r_size = remainder_str . size ( ) ;
currentUnit = unit ;
if ( r_size > 4 )
for ( int i = 3 , adj = 0 ; i < r_size ; i + = 3 , adj + + )
remainder_str . insert ( i + adj , thin_sp ) ;
if ( remainder_str . isEmpty ( ) )
if ( valid )
return quotient_str ;
setValue ( val ) ;
else
else
return quotient_str + QSt ring ( " . " ) + remainder_str ;
clea r( ) ;
}
}
QValidator : : State validate ( QString & text , int & pos ) const
void setSingleStep ( qint64 step )
{
{
QString s ( BitcoinUnits : : removeSpaces ( text ) ) ;
singleStep = step ;
return QDoubleSpinBox : : validate ( s , pos ) ;
}
}
double valueFromText ( const QString & text ) const
QSize minimumSizeHint ( ) const
{
if ( cachedMinimumSizeHint . isEmpty ( ) )
{
ensurePolished ( ) ;
const QFontMetrics fm ( fontMetrics ( ) ) ;
int h = lineEdit ( ) - > minimumSizeHint ( ) . height ( ) ;
int w = fm . width ( BitcoinUnits : : format ( BitcoinUnits : : BTC , BitcoinUnits : : maxMoney ( ) , false , BitcoinUnits : : separatorAlways ) ) ;
w + = 2 ; // cursor blinking space
QStyleOptionSpinBox opt ;
initStyleOption ( & opt ) ;
QSize hint ( w , h ) ;
QSize extra ( 35 , 6 ) ;
opt . rect . setSize ( hint + extra ) ;
extra + = hint - style ( ) - > subControlRect ( QStyle : : CC_SpinBox , & opt ,
QStyle : : SC_SpinBoxEditField , this ) . size ( ) ;
// get closer to final result by repeating the calculation
opt . rect . setSize ( hint + extra ) ;
extra + = hint - style ( ) - > subControlRect ( QStyle : : CC_SpinBox , & opt ,
QStyle : : SC_SpinBoxEditField , this ) . size ( ) ;
hint + = extra ;
opt . rect = rect ( ) ;
cachedMinimumSizeHint = style ( ) - > sizeFromContents ( QStyle : : CT_SpinBox , & opt , hint , this )
. expandedTo ( QApplication : : globalStrut ( ) ) ;
}
return cachedMinimumSizeHint ;
}
private :
int currentUnit ;
qint64 singleStep ;
mutable QSize cachedMinimumSizeHint ;
/**
* Parse a string into a number of base monetary units and
* return validity .
* @ note Must return 0 if ! valid .
*/
qint64 parse ( const QString & text , bool * valid_out = 0 ) const
{
qint64 val = 0 ;
bool valid = BitcoinUnits : : parse ( currentUnit , text , & val ) ;
if ( valid )
{
if ( val < 0 | | val > BitcoinUnits : : maxMoney ( ) )
valid = false ;
}
if ( valid_out )
* valid_out = valid ;
return valid ? val : 0 ;
}
protected :
bool event ( QEvent * event )
{
if ( event - > type ( ) = = QEvent : : KeyPress | | event - > type ( ) = = QEvent : : KeyRelease )
{
{
return QDoubleSpinBox : : valueFromText ( BitcoinUnits : : removeSpaces ( text ) ) ;
QKeyEvent * keyEvent = static_cast < QKeyEvent * > ( event ) ;
if ( keyEvent - > key ( ) = = Qt : : Key_Comma )
{
// Translate a comma into a period
QKeyEvent periodKeyEvent ( event - > type ( ) , Qt : : Key_Period , keyEvent - > modifiers ( ) , " . " , keyEvent - > isAutoRepeat ( ) , keyEvent - > count ( ) ) ;
return QAbstractSpinBox : : event ( & periodKeyEvent ) ;
}
}
return QAbstractSpinBox : : event ( event ) ;
}
}
signals :
void valueChanged ( ) ;
} ;
} ;
# include "bitcoinamountfield.moc"
BitcoinAmountField : : BitcoinAmountField ( QWidget * parent ) :
BitcoinAmountField : : BitcoinAmountField ( QWidget * parent ) :
QWidget ( parent ) ,
QWidget ( parent ) ,
amount ( 0 ) ,
amount ( 0 )
currentUnit ( - 1 )
{
{
nSingleStep = 100000 ; // satoshis
amount = new AmountSpinBox ( this ) ;
amount = new AmountSpinBox ( this ) ;
amount - > setLocale ( QLocale : : c ( ) ) ;
amount - > setLocale ( QLocale : : c ( ) ) ;
amount - > installEventFilter ( this ) ;
amount - > installEventFilter ( this ) ;
@ -85,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
setFocusProxy ( amount ) ;
setFocusProxy ( amount ) ;
// If one if the widgets changes, the combined content changes as well
// If one if the widgets changes, the combined content changes as well
connect ( amount , SIGNAL ( valueChanged ( QString ) ) , this , SIGNAL ( text Changed( ) ) ) ;
connect ( amount , SIGNAL ( valueChanged ( ) ) , this , SIGNAL ( value Changed( ) ) ) ;
connect ( unit , SIGNAL ( currentIndexChanged ( int ) ) , this , SLOT ( unitChanged ( int ) ) ) ;
connect ( unit , SIGNAL ( currentIndexChanged ( int ) ) , this , SLOT ( unitChanged ( int ) ) ) ;
// Set default based on configuration
// Set default based on configuration
unitChanged ( unit - > currentIndex ( ) ) ;
unitChanged ( unit - > currentIndex ( ) ) ;
}
}
void BitcoinAmountField : : setText ( const QString & text )
{
if ( text . isEmpty ( ) )
amount - > clear ( ) ;
else
amount - > setValue ( BitcoinUnits : : removeSpaces ( text ) . toDouble ( ) ) ;
}
void BitcoinAmountField : : clear ( )
void BitcoinAmountField : : clear ( )
{
{
amount - > clear ( ) ;
amount - > clear ( ) ;
@ -108,16 +222,9 @@ void BitcoinAmountField::clear()
bool BitcoinAmountField : : validate ( )
bool BitcoinAmountField : : validate ( )
{
{
bool valid = true ;
bool valid = false ;
if ( amount - > value ( ) = = 0.0 )
value ( & valid ) ;
valid = false ;
else if ( ! BitcoinUnits : : parse ( currentUnit , text ( ) , 0 ) )
valid = false ;
else if ( amount - > value ( ) > BitcoinUnits : : maxAmount ( currentUnit ) )
valid = false ;
setValid ( valid ) ;
setValid ( valid ) ;
return valid ;
return valid ;
}
}
@ -129,14 +236,6 @@ void BitcoinAmountField::setValid(bool valid)
amount - > setStyleSheet ( STYLE_INVALID ) ;
amount - > setStyleSheet ( STYLE_INVALID ) ;
}
}
QString BitcoinAmountField : : text ( ) const
{
if ( amount - > text ( ) . isEmpty ( ) )
return QString ( ) ;
else
return amount - > text ( ) ;
}
bool BitcoinAmountField : : eventFilter ( QObject * object , QEvent * event )
bool BitcoinAmountField : : eventFilter ( QObject * object , QEvent * event )
{
{
if ( event - > type ( ) = = QEvent : : FocusIn )
if ( event - > type ( ) = = QEvent : : FocusIn )
@ -144,17 +243,6 @@ bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
// Clear invalid flag on focus
// Clear invalid flag on focus
setValid ( true ) ;
setValid ( true ) ;
}
}
else if ( event - > type ( ) = = QEvent : : KeyPress | | event - > type ( ) = = QEvent : : KeyRelease )
{
QKeyEvent * keyEvent = static_cast < QKeyEvent * > ( event ) ;
if ( keyEvent - > key ( ) = = Qt : : Key_Comma )
{
// Translate a comma into a period
QKeyEvent periodKeyEvent ( event - > type ( ) , Qt : : Key_Period , keyEvent - > modifiers ( ) , " . " , keyEvent - > isAutoRepeat ( ) , keyEvent - > count ( ) ) ;
QApplication : : sendEvent ( object , & periodKeyEvent ) ;
return true ;
}
}
return QWidget : : eventFilter ( object , event ) ;
return QWidget : : eventFilter ( object , event ) ;
}
}
@ -167,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
qint64 BitcoinAmountField : : value ( bool * valid_out ) const
qint64 BitcoinAmountField : : value ( bool * valid_out ) const
{
{
qint64 val_out = 0 ;
return amount - > value ( valid_out ) ;
bool valid = BitcoinUnits : : parse ( currentUnit , text ( ) , & val_out ) ;
if ( valid_out )
{
* valid_out = valid ;
}
return val_out ;
}
}
void BitcoinAmountField : : setValue ( qint64 value )
void BitcoinAmountField : : setValue ( qint64 value )
{
{
setText ( BitcoinUnits : : format ( currentUnit , value ) ) ;
amount - > setValue ( value ) ;
}
}
void BitcoinAmountField : : setReadOnly ( bool fReadOnly )
void BitcoinAmountField : : setReadOnly ( bool fReadOnly )
@ -195,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx)
// Determine new unit ID
// Determine new unit ID
int newUnit = unit - > itemData ( idx , BitcoinUnits : : UnitRole ) . toInt ( ) ;
int newUnit = unit - > itemData ( idx , BitcoinUnits : : UnitRole ) . toInt ( ) ;
// Parse current value and convert to new unit
amount - > setDisplayUnit ( newUnit ) ;
bool valid = false ;
qint64 currentValue = value ( & valid ) ;
currentUnit = newUnit ;
// Set max length after retrieving the value, to prevent truncation
amount - > setDecimals ( BitcoinUnits : : decimals ( currentUnit ) ) ;
amount - > setMaximum ( qPow ( 10 , BitcoinUnits : : amountDigits ( currentUnit ) ) - qPow ( 10 , - amount - > decimals ( ) ) ) ;
amount - > setSingleStep ( ( double ) nSingleStep / ( double ) BitcoinUnits : : factor ( currentUnit ) ) ;
if ( valid )
{
// If value was valid, re-place it in the widget with the new unit
setValue ( currentValue ) ;
}
else
{
// If current value is invalid, just clear field
setText ( " " ) ;
}
setValid ( true ) ;
}
}
void BitcoinAmountField : : setDisplayUnit ( int newUnit )
void BitcoinAmountField : : setDisplayUnit ( int newUnit )
@ -226,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit)
void BitcoinAmountField : : setSingleStep ( qint64 step )
void BitcoinAmountField : : setSingleStep ( qint64 step )
{
{
nSingleStep = step ;
amount - > setSingleStep ( step ) ;
unitChanged ( unit - > currentIndex ( ) ) ;
}
}