99#include  " qvaluecombobox.h" 
1010
1111#include  < QApplication> 
12- #include  < QDoubleSpinBox > 
12+ #include  < QAbstractSpinBox > 
1313#include  < QHBoxLayout> 
1414#include  < QKeyEvent> 
15- #include  < qmath.h >   //  for qPow() 
15+ #include  < QLineEdit > 
1616
17- //  QDoubleSpinBox that shows SI-style thin space thousands separators
18- class  AmountSpinBox : public  QDoubleSpinBox 
17+ /* * QSpinBox that uses fixed-point numbers internally and uses our own
18+  * formatting/parsing functions. 
19+  */  
20+ class  AmountSpinBox : public  QAbstractSpinBox 
1921{
22+     Q_OBJECT
2023public: 
2124    explicit  AmountSpinBox (QWidget *parent):
22-         QDoubleSpinBox(parent)
25+         QAbstractSpinBox(parent),
26+         currentUnit(BitcoinUnits::BTC),
27+         singleStep(100000 ) //  satoshis
2328    {
29+         setAlignment (Qt::AlignRight);
30+ 
31+         connect (lineEdit (), SIGNAL (textEdited (QString)), this , SIGNAL (valueChanged ()));
32+     }
33+ 
34+     QValidator::State validate (QString &text, int  &pos) const 
35+     {
36+         if (text.isEmpty ())
37+             return  QValidator::Intermediate;
38+         bool  valid = false ;
39+         parse (text, &valid);
40+         /*  Make sure we return Intermediate so that fixup() is called on defocus */ 
41+         return  valid ? QValidator::Intermediate : QValidator::Invalid;
42+     }
43+ 
44+     void  fixup (QString &input) const 
45+     {
46+         bool  valid = false ;
47+         qint64 val = parse (input, &valid);
48+         if (valid)
49+         {
50+             input = BitcoinUnits::format (currentUnit, val, false , BitcoinUnits::separatorAlways);
51+             lineEdit ()->setText (input);
52+         }
2453    }
25-     QString textFromValue (double  value) const 
54+ 
55+     qint64 value (bool  *valid_out=0 ) const 
2656    {
27-         QStringList parts = QDoubleSpinBox::textFromValue (value).split (" ."  );
28-         QString quotient_str = parts[0 ];
29-         QString remainder_str;
30-         if (parts.size () > 1 )
31-             remainder_str = parts[1 ];
32- 
33-         //  Code duplication between here and BitcoinUnits::format
34-         //  TODO: Figure out how to share this code
35-         QChar thin_sp (THIN_SP_CP);
36-         int  q_size = quotient_str.size ();
37-         if  (q_size > 4 )
38-             for  (int  i = 3 ; i < q_size; i += 3 )
39-                 quotient_str.insert (q_size - i, thin_sp);
40- 
41-         int  r_size = remainder_str.size ();
42-         if  (r_size > 4 )
43-             for  (int  i = 3 , adj = 0 ; i < r_size; i += 3 , adj++)
44-                 remainder_str.insert (i + adj, thin_sp);
45- 
46-         if (remainder_str.isEmpty ())
47-             return  quotient_str;
57+         return  parse (text (), valid_out);
58+     }
59+ 
60+     void  setValue (qint64 value)
61+     {
62+         lineEdit ()->setText (BitcoinUnits::format (currentUnit, value, false , BitcoinUnits::separatorAlways));
63+         emit valueChanged ();
64+     }
65+ 
66+     void  stepBy (int  steps)
67+     {
68+         bool  valid = false ;
69+         qint64 val = value (&valid);
70+         val = val + steps * singleStep;
71+         val = qMin (qMax (val, Q_INT64_C (0 )), BitcoinUnits::maxMoney ());
72+         setValue (val);
73+     }
74+ 
75+     StepEnabled stepEnabled () const 
76+     {
77+         StepEnabled rv = 0 ;
78+         if (text ().isEmpty ()) //  Allow step-up with empty field
79+             return  StepUpEnabled;
80+         bool  valid = false ;
81+         qint64 val = value (&valid);
82+         if (valid)
83+         {
84+             if (val > 0 )
85+                 rv |= StepDownEnabled;
86+             if (val < BitcoinUnits::maxMoney ())
87+                 rv |= StepUpEnabled;
88+         }
89+         return  rv;
90+     }
91+ 
92+     void  setDisplayUnit (int  unit)
93+     {
94+         bool  valid = false ;
95+         qint64 val = value (&valid);
96+ 
97+         currentUnit = unit;
98+ 
99+         if (valid)
100+             setValue (val);
48101        else 
49-             return  quotient_str +  QString ( " . " ) + remainder_str ;
102+             clear () ;
50103    }
51-     QValidator::State validate  (QString &text, int  &pos) const 
104+ 
105+     void  setSingleStep (qint64 step)
52106    {
53-         QString s (BitcoinUnits::removeSpaces (text));
54-         return  QDoubleSpinBox::validate (s, pos);
107+         singleStep = step;
108+     }
109+ 
110+     QSize minimumSizeHint () const 
111+     {
112+         if (cachedMinimumSizeHint.isEmpty ())
113+         {
114+             ensurePolished ();
115+ 
116+             const  QFontMetrics fm (fontMetrics ());
117+             int  h = lineEdit ()->minimumSizeHint ().height ();
118+             int  w = fm.width (BitcoinUnits::format (BitcoinUnits::BTC, BitcoinUnits::maxMoney (), false , BitcoinUnits::separatorAlways));
119+             w += 2 ; //  cursor blinking space
120+ 
121+             QStyleOptionSpinBox opt;
122+             initStyleOption (&opt);
123+             QSize hint (w, h);
124+             QSize extra (35 , 6 );
125+             opt.rect .setSize (hint + extra);
126+             extra += hint - style ()->subControlRect (QStyle::CC_SpinBox, &opt,
127+                                                     QStyle::SC_SpinBoxEditField, this ).size ();
128+             //  get closer to final result by repeating the calculation
129+             opt.rect .setSize (hint + extra);
130+             extra += hint - style ()->subControlRect (QStyle::CC_SpinBox, &opt,
131+                                                     QStyle::SC_SpinBoxEditField, this ).size ();
132+             hint += extra;
133+ 
134+             opt.rect  = rect ();
135+ 
136+             cachedMinimumSizeHint = style ()->sizeFromContents (QStyle::CT_SpinBox, &opt, hint, this )
137+                                     .expandedTo (QApplication::globalStrut ());
138+         }
139+         return  cachedMinimumSizeHint;
140+     }
141+ private: 
142+     int  currentUnit;
143+     qint64 singleStep;
144+     mutable  QSize cachedMinimumSizeHint;
145+ 
146+     /* *
147+      * Parse a string into a number of base monetary units and 
148+      * return validity. 
149+      * @note Must return 0 if !valid. 
150+      */  
151+     qint64 parse (const  QString &text, bool  *valid_out=0 ) const 
152+     {
153+         qint64 val = 0 ;
154+         bool  valid = BitcoinUnits::parse (currentUnit, text, &val);
155+         if (valid)
156+         {
157+             if (val < 0  || val > BitcoinUnits::maxMoney ())
158+                 valid = false ;
159+         }
160+         if (valid_out)
161+             *valid_out = valid;
162+         return  valid ? val : 0 ;
55163    }
56-     double  valueFromText (const  QString& text) const 
164+ 
165+ protected: 
166+     bool  event (QEvent *event)
57167    {
58-         return  QDoubleSpinBox::valueFromText (BitcoinUnits::removeSpaces (text));
168+         if  (event->type () == QEvent::KeyPress || event->type () == QEvent::KeyRelease)
169+         {
170+             QKeyEvent *keyEvent = static_cast <QKeyEvent *>(event);
171+             if  (keyEvent->key () == Qt::Key_Comma)
172+             {
173+                 //  Translate a comma into a period
174+                 QKeyEvent periodKeyEvent (event->type (), Qt::Key_Period, keyEvent->modifiers (), " ."  , keyEvent->isAutoRepeat (), keyEvent->count ());
175+                 return  QAbstractSpinBox::event (&periodKeyEvent);
176+             }
177+         }
178+         return  QAbstractSpinBox::event (event);
59179    }
180+ 
181+ signals:
182+     void  valueChanged ();
60183};
61184
185+ #include  " bitcoinamountfield.moc" 
186+ 
62187BitcoinAmountField::BitcoinAmountField (QWidget *parent) :
63188    QWidget(parent),
64-     amount(0 ),
65-     currentUnit(-1 )
189+     amount(0 )
66190{
67-     nSingleStep = 100000 ; //  satoshis
68- 
69191    amount = new  AmountSpinBox (this );
70192    amount->setLocale (QLocale::c ());
71193    amount->installEventFilter (this );
@@ -85,21 +207,13 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
85207    setFocusProxy (amount);
86208
87209    //  If one if the widgets changes, the combined content changes as well
88-     connect (amount, SIGNAL (valueChanged (QString )), this , SIGNAL (textChanged ()));
210+     connect (amount, SIGNAL (valueChanged ()), this , SIGNAL (valueChanged ()));
89211    connect (unit, SIGNAL (currentIndexChanged (int )), this , SLOT (unitChanged (int )));
90212
91213    //  Set default based on configuration
92214    unitChanged (unit->currentIndex ());
93215}
94216
95- void  BitcoinAmountField::setText (const  QString &text)
96- {
97-     if  (text.isEmpty ())
98-         amount->clear ();
99-     else 
100-         amount->setValue (BitcoinUnits::removeSpaces (text).toDouble ());
101- }
102- 
103217void  BitcoinAmountField::clear ()
104218{
105219    amount->clear ();
@@ -108,16 +222,9 @@ void BitcoinAmountField::clear()
108222
109223bool  BitcoinAmountField::validate ()
110224{
111-     bool  valid = true ;
112-     if  (amount->value () == 0.0 )
113-         valid = false ;
114-     else  if  (!BitcoinUnits::parse (currentUnit, text (), 0 ))
115-         valid = false ;
116-     else  if  (amount->value () > BitcoinUnits::maxAmount (currentUnit))
117-         valid = false ;
118- 
225+     bool  valid = false ;
226+     value (&valid);
119227    setValid (valid);
120- 
121228    return  valid;
122229}
123230
@@ -129,32 +236,13 @@ void BitcoinAmountField::setValid(bool valid)
129236        amount->setStyleSheet (STYLE_INVALID);
130237}
131238
132- QString BitcoinAmountField::text () const 
133- {
134-     if  (amount->text ().isEmpty ())
135-         return  QString ();
136-     else 
137-         return  amount->text ();
138- }
139- 
140239bool  BitcoinAmountField::eventFilter (QObject *object, QEvent *event)
141240{
142241    if  (event->type () == QEvent::FocusIn)
143242    {
144243        //  Clear invalid flag on focus
145244        setValid (true );
146245    }
147-     else  if  (event->type () == QEvent::KeyPress || event->type () == QEvent::KeyRelease)
148-     {
149-         QKeyEvent *keyEvent = static_cast <QKeyEvent *>(event);
150-         if  (keyEvent->key () == Qt::Key_Comma)
151-         {
152-             //  Translate a comma into a period
153-             QKeyEvent periodKeyEvent (event->type (), Qt::Key_Period, keyEvent->modifiers (), " ."  , keyEvent->isAutoRepeat (), keyEvent->count ());
154-             QApplication::sendEvent (object, &periodKeyEvent);
155-             return  true ;
156-         }
157-     }
158246    return  QWidget::eventFilter (object, event);
159247}
160248
@@ -167,18 +255,12 @@ QWidget *BitcoinAmountField::setupTabChain(QWidget *prev)
167255
168256qint64 BitcoinAmountField::value (bool  *valid_out) const 
169257{
170-     qint64 val_out = 0 ;
171-     bool  valid = BitcoinUnits::parse (currentUnit, text (), &val_out);
172-     if  (valid_out)
173-     {
174-         *valid_out = valid;
175-     }
176-     return  val_out;
258+     return  amount->value (valid_out);
177259}
178260
179261void  BitcoinAmountField::setValue (qint64 value)
180262{
181-     setText ( BitcoinUnits::format (currentUnit,  value) );
263+     amount-> setValue ( value);
182264}
183265
184266void  BitcoinAmountField::setReadOnly (bool  fReadOnly )
@@ -195,28 +277,7 @@ void BitcoinAmountField::unitChanged(int idx)
195277    //  Determine new unit ID
196278    int  newUnit = unit->itemData (idx, BitcoinUnits::UnitRole).toInt ();
197279
198-     //  Parse current value and convert to new unit
199-     bool  valid = false ;
200-     qint64 currentValue = value (&valid);
201- 
202-     currentUnit = newUnit;
203- 
204-     //  Set max length after retrieving the value, to prevent truncation
205-     amount->setDecimals (BitcoinUnits::decimals (currentUnit));
206-     amount->setMaximum (qPow (10 , BitcoinUnits::amountDigits (currentUnit)) - qPow (10 , -amount->decimals ()));
207-     amount->setSingleStep ((double )nSingleStep / (double )BitcoinUnits::factor (currentUnit));
208- 
209-     if  (valid)
210-     {
211-         //  If value was valid, re-place it in the widget with the new unit
212-         setValue (currentValue);
213-     }
214-     else 
215-     {
216-         //  If current value is invalid, just clear field
217-         setText (" "  );
218-     }
219-     setValid (true );
280+     amount->setDisplayUnit (newUnit);
220281}
221282
222283void  BitcoinAmountField::setDisplayUnit (int  newUnit)
@@ -226,6 +287,5 @@ void BitcoinAmountField::setDisplayUnit(int newUnit)
226287
227288void  BitcoinAmountField::setSingleStep (qint64 step)
228289{
229-     nSingleStep = step;
230-     unitChanged (unit->currentIndex ());
290+     amount->setSingleStep (step);
231291}
0 commit comments