ctkDoubleSpinBox.cpp 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // CTK includes
  15. #include "ctkDoubleSpinBox_p.h"
  16. #include "ctkUtils.h"
  17. #include "ctkValueProxy.h"
  18. #include "ctkPimpl.h"
  19. // Qt includes
  20. #include <QApplication>
  21. #include <QDebug>
  22. #include <QEvent>
  23. #include <QHBoxLayout>
  24. #include <QKeyEvent>
  25. #include <QLineEdit>
  26. #include <QShortcut>
  27. #include <QSizePolicy>
  28. #include <QStyle>
  29. #include <QStyleOptionSpinBox>
  30. #include <QVariant>
  31. //-----------------------------------------------------------------------------
  32. // ctkQDoubleSpinBox
  33. //----------------------------------------------------------------------------
  34. ctkQDoubleSpinBox::ctkQDoubleSpinBox(ctkDoubleSpinBoxPrivate* pimpl,
  35. QWidget* spinBoxParent)
  36. : QDoubleSpinBox(spinBoxParent)
  37. , d_ptr(pimpl)
  38. {
  39. this->InvertedControls = false;
  40. }
  41. //----------------------------------------------------------------------------
  42. QLineEdit* ctkQDoubleSpinBox::lineEdit()const
  43. {
  44. return this->QDoubleSpinBox::lineEdit();
  45. }
  46. //----------------------------------------------------------------------------
  47. void ctkQDoubleSpinBox::initStyleOptionSpinBox(QStyleOptionSpinBox* option)
  48. {
  49. this->initStyleOption(option);
  50. }
  51. //----------------------------------------------------------------------------
  52. void ctkQDoubleSpinBox::setInvertedControls(bool invertedControls)
  53. {
  54. this->InvertedControls = invertedControls;
  55. }
  56. //----------------------------------------------------------------------------
  57. bool ctkQDoubleSpinBox::invertedControls() const
  58. {
  59. return this->InvertedControls;
  60. }
  61. //----------------------------------------------------------------------------
  62. void ctkQDoubleSpinBox::stepBy(int steps)
  63. {
  64. if (this->InvertedControls)
  65. {
  66. steps = -steps;
  67. }
  68. this->Superclass::stepBy(steps);
  69. }
  70. //----------------------------------------------------------------------------
  71. QAbstractSpinBox::StepEnabled ctkQDoubleSpinBox::stepEnabled() const
  72. {
  73. if (!this->InvertedControls)
  74. {
  75. return this->Superclass::stepEnabled();
  76. }
  77. if (this->isReadOnly())
  78. {
  79. return StepNone;
  80. }
  81. if (this->wrapping())
  82. {
  83. return StepEnabled(StepUpEnabled | StepDownEnabled);
  84. }
  85. StepEnabled ret = StepNone;
  86. double value = this->value();
  87. if (value < this->maximum())
  88. {
  89. ret |= StepDownEnabled;
  90. }
  91. if (value > this->minimum())
  92. {
  93. ret |= StepUpEnabled;
  94. }
  95. return ret;
  96. }
  97. //-----------------------------------------------------------------------------
  98. double ctkQDoubleSpinBox::valueFromText(const QString &text) const
  99. {
  100. Q_D(const ctkDoubleSpinBox);
  101. QString copy = text;
  102. int pos = this->lineEdit()->cursorPosition();
  103. QValidator::State state = QValidator::Acceptable;
  104. int decimals = 0;
  105. double value = d->validateAndInterpret(copy, pos, state, decimals);
  106. return value;
  107. }
  108. //-----------------------------------------------------------------------------
  109. QString ctkQDoubleSpinBox::textFromValue(double value) const
  110. {
  111. Q_D(const ctkDoubleSpinBox);
  112. QString text = this->QDoubleSpinBox::textFromValue(value);
  113. if (text.isEmpty())
  114. {
  115. text = "0";
  116. }
  117. // If there is no decimal, it does not mean there won't be any.
  118. if (d->DOption & ctkDoubleSpinBox::DecimalPointAlwaysVisible &&
  119. text.indexOf(this->locale().decimalPoint()) == -1)
  120. {
  121. text += this->locale().decimalPoint();
  122. }
  123. return text;
  124. }
  125. //-----------------------------------------------------------------------------
  126. int ctkQDoubleSpinBox::decimalsFromText(const QString &text) const
  127. {
  128. Q_D(const ctkDoubleSpinBox);
  129. QString copy = text;
  130. int pos = this->lineEdit()->cursorPosition();
  131. int decimals = 0;
  132. QValidator::State state = QValidator::Acceptable;
  133. d->validateAndInterpret(copy, pos, state, decimals);
  134. return decimals;
  135. }
  136. //-----------------------------------------------------------------------------
  137. QValidator::State ctkQDoubleSpinBox::validate(QString &text, int &pos) const
  138. {
  139. Q_D(const ctkDoubleSpinBox);
  140. QValidator::State state = QValidator::Acceptable;
  141. int decimals = 0;
  142. d->validateAndInterpret(text, pos, state, decimals);
  143. return state;
  144. }
  145. //-----------------------------------------------------------------------------
  146. // ctkDoubleSpinBoxPrivate
  147. //-----------------------------------------------------------------------------
  148. ctkDoubleSpinBoxPrivate::ctkDoubleSpinBoxPrivate(ctkDoubleSpinBox& object)
  149. : q_ptr(&object)
  150. {
  151. qRegisterMetaType<ctkDoubleSpinBox::SetMode>("ctkDoubleSpinBox::SetMode");
  152. qRegisterMetaType<ctkDoubleSpinBox::DecimalsOptions>("ctkDoubleSpinBox::DecimalsOption");
  153. this->SpinBox = 0;
  154. this->Mode = ctkDoubleSpinBox::SetIfDifferent;
  155. this->DefaultDecimals = 2;
  156. // InsertDecimals is not a great default, but it is QDoubleSpinBox's default.
  157. this->DOption = ctkDoubleSpinBox::DecimalsByShortcuts
  158. | ctkDoubleSpinBox::InsertDecimals;
  159. this->InvertedControls = false;
  160. this->SizeHintPolicy = ctkDoubleSpinBox::SizeHintByMinMax;
  161. this->InputValue = 0.;
  162. this->InputRange[0] = 0.;
  163. this->InputRange[1] = 99.99;
  164. this->ForceInputValueUpdate = false;
  165. }
  166. //-----------------------------------------------------------------------------
  167. void ctkDoubleSpinBoxPrivate::init()
  168. {
  169. Q_Q(ctkDoubleSpinBox);
  170. this->SpinBox = new ctkQDoubleSpinBox(this, q);
  171. this->SpinBox->setInvertedControls(this->InvertedControls);
  172. // ctkQDoubleSpinBox needs to be first to receive textChanged() signals.
  173. QLineEdit* lineEdit = new QLineEdit(q);
  174. QObject::connect(lineEdit, SIGNAL(textChanged(QString)),
  175. this, SLOT(editorTextChanged(QString)));
  176. this->SpinBox->setLineEdit(lineEdit);
  177. lineEdit->setObjectName(QLatin1String("qt_spinbox_lineedit"));
  178. this->InputValue = this->SpinBox->value();
  179. this->InputRange[0] = this->SpinBox->minimum();
  180. this->InputRange[1] = this->SpinBox->maximum();
  181. QObject::connect(this->SpinBox, SIGNAL(valueChanged(double)),
  182. this, SLOT(onValueChanged()));
  183. QObject::connect(this->SpinBox, SIGNAL(editingFinished()),
  184. q, SIGNAL(editingFinished()));
  185. QHBoxLayout* l = new QHBoxLayout(q);
  186. l->addWidget(this->SpinBox);
  187. l->setContentsMargins(0,0,0,0);
  188. q->setLayout(l);
  189. q->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,
  190. QSizePolicy::Fixed, QSizePolicy::ButtonBox));
  191. this->SpinBox->installEventFilter(q);
  192. }
  193. //-----------------------------------------------------------------------------
  194. bool ctkDoubleSpinBoxPrivate::compare(double x1, double x2) const
  195. {
  196. Q_Q(const ctkDoubleSpinBox);
  197. return q->round(x1) == q->round(x2);
  198. }
  199. //-----------------------------------------------------------------------------
  200. double ctkDoubleSpinBoxPrivate::round(double value, int decimals) const
  201. {
  202. return QString::number(value, 'f', decimals).toDouble();
  203. }
  204. //-----------------------------------------------------------------------------
  205. QString ctkDoubleSpinBoxPrivate::stripped(const QString& text, int* pos) const
  206. {
  207. Q_Q(const ctkDoubleSpinBox);
  208. QString strip(text);
  209. if (strip.startsWith(q->prefix()))
  210. {
  211. strip.remove(0, q->prefix().size());
  212. }
  213. if (strip.endsWith(q->suffix()))
  214. {
  215. strip.chop(q->suffix().size());
  216. }
  217. strip = strip.trimmed();
  218. if (pos)
  219. {
  220. int stripInText = text.indexOf(strip);
  221. *pos = qBound(0, *pos - stripInText, strip.size());
  222. }
  223. return strip;
  224. }
  225. //-----------------------------------------------------------------------------
  226. int ctkDoubleSpinBoxPrivate::boundDecimals(int dec)const
  227. {
  228. Q_Q(const ctkDoubleSpinBox);
  229. if (dec == -1)
  230. {
  231. return q->decimals();
  232. }
  233. int min = (this->DOption & ctkDoubleSpinBox::DecimalsAsMin) ?
  234. this->DefaultDecimals : 0;
  235. int max = (this->DOption & ctkDoubleSpinBox::DecimalsAsMax) ?
  236. this->DefaultDecimals : 323; // see QDoubleSpinBox::decimals doc
  237. return qBound(min, dec, max);
  238. }
  239. //-----------------------------------------------------------------------------
  240. int ctkDoubleSpinBoxPrivate::decimalsForValue(double value) const
  241. {
  242. int decimals = this->DefaultDecimals;
  243. if (this->DOption & ctkDoubleSpinBox::DecimalsByValue)
  244. {
  245. decimals = ctk::significantDecimals(value, decimals);
  246. }
  247. return this->boundDecimals(decimals);
  248. }
  249. //-----------------------------------------------------------------------------
  250. void ctkDoubleSpinBoxPrivate::setValue(double value, int dec)
  251. {
  252. Q_Q(ctkDoubleSpinBox);
  253. dec = this->boundDecimals(dec);
  254. const bool changeDecimals = dec != q->decimals();
  255. if (changeDecimals)
  256. {
  257. // don't fire valueChanged signal because we will change the value
  258. // right after anyway.
  259. const bool blockValueChangedSignal = (this->round(this->SpinBox->value(), dec) != value);
  260. bool wasBlocked = false;
  261. if (blockValueChangedSignal)
  262. {
  263. wasBlocked = this->SpinBox->blockSignals(true);
  264. }
  265. // don't fire decimalsChanged signal yet, wait for the value to be
  266. // up-to-date.
  267. this->SpinBox->setDecimals(dec);
  268. if (blockValueChangedSignal)
  269. {
  270. this->SpinBox->blockSignals(wasBlocked);
  271. }
  272. }
  273. this->SpinBox->setValue(value); // re-do the text (calls textFromValue())
  274. if (changeDecimals)
  275. {
  276. emit q->decimalsChanged(dec);
  277. }
  278. if (this->SizeHintPolicy == ctkDoubleSpinBox::SizeHintByValue)
  279. {
  280. this->CachedSizeHint = QSize();
  281. this->CachedMinimumSizeHint = QSize();
  282. q->updateGeometry();
  283. }
  284. }
  285. //-----------------------------------------------------------------------------
  286. void ctkDoubleSpinBoxPrivate::setDecimals(int dec)
  287. {
  288. Q_Q(ctkDoubleSpinBox);
  289. dec = this->boundDecimals(dec);
  290. this->SpinBox->setDecimals(dec);
  291. emit q->decimalsChanged(dec);
  292. }
  293. //-----------------------------------------------------------------------------
  294. void ctkDoubleSpinBoxPrivate::editorTextChanged(const QString& text)
  295. {
  296. if (this->SpinBox->keyboardTracking())
  297. {
  298. QString tmp = text;
  299. int pos = this->SpinBox->lineEdit()->cursorPosition();
  300. QValidator::State state = QValidator::Invalid;
  301. int decimals = 0;
  302. this->validateAndInterpret(tmp, pos, state, decimals);
  303. if (state == QValidator::Acceptable)
  304. {
  305. double newValue = this->SpinBox->valueFromText(tmp);
  306. int decimals = this->boundDecimals(this->SpinBox->decimalsFromText(tmp));
  307. bool changeDecimals = this->DOption & ctkDoubleSpinBox::DecimalsByKey &&
  308. decimals != this->SpinBox->decimals();
  309. if (changeDecimals)
  310. {
  311. this->ForceInputValueUpdate = true;
  312. this->setValue(newValue, decimals);
  313. this->ForceInputValueUpdate = false;
  314. }
  315. // else, let QDoubleSpinBox process the validation.
  316. }
  317. }
  318. }
  319. //-----------------------------------------------------------------------------
  320. double ctkDoubleSpinBoxPrivate
  321. ::validateAndInterpret(QString &input, int &pos,
  322. QValidator::State &state, int &decimals) const
  323. {
  324. Q_Q(const ctkDoubleSpinBox);
  325. if (this->CachedText == input)
  326. {
  327. state = this->CachedState;
  328. decimals = this->CachedDecimals;
  329. return this->CachedValue;
  330. }
  331. const double max = this->SpinBox->maximum();
  332. const double min = this->SpinBox->minimum();
  333. int posInValue = pos;
  334. QString text = this->stripped(input, &posInValue);
  335. // posInValue can change, track the offset.
  336. const int oldPosInValue = posInValue;
  337. state = QValidator::Acceptable;
  338. decimals = 0;
  339. double value = min;
  340. const int dec = text.indexOf(q->locale().decimalPoint());
  341. bool ok = false;
  342. value = q->locale().toDouble(text, &ok);
  343. // could be in an intermediate state
  344. if (!ok && state == QValidator::Acceptable)
  345. {
  346. if (text.isEmpty() ||
  347. text == "." ||
  348. text == "-" ||
  349. text == "+" ||
  350. text == "-." ||
  351. text == "+.")
  352. {
  353. state = QValidator::Intermediate;
  354. }
  355. }
  356. // could be because of group separators:
  357. if (!ok && state == QValidator::Acceptable)
  358. {
  359. if (q->locale().groupSeparator().isPrint())
  360. {
  361. int start = (dec == -1 ? text.size() : dec)- 1;
  362. int lastGroupSeparator = start;
  363. for (int digit = start; digit >= 0; --digit)
  364. {
  365. if (text.at(digit) == q->locale().groupSeparator())
  366. {
  367. if (digit != lastGroupSeparator - 3)
  368. {
  369. state = QValidator::Invalid;
  370. break;
  371. }
  372. text.remove(digit, 1);
  373. lastGroupSeparator = digit;
  374. }
  375. }
  376. }
  377. // try again without the group separators
  378. value = q->locale().toDouble(text, &ok);
  379. }
  380. // test the decimalPoint
  381. if (!ok && state == QValidator::Acceptable)
  382. {
  383. // duplicate decimal points probably means the user typed another decimal points,
  384. // move the cursor pos to the right then
  385. if (dec + 1 < text.size() &&
  386. text.at(dec + 1) == q->locale().decimalPoint() &&
  387. posInValue == dec + 1)
  388. {
  389. text.remove(dec + 1, 1);
  390. value = q->locale().toDouble(text, &ok);
  391. }
  392. }
  393. if (ok && state != QValidator::Invalid)
  394. {
  395. if (dec != -1)
  396. {
  397. decimals = text.size() - (dec + 1);
  398. if (decimals > q->decimals())
  399. {
  400. // With ReplaceDecimals on, key strokes replace decimal digits
  401. if (posInValue > dec && posInValue < text.size())
  402. {
  403. const int extraDecimals = decimals - q->decimals();
  404. if (this->DOption & ctkDoubleSpinBox::ReplaceDecimals)
  405. {
  406. text.remove(posInValue, extraDecimals);
  407. decimals = q->decimals();
  408. value = q->locale().toDouble(text, &ok);
  409. }
  410. else if (!(this->DOption & ctkDoubleSpinBox::InsertDecimals))
  411. {
  412. text.remove(text.size() - extraDecimals, extraDecimals);
  413. decimals = q->decimals();
  414. value = q->locale().toDouble(text, &ok);
  415. }
  416. }
  417. }
  418. // When DecimalsByKey is set, it is possible to extend the number of decimals
  419. if (decimals > q->decimals() &&
  420. !(this->DOption & ctkDoubleSpinBox::DecimalsByKey) )
  421. {
  422. state = QValidator::Invalid;
  423. }
  424. }
  425. }
  426. if (state == QValidator::Acceptable)
  427. {
  428. if (!ok)
  429. {
  430. state = QValidator::Invalid;
  431. }
  432. else if (value >= min && value <= max)
  433. {
  434. state = QValidator::Acceptable;
  435. }
  436. else if (max == min)
  437. { // when max and min is the same the only non-Invalid input is max (or min)
  438. state = QValidator::Invalid;
  439. }
  440. else if ((value >= 0 && value > max) || (value < 0 && value < min))
  441. {
  442. state = QValidator::Invalid;
  443. }
  444. else
  445. {
  446. state = QValidator::Intermediate;
  447. }
  448. }
  449. if (state != QValidator::Acceptable)
  450. {
  451. value = max > 0 ? min : max;
  452. }
  453. pos += posInValue - oldPosInValue;
  454. input = q->prefix() + text + q->suffix();
  455. this->CachedText = input;
  456. this->CachedState = state;
  457. this->CachedValue = value;
  458. this->CachedDecimals = decimals;
  459. return value;
  460. }
  461. //-----------------------------------------------------------------------------
  462. void ctkDoubleSpinBoxPrivate::onValueChanged()
  463. {
  464. Q_Q(ctkDoubleSpinBox);
  465. double newValue = this->SpinBox->value();
  466. double oldValue = q->value();
  467. if (this->Proxy)
  468. {
  469. oldValue = this->Proxy.data()->proxyValueFromValue(oldValue);
  470. }
  471. // Don't trigger value changed signal if the difference only happened on the
  472. // precision.
  473. if (this->compare(oldValue, newValue) && !this->ForceInputValueUpdate)
  474. {
  475. return;
  476. }
  477. // Force it only once (when the user typed a new number that could have change
  478. // the number of decimals which could have make the compare test always pass.
  479. this->ForceInputValueUpdate = false;
  480. double minimum = q->minimum();
  481. double maximum = q->maximum();
  482. if (this->Proxy)
  483. {
  484. minimum = this->Proxy.data()->proxyValueFromValue(minimum);
  485. maximum = this->Proxy.data()->proxyValueFromValue(maximum);
  486. }
  487. // Special case to return max precision
  488. if (this->compare(minimum, newValue))
  489. {
  490. newValue = q->minimum();
  491. }
  492. else if (this->compare(maximum, newValue))
  493. {
  494. newValue = q->maximum();
  495. }
  496. else if (this->Proxy)
  497. {
  498. newValue = this->Proxy.data()->valueFromProxyValue(newValue);
  499. }
  500. this->InputValue = newValue;
  501. emit q->valueChanged(newValue);
  502. // \tbd The string might not make much sense when using proxies.
  503. emit q->valueChanged(
  504. QString::number(newValue, 'f', this->SpinBox->decimals()));
  505. }
  506. //-----------------------------------------------------------------------------
  507. void ctkDoubleSpinBoxPrivate::onValueProxyAboutToBeModified()
  508. {
  509. }
  510. //-----------------------------------------------------------------------------
  511. void ctkDoubleSpinBoxPrivate::onValueProxyModified()
  512. {
  513. Q_Q(ctkDoubleSpinBox);
  514. int oldDecimals = q->decimals();
  515. double oldValue = this->InputValue;
  516. ctkDoubleSpinBox::SetMode oldSetMode = this->Mode;
  517. // Only the display is changed, not the programatic value, no need to trigger
  518. // signals
  519. bool wasBlocking = q->blockSignals(true);
  520. // Enforce a refresh. Signals are blocked so it should not trigger unwanted
  521. // signals
  522. this->Mode = ctkDoubleSpinBox::SetAlways;
  523. q->setRange(this->InputRange[0], this->InputRange[1]);
  524. q->setValue(oldValue);
  525. this->Mode = oldSetMode;
  526. q->blockSignals(wasBlocking);
  527. // Decimals might change when value proxy is modified.
  528. if (oldDecimals != q->decimals())
  529. {
  530. emit q->decimalsChanged(q->decimals());
  531. }
  532. }
  533. //-----------------------------------------------------------------------------
  534. // ctkDoubleSpinBox
  535. //-----------------------------------------------------------------------------
  536. ctkDoubleSpinBox::ctkDoubleSpinBox(QWidget* newParent)
  537. : QWidget(newParent)
  538. , d_ptr(new ctkDoubleSpinBoxPrivate(*this))
  539. {
  540. Q_D(ctkDoubleSpinBox);
  541. d->init();
  542. }
  543. //-----------------------------------------------------------------------------
  544. ctkDoubleSpinBox::ctkDoubleSpinBox(ctkDoubleSpinBox::SetMode mode, QWidget* newParent)
  545. : QWidget(newParent)
  546. , d_ptr(new ctkDoubleSpinBoxPrivate(*this))
  547. {
  548. Q_D(ctkDoubleSpinBox);
  549. d->init();
  550. this->setSetMode(mode);
  551. }
  552. //-----------------------------------------------------------------------------
  553. ctkDoubleSpinBox::~ctkDoubleSpinBox()
  554. {
  555. }
  556. //-----------------------------------------------------------------------------
  557. double ctkDoubleSpinBox::value() const
  558. {
  559. Q_D(const ctkDoubleSpinBox);
  560. return d->InputValue;
  561. }
  562. //-----------------------------------------------------------------------------
  563. double ctkDoubleSpinBox::displayedValue() const
  564. {
  565. Q_D(const ctkDoubleSpinBox);
  566. return d->SpinBox->value();
  567. }
  568. //----------------------------------------------------------------------------
  569. void ctkDoubleSpinBox::setDisplayedValue(double value)
  570. {
  571. Q_D(ctkDoubleSpinBox);
  572. d->SpinBox->setValue(value);
  573. }
  574. //-----------------------------------------------------------------------------
  575. QString ctkDoubleSpinBox::text() const
  576. {
  577. Q_D(const ctkDoubleSpinBox);
  578. return d->SpinBox->text();
  579. }
  580. //-----------------------------------------------------------------------------
  581. QString ctkDoubleSpinBox::cleanText() const
  582. {
  583. Q_D(const ctkDoubleSpinBox);
  584. return d->SpinBox->cleanText();
  585. }
  586. //-----------------------------------------------------------------------------
  587. Qt::Alignment ctkDoubleSpinBox::alignment() const
  588. {
  589. Q_D(const ctkDoubleSpinBox);
  590. return d->SpinBox->alignment();
  591. }
  592. //-----------------------------------------------------------------------------
  593. void ctkDoubleSpinBox::setAlignment(Qt::Alignment flag)
  594. {
  595. Q_D(const ctkDoubleSpinBox);
  596. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent && flag == d->SpinBox->alignment())
  597. {
  598. return;
  599. }
  600. d->SpinBox->setAlignment(flag);
  601. }
  602. //-----------------------------------------------------------------------------
  603. void ctkDoubleSpinBox::setFrame(bool frame)
  604. {
  605. Q_D(const ctkDoubleSpinBox);
  606. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent && frame == d->SpinBox->hasFrame())
  607. {
  608. return;
  609. }
  610. d->SpinBox->setFrame(frame);
  611. }
  612. //-----------------------------------------------------------------------------
  613. bool ctkDoubleSpinBox::hasFrame() const
  614. {
  615. Q_D(const ctkDoubleSpinBox);
  616. return d->SpinBox->hasFrame();
  617. }
  618. //-----------------------------------------------------------------------------
  619. QString ctkDoubleSpinBox::prefix() const
  620. {
  621. Q_D(const ctkDoubleSpinBox);
  622. return d->SpinBox->prefix();
  623. }
  624. //-----------------------------------------------------------------------------
  625. void ctkDoubleSpinBox::setPrefix(const QString &prefix)
  626. {
  627. Q_D(const ctkDoubleSpinBox);
  628. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent && prefix == d->SpinBox->prefix())
  629. {
  630. return;
  631. }
  632. #if QT_VERSION < 0x040800
  633. /// Setting the prefix doesn't recompute the sizehint, do it manually here:
  634. /// See: http://bugreports.qt.nokia.com/browse/QTBUG-9530
  635. d->SpinBox->setRange(d->SpinBox->minimum(), d->SpinBox->maximum());
  636. #endif
  637. d->SpinBox->setPrefix(prefix);
  638. }
  639. //-----------------------------------------------------------------------------
  640. QString ctkDoubleSpinBox::suffix() const
  641. {
  642. Q_D(const ctkDoubleSpinBox);
  643. return d->SpinBox->suffix();
  644. }
  645. //-----------------------------------------------------------------------------
  646. void ctkDoubleSpinBox::setSuffix(const QString &suffix)
  647. {
  648. Q_D(const ctkDoubleSpinBox);
  649. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent && suffix == d->SpinBox->suffix())
  650. {
  651. return;
  652. }
  653. #if QT_VERSION < 0x040800
  654. /// Setting the suffix doesn't recompute the sizehint, do it manually here:
  655. /// See: http://bugreports.qt.nokia.com/browse/QTBUG-9530
  656. d->SpinBox->setRange(d->SpinBox->minimum(), d->SpinBox->maximum());
  657. #endif
  658. d->SpinBox->setSuffix(suffix);
  659. }
  660. //-----------------------------------------------------------------------------
  661. double ctkDoubleSpinBox::singleStep() const
  662. {
  663. Q_D(const ctkDoubleSpinBox);
  664. double step = d->SpinBox->singleStep();
  665. return step;
  666. }
  667. //-----------------------------------------------------------------------------
  668. void ctkDoubleSpinBox::setSingleStep(double newStep)
  669. {
  670. Q_D(ctkDoubleSpinBox);
  671. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent
  672. && d->compare(newStep, this->singleStep()))
  673. {
  674. return;
  675. }
  676. d->SpinBox->setSingleStep(newStep);
  677. }
  678. //-----------------------------------------------------------------------------
  679. double ctkDoubleSpinBox::minimum() const
  680. {
  681. Q_D(const ctkDoubleSpinBox);
  682. return d->InputRange[0];
  683. }
  684. //-----------------------------------------------------------------------------
  685. void ctkDoubleSpinBox::setMinimum(double newMin)
  686. {
  687. this->setRange(newMin, qMax(newMin, this->maximum()));
  688. }
  689. //-----------------------------------------------------------------------------
  690. double ctkDoubleSpinBox::maximum() const
  691. {
  692. Q_D(const ctkDoubleSpinBox);
  693. return d->InputRange[1];
  694. }
  695. //-----------------------------------------------------------------------------
  696. void ctkDoubleSpinBox::setMaximum(double newMax)
  697. {
  698. this->setRange(qMin(newMax, this->minimum()), newMax);
  699. }
  700. //-----------------------------------------------------------------------------
  701. void ctkDoubleSpinBox::setRange(double newMin, double newMax)
  702. {
  703. Q_D(ctkDoubleSpinBox);
  704. if (newMin > newMax)
  705. {
  706. qSwap(newMin, newMax);
  707. }
  708. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent
  709. && newMin == d->InputRange[0]
  710. && newMax == d->InputRange[1])
  711. {
  712. return;
  713. }
  714. d->InputRange[0] = newMin;
  715. d->InputRange[1] = newMax;
  716. if (d->Proxy)
  717. {
  718. newMin = d->Proxy.data()->proxyValueFromValue(newMin);
  719. newMax = d->Proxy.data()->proxyValueFromValue(newMax);
  720. if (newMin > newMax)
  721. {
  722. qSwap(newMin, newMax);
  723. }
  724. }
  725. d->SpinBox->setRange(newMin, newMax);
  726. }
  727. //-----------------------------------------------------------------------------
  728. int ctkDoubleSpinBox::decimals() const
  729. {
  730. Q_D(const ctkDoubleSpinBox);
  731. return d->SpinBox->decimals();
  732. }
  733. //-----------------------------------------------------------------------------
  734. void ctkDoubleSpinBox::setDecimals(int dec)
  735. {
  736. Q_D(ctkDoubleSpinBox);
  737. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent
  738. && dec == this->decimals()
  739. && dec == d->DefaultDecimals)
  740. {
  741. return;
  742. }
  743. d->DefaultDecimals = dec;
  744. // The number of decimals may or may not depend on the value. Recompute the
  745. // new number of decimals.
  746. double currentValue = this->value();
  747. if (d->Proxy)
  748. {
  749. currentValue = d->Proxy.data()->proxyValueFromValue(currentValue);
  750. }
  751. int newDecimals = d->decimalsForValue(currentValue);
  752. d->setValue(currentValue, newDecimals);
  753. }
  754. //-----------------------------------------------------------------------------
  755. double ctkDoubleSpinBox::round(double value) const
  756. {
  757. Q_D(const ctkDoubleSpinBox);
  758. return QString::number(value, 'f', d->SpinBox->decimals()).toDouble();
  759. }
  760. //-----------------------------------------------------------------------------
  761. QDoubleSpinBox* ctkDoubleSpinBox::spinBox() const
  762. {
  763. Q_D(const ctkDoubleSpinBox);
  764. return d->SpinBox;
  765. }
  766. //-----------------------------------------------------------------------------
  767. QLineEdit* ctkDoubleSpinBox::lineEdit() const
  768. {
  769. Q_D(const ctkDoubleSpinBox);
  770. return d->SpinBox->lineEdit();
  771. }
  772. //-----------------------------------------------------------------------------
  773. void ctkDoubleSpinBox::setValue(double value)
  774. {
  775. Q_D(ctkDoubleSpinBox);
  776. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent)
  777. {
  778. this->setValueIfDifferent(value);
  779. }
  780. else
  781. {
  782. this->setValueAlways(value);
  783. }
  784. }
  785. //-----------------------------------------------------------------------------
  786. void ctkDoubleSpinBox::setValueIfDifferent(double newValue)
  787. {
  788. Q_D(ctkDoubleSpinBox);
  789. if (newValue == d->InputValue)
  790. {
  791. return;
  792. }
  793. this->setValueAlways(newValue);
  794. }
  795. //-----------------------------------------------------------------------------
  796. void ctkDoubleSpinBox::setValueAlways(double newValue)
  797. {
  798. Q_D(ctkDoubleSpinBox);
  799. newValue = qBound(d->InputRange[0], newValue, d->InputRange[1]);
  800. const bool valueModified = d->InputValue != newValue;
  801. d->InputValue = newValue;
  802. double newValueToDisplay = newValue;
  803. if (d->Proxy)
  804. {
  805. newValueToDisplay = d->Proxy.data()->proxyValueFromValue(newValueToDisplay);
  806. }
  807. const int decimals = d->decimalsForValue(newValueToDisplay);
  808. // setValueAlways already fires the valueChanged() signal if needed, same
  809. // thing for d->setValue() with decimalsChanged(). There is no need to
  810. // propagate the valueChanged/decimalsChanged signals from the spinbox.
  811. // Alternatively we could also have set a flag that prevents onValueChanged()
  812. // to trigger the valueChanged() signal.
  813. //bool wasBlocking = d->SpinBox->blockSignals(true);
  814. d->setValue(newValueToDisplay, decimals);
  815. //d->SpinBox->blockSignals(wasBlocking);
  816. const bool signalsEmitted = (newValue != d->InputValue);
  817. // Fire the valueChanged signal only if d->setValue() did not fire it
  818. // already..
  819. if (valueModified && !signalsEmitted)
  820. {
  821. emit valueChanged(d->InputValue);
  822. emit valueChanged(QString::number(d->InputValue, 'f', d->SpinBox->decimals()));
  823. }
  824. }
  825. //-----------------------------------------------------------------------------
  826. void ctkDoubleSpinBox::stepUp()
  827. {
  828. Q_D(const ctkDoubleSpinBox);
  829. d->SpinBox->stepUp();
  830. }
  831. //-----------------------------------------------------------------------------
  832. void ctkDoubleSpinBox::stepDown()
  833. {
  834. Q_D(const ctkDoubleSpinBox);
  835. d->SpinBox->stepDown();
  836. }
  837. //-----------------------------------------------------------------------------
  838. ctkDoubleSpinBox::SetMode ctkDoubleSpinBox::setMode() const
  839. {
  840. Q_D(const ctkDoubleSpinBox);
  841. return d->Mode;
  842. }
  843. //-----------------------------------------------------------------------------
  844. void ctkDoubleSpinBox::setSetMode(ctkDoubleSpinBox::SetMode newMode)
  845. {
  846. Q_D(ctkDoubleSpinBox);
  847. d->Mode = newMode;
  848. }
  849. //-----------------------------------------------------------------------------
  850. ctkDoubleSpinBox::DecimalsOptions ctkDoubleSpinBox::decimalsOption()
  851. {
  852. Q_D(const ctkDoubleSpinBox);
  853. return d->DOption;
  854. }
  855. //-----------------------------------------------------------------------------
  856. void ctkDoubleSpinBox::setDecimalsOption(ctkDoubleSpinBox::DecimalsOptions option)
  857. {
  858. Q_D(ctkDoubleSpinBox);
  859. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent && option == d->DOption)
  860. {
  861. return;
  862. }
  863. d->DOption = option;
  864. this->setValueAlways(this->value());
  865. }
  866. //----------------------------------------------------------------------------
  867. void ctkDoubleSpinBox::setInvertedControls(bool invertedControls)
  868. {
  869. Q_D(ctkDoubleSpinBox);
  870. d->InvertedControls = invertedControls;
  871. d->SpinBox->setInvertedControls(d->InvertedControls);
  872. }
  873. //----------------------------------------------------------------------------
  874. bool ctkDoubleSpinBox::invertedControls() const
  875. {
  876. Q_D(const ctkDoubleSpinBox);
  877. return d->InvertedControls;
  878. }
  879. //----------------------------------------------------------------------------
  880. void ctkDoubleSpinBox
  881. ::setSizeHintPolicy(ctkDoubleSpinBox::SizeHintPolicy newSizeHintPolicy)
  882. {
  883. Q_D(ctkDoubleSpinBox);
  884. if (d->Mode == ctkDoubleSpinBox::SetIfDifferent
  885. && newSizeHintPolicy == d->SizeHintPolicy)
  886. {
  887. return;
  888. }
  889. d->SizeHintPolicy = newSizeHintPolicy;
  890. d->CachedSizeHint = QSize();
  891. d->CachedMinimumSizeHint = QSize();
  892. this->updateGeometry();
  893. }
  894. //----------------------------------------------------------------------------
  895. ctkDoubleSpinBox::SizeHintPolicy ctkDoubleSpinBox::sizeHintPolicy() const
  896. {
  897. Q_D(const ctkDoubleSpinBox);
  898. return d->SizeHintPolicy;
  899. }
  900. //----------------------------------------------------------------------------
  901. void ctkDoubleSpinBox::setValueProxy(ctkValueProxy* proxy)
  902. {
  903. Q_D(ctkDoubleSpinBox);
  904. if (proxy == d->Proxy.data())
  905. {
  906. return;
  907. }
  908. d->onValueProxyAboutToBeModified();
  909. if (d->Proxy)
  910. {
  911. disconnect(d->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
  912. d, SLOT(onValueProxyAboutToBeModified()));
  913. disconnect(d->Proxy.data(), SIGNAL(proxyModified()),
  914. d, SLOT(onValueProxyModified()));
  915. }
  916. d->Proxy = proxy;
  917. if (d->Proxy)
  918. {
  919. connect(d->Proxy.data(), SIGNAL(proxyAboutToBeModified()),
  920. d, SLOT(onValueProxyAboutToBeModified()));
  921. connect(d->Proxy.data(), SIGNAL(proxyModified()),
  922. d, SLOT(onValueProxyModified()));
  923. }
  924. d->onValueProxyModified();
  925. }
  926. //----------------------------------------------------------------------------
  927. ctkValueProxy* ctkDoubleSpinBox::valueProxy() const
  928. {
  929. Q_D(const ctkDoubleSpinBox);
  930. return d->Proxy.data();
  931. }
  932. //----------------------------------------------------------------------------
  933. QSize ctkDoubleSpinBox::sizeHint() const
  934. {
  935. Q_D(const ctkDoubleSpinBox);
  936. if (d->SizeHintPolicy == ctkDoubleSpinBox::SizeHintByMinMax)
  937. {
  938. return this->Superclass::sizeHint();
  939. }
  940. if (!d->CachedSizeHint.isEmpty())
  941. {
  942. return d->CachedSizeHint;
  943. }
  944. QSize newSizeHint;
  945. newSizeHint.setHeight(this->lineEdit()->sizeHint().height());
  946. QString extraString = " "; // give some room
  947. QString s = this->text() + extraString;
  948. s.truncate(18);
  949. int extraWidth = 2; // cursor width
  950. this->ensurePolished(); // ensure we are using the right font
  951. const QFontMetrics fm(this->fontMetrics());
  952. newSizeHint.setWidth(fm.width(s + extraString) + extraWidth);
  953. QStyleOptionSpinBox opt;
  954. d->SpinBox->initStyleOptionSpinBox(&opt);
  955. QSize extraSize(35, 6);
  956. opt.rect.setSize(newSizeHint + extraSize);
  957. extraSize += newSizeHint - this->style()->subControlRect(
  958. QStyle::CC_SpinBox, &opt,
  959. QStyle::SC_SpinBoxEditField, this).size();
  960. // Converging size hint...
  961. opt.rect.setSize(newSizeHint + extraSize);
  962. extraSize += newSizeHint - this->style()->subControlRect(
  963. QStyle::CC_SpinBox, &opt,
  964. QStyle::SC_SpinBoxEditField, this).size();
  965. newSizeHint += extraSize;
  966. opt.rect = this->rect();
  967. d->CachedSizeHint = this->style()->sizeFromContents(
  968. QStyle::CT_SpinBox, &opt, newSizeHint, this)
  969. .expandedTo(QApplication::globalStrut());
  970. return d->CachedSizeHint;
  971. }
  972. //----------------------------------------------------------------------------
  973. QSize ctkDoubleSpinBox::minimumSizeHint() const
  974. {
  975. Q_D(const ctkDoubleSpinBox);
  976. if (d->SizeHintPolicy == ctkDoubleSpinBox::SizeHintByMinMax)
  977. {
  978. // For some reasons, Superclass::minimumSizeHint() returns the spinbox
  979. // sizeHint()
  980. return this->spinBox()->minimumSizeHint();
  981. }
  982. if (!d->CachedMinimumSizeHint.isEmpty())
  983. {
  984. return d->CachedMinimumSizeHint;
  985. }
  986. QSize newSizeHint;
  987. newSizeHint.setHeight(this->lineEdit()->minimumSizeHint().height());
  988. QString extraString = " "; // give some room
  989. QString s = this->text() + extraString;
  990. s.truncate(18);
  991. int extraWidth = 2; // cursor width
  992. this->ensurePolished(); // ensure we are using the right font
  993. const QFontMetrics fm(this->fontMetrics());
  994. newSizeHint.setWidth(fm.width(s + extraString) + extraWidth);
  995. QStyleOptionSpinBox opt;
  996. d->SpinBox->initStyleOptionSpinBox(&opt);
  997. QSize extraSize(35, 6);
  998. opt.rect.setSize(newSizeHint + extraSize);
  999. extraSize += newSizeHint - this->style()->subControlRect(
  1000. QStyle::CC_SpinBox, &opt,
  1001. QStyle::SC_SpinBoxEditField, this).size();
  1002. // Converging size hint...
  1003. opt.rect.setSize(newSizeHint + extraSize);
  1004. extraSize += newSizeHint - this->style()->subControlRect(
  1005. QStyle::CC_SpinBox, &opt,
  1006. QStyle::SC_SpinBoxEditField, this).size();
  1007. newSizeHint += extraSize;
  1008. opt.rect = this->rect();
  1009. d->CachedMinimumSizeHint = this->style()->sizeFromContents(
  1010. QStyle::CT_SpinBox, &opt, newSizeHint, this)
  1011. .expandedTo(QApplication::globalStrut());
  1012. return d->CachedMinimumSizeHint;
  1013. }
  1014. //-----------------------------------------------------------------------------
  1015. void ctkDoubleSpinBox::keyPressEvent(QKeyEvent* event)
  1016. {
  1017. Q_D(ctkDoubleSpinBox);
  1018. const bool accept = this->eventFilter(d->SpinBox, event);
  1019. event->setAccepted(accept);
  1020. }
  1021. //-----------------------------------------------------------------------------
  1022. bool ctkDoubleSpinBox::eventFilter(QObject* obj, QEvent* event)
  1023. {
  1024. Q_D(ctkDoubleSpinBox);
  1025. if (d->DOption & ctkDoubleSpinBox::DecimalsByShortcuts &&
  1026. obj == d->SpinBox && event->type() == QEvent::KeyPress)
  1027. {
  1028. QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
  1029. Q_ASSERT(keyEvent);
  1030. int newDecimals = -1;
  1031. if (keyEvent->modifiers() & Qt::ControlModifier)
  1032. {
  1033. if (keyEvent->key() == Qt::Key_Plus
  1034. || keyEvent->key() == Qt::Key_Equal)
  1035. {
  1036. newDecimals = this->decimals() + 1;
  1037. }
  1038. else if (keyEvent->key() == Qt::Key_Minus)
  1039. {
  1040. newDecimals = this->decimals() - 1;
  1041. }
  1042. else if (keyEvent->key() == Qt::Key_0)
  1043. {
  1044. newDecimals = d->DefaultDecimals;
  1045. }
  1046. }
  1047. if (newDecimals != -1)
  1048. {
  1049. double currentValue = this->value();
  1050. if (d->Proxy)
  1051. {
  1052. currentValue = d->Proxy.data()->proxyValueFromValue(currentValue);
  1053. }
  1054. // increasing the number of decimals should restore lost precision
  1055. d->setValue(currentValue, newDecimals);
  1056. return true;
  1057. }
  1058. return QWidget::eventFilter(obj, event);
  1059. }
  1060. else
  1061. {
  1062. // pass the event on to the parent class
  1063. return QWidget::eventFilter(obj, event);
  1064. }
  1065. }