1 /*
2 SPDX-FileCopyrightText: 2001-2013 Evan Teran <evan.teran@gmail.com>
3 SPDX-FileCopyrightText: 1996-2000 Bernd Johannes Wuebben <wuebben@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "kcalcdisplay.h"
9
10 #include <QApplication>
11 #include <QClipboard>
12 #include <QMouseEvent>
13 #include <QPainter>
14 #include <QStyle>
15 #include <QStyleOption>
16 #include <QTimer>
17
18 #include <KNotification>
19
20 #include "kcalc_core.h"
21 #include "kcalc_settings.h"
22
23 //------------------------------------------------------------------------------
24 // Name: KCalcDisplay
25 // Desc: constructor
26 //------------------------------------------------------------------------------
KCalcDisplay(QWidget * parent)27 KCalcDisplay::KCalcDisplay(QWidget *parent)
28 : QFrame(parent)
29 , beep_(false)
30 , groupdigits_(true)
31 , twoscomplement_(true)
32 , button_(0)
33 , lit_(false)
34 , num_base_(NB_DECIMAL)
35 , precision_(9)
36 , fixed_precision_(-1)
37 , display_amount_(0)
38 , history_index_(0)
39 , selection_timer_(new QTimer(this))
40 {
41 setFocusPolicy(Qt::StrongFocus);
42
43 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
44
45 setBackgroundRole(QPalette::Base);
46 setForegroundRole(QPalette::Text);
47 setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // set in kalc.ui
48
49 KNumber::setDefaultFloatOutput(true);
50 KNumber::setDefaultFractionalInput(true);
51
52 connect(this, &KCalcDisplay::clicked, this, &KCalcDisplay::slotDisplaySelected);
53 connect(selection_timer_, &QTimer::timeout, this, &KCalcDisplay::slotSelectionTimedOut);
54
55 sendEvent(EventReset);
56 }
57
58 //------------------------------------------------------------------------------
59 // Name: ~KCalcDisplay
60 // Desc: destructor
61 //------------------------------------------------------------------------------
~KCalcDisplay()62 KCalcDisplay::~KCalcDisplay()
63 {
64 }
65
66 //------------------------------------------------------------------------------
67 // Name: changeSettings
68 // Desc:
69 //------------------------------------------------------------------------------
changeSettings()70 void KCalcDisplay::changeSettings()
71 {
72 QPalette pal = palette();
73
74 pal.setColor(QPalette::Text, KCalcSettings::foreColor());
75 pal.setColor(QPalette::Base, KCalcSettings::backColor());
76
77 setPalette(pal);
78
79 setFont(KCalcSettings::displayFont());
80
81 setPrecision(KCalcSettings::precision());
82
83 if (!KCalcSettings::fixed()) {
84 setFixedPrecision(-1);
85 } else {
86 setFixedPrecision(KCalcSettings::fixedPrecision());
87 }
88
89 setBeep(KCalcSettings::beep());
90 setGroupDigits(KCalcSettings::groupDigits());
91 setTwosComplement(KCalcSettings::twosComplement());
92 setBinaryGrouping(KCalcSettings::binaryGrouping());
93 setOctalGrouping(KCalcSettings::octalGrouping());
94 setHexadecimalGrouping(KCalcSettings::hexadecimalGrouping());
95 updateDisplay();
96 }
97
98 //------------------------------------------------------------------------------
99 // Name:
100 // Desc:
101 //------------------------------------------------------------------------------
updateFromCore(const CalcEngine & core,bool store_result_in_history)102 void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history)
103 {
104 bool tmp_error;
105 const KNumber &output = core.lastOutput(tmp_error);
106
107 #if 0
108 // TODO: do we really need explicit error tracking?
109 // isn't the type of the KNumber good enough?
110 // I think it is and that this error tracking is cruft
111 // left over from a LONG time ago...
112 if(output.type() == KNumber::TYPE_ERROR) {
113 #else
114 if (tmp_error) {
115 #endif
116 sendEvent(EventError);
117 }
118
119 if (setAmount(output) && store_result_in_history && (output != KNumber::Zero)) {
120 // add this latest value to our history
121 history_list_.insert(history_list_.begin(), output);
122 history_index_ = 0;
123 }
124 }
125
126 //------------------------------------------------------------------------------
127 // Name: enterDigit
128 // Desc:
129 //------------------------------------------------------------------------------
130 void KCalcDisplay::enterDigit(int data)
131 {
132 switch (data) {
133 case 0:
134 newCharacter(QLatin1Char('0'));
135 break;
136 case 1:
137 newCharacter(QLatin1Char('1'));
138 break;
139 case 2:
140 newCharacter(QLatin1Char('2'));
141 break;
142 case 3:
143 newCharacter(QLatin1Char('3'));
144 break;
145 case 4:
146 newCharacter(QLatin1Char('4'));
147 break;
148 case 5:
149 newCharacter(QLatin1Char('5'));
150 break;
151 case 6:
152 newCharacter(QLatin1Char('6'));
153 break;
154 case 7:
155 newCharacter(QLatin1Char('7'));
156 break;
157 case 8:
158 newCharacter(QLatin1Char('8'));
159 break;
160 case 9:
161 newCharacter(QLatin1Char('9'));
162 break;
163 case 0xa:
164 newCharacter(QLatin1Char('A'));
165 break;
166 case 0xb:
167 newCharacter(QLatin1Char('B'));
168 break;
169 case 0xc:
170 newCharacter(QLatin1Char('C'));
171 break;
172 case 0xd:
173 newCharacter(QLatin1Char('D'));
174 break;
175 case 0xe:
176 newCharacter(QLatin1Char('E'));
177 break;
178 case 0xf:
179 newCharacter(QLatin1Char('F'));
180 break;
181 default:
182 Q_ASSERT(0);
183 break;
184 }
185 }
186
187 //------------------------------------------------------------------------------
188 // Name: slotHistoryForward
189 // Desc:
190 //------------------------------------------------------------------------------
191 void KCalcDisplay::slotHistoryForward()
192 {
193 if (history_list_.empty()) {
194 return;
195 }
196
197 if (history_index_ <= 0) {
198 return;
199 }
200
201 history_index_--;
202 setAmount(history_list_[history_index_]);
203 }
204
205 //------------------------------------------------------------------------------
206 // Name: slotHistoryBack
207 // Desc:
208 //------------------------------------------------------------------------------
209 void KCalcDisplay::slotHistoryBack()
210 {
211 if (history_list_.empty()) {
212 return;
213 }
214
215 if (history_index_ >= history_list_.size()) {
216 return;
217 }
218
219 setAmount(history_list_[history_index_]);
220 history_index_++;
221 }
222
223 //------------------------------------------------------------------------------
224 // Name: sendEvent
225 // Desc:
226 //------------------------------------------------------------------------------
227 bool KCalcDisplay::sendEvent(Event event)
228 {
229 switch (event) {
230 case EventClear:
231 case EventReset:
232 display_amount_ = KNumber::Zero;
233 str_int_ = QStringLiteral("0");
234 str_int_exp_.clear();
235
236 eestate_ = false;
237 period_ = false;
238 neg_sign_ = false;
239
240 updateDisplay();
241
242 return true;
243
244 case EventChangeSign:
245 return changeSign();
246
247 case EventError:
248 updateDisplay();
249 return true;
250
251 default:
252 return false;
253 }
254 }
255
256 //------------------------------------------------------------------------------
257 // Name: slotCut
258 // Desc:
259 //------------------------------------------------------------------------------
260 void KCalcDisplay::slotCut()
261 {
262 slotCopy();
263 sendEvent(EventReset);
264 }
265
266 //------------------------------------------------------------------------------
267 // Name: slotCopy
268 // Desc:
269 //------------------------------------------------------------------------------
270 void KCalcDisplay::slotCopy()
271 {
272 QString txt = text_;
273
274 switch (num_base_) {
275 case NB_HEX:
276 txt.prepend(QLatin1String("0x"));
277 txt.remove(QLatin1Char(' '));
278 break;
279 case NB_BINARY:
280 txt.prepend(QLatin1String("0b"));
281 txt.remove(QLatin1Char(' '));
282 break;
283 case NB_OCTAL:
284 txt.prepend(QLatin1String("0"));
285 txt.remove(QLatin1Char(' '));
286 break;
287 case NB_DECIMAL:
288 txt.remove(QLocale().groupSeparator());
289 break;
290 }
291
292 QApplication::clipboard()->setText(txt, QClipboard::Clipboard);
293 QApplication::clipboard()->setText(txt, QClipboard::Selection);
294 }
295
296 //------------------------------------------------------------------------------
297 // Name: slotPaste
298 // Desc:
299 //------------------------------------------------------------------------------
300 void KCalcDisplay::slotPaste(bool bClipboard)
301 {
302 QString tmp_str = (QApplication::clipboard())->text(bClipboard ? QClipboard::Clipboard : QClipboard::Selection);
303
304 if (tmp_str.isNull()) {
305 if (beep_) {
306 KNotification::beep();
307 }
308 return;
309 }
310
311 NumBase tmp_num_base = num_base_;
312
313 // fix up string
314 tmp_str = tmp_str.trimmed();
315
316 if (groupdigits_) {
317 tmp_str.remove(QLocale().groupSeparator());
318 }
319
320 tmp_str = tmp_str.toLower();
321
322 // determine base
323 if (tmp_str.startsWith(QLatin1String("0x"))) {
324 tmp_num_base = NB_HEX;
325 tmp_str.remove(0, 2);
326 } else if (tmp_str.startsWith(QLatin1String("0b"))) {
327 tmp_num_base = NB_BINARY;
328 tmp_str.remove(0, 2);
329 } else if (tmp_str.startsWith(QLatin1Char('0'))) {
330 // we don't want this to trigger on "0.xxxxxx" cases
331 if (tmp_str.length() < 2 || QString(tmp_str[1]) != KNumber::decimalSeparator()) {
332 tmp_num_base = NB_OCTAL;
333 tmp_str.remove(0, 1);
334 }
335 }
336
337 // for locales where the groups separator is not a comma (,) but a non breaking space
338 // accept (and correct) both decimal separators (comma and dot) for convenience
339 if (KNumber::decimalSeparator() == QChar::fromLatin1(',')
340 && (QLocale().groupSeparator() != QChar::fromLatin1(',') && QLocale().groupSeparator() != QChar::fromLatin1('.'))
341 && tmp_str.count(QChar::fromLatin1('.')) == 1) {
342 tmp_str.replace(QChar::fromLatin1('.'), QChar::fromLatin1(','));
343 }
344
345 if (tmp_num_base != NB_DECIMAL) {
346 bool was_ok;
347 const qint64 tmp_result = tmp_str.toULongLong(&was_ok, tmp_num_base);
348
349 if (!was_ok) {
350 setAmount(KNumber::NaN);
351 if (beep_) {
352 KNotification::beep();
353 }
354 return;
355 }
356 setAmount(KNumber(tmp_result));
357 } else {
358 setAmount(KNumber(tmp_str));
359 if (beep_ && display_amount_ == KNumber::NaN) {
360 KNotification::beep();
361 }
362 }
363 }
364
365 //------------------------------------------------------------------------------
366 // Name: slotDisplaySelected
367 // Desc:
368 //------------------------------------------------------------------------------
369 void KCalcDisplay::slotDisplaySelected()
370 {
371 if (button_ == Qt::LeftButton) {
372 if (lit_) {
373 slotCopy();
374 selection_timer_->start(100);
375 } else {
376 selection_timer_->stop();
377 }
378
379 invertColors();
380 } else {
381 slotPaste(false); // Selection
382 }
383 }
384
385 //------------------------------------------------------------------------------
386 // Name: slotSelectionTimedOut
387 // Desc:
388 //------------------------------------------------------------------------------
389 void KCalcDisplay::slotSelectionTimedOut()
390 {
391 lit_ = false;
392 invertColors();
393 selection_timer_->stop();
394 }
395
396 //------------------------------------------------------------------------------
397 // Name: invertColors
398 // Desc:
399 //------------------------------------------------------------------------------
400 void KCalcDisplay::invertColors()
401 {
402 QPalette tmp_palette = palette();
403 tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text));
404 tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base));
405 setPalette(tmp_palette);
406 }
407
408 //------------------------------------------------------------------------------
409 // Name: mousePressEvent
410 // Desc:
411 //------------------------------------------------------------------------------
412 void KCalcDisplay::mousePressEvent(QMouseEvent *e)
413 {
414 if (e->button() == Qt::LeftButton) {
415 lit_ = !lit_;
416 button_ = Qt::LeftButton;
417 } else {
418 button_ = Qt::MiddleButton;
419 }
420
421 Q_EMIT clicked();
422 }
423
424 //------------------------------------------------------------------------------
425 // Name: setPrecision
426 // Desc:
427 //------------------------------------------------------------------------------
428 void KCalcDisplay::setPrecision(int precision)
429 {
430 precision_ = precision;
431 }
432
433 //------------------------------------------------------------------------------
434 // Name: setFixedPrecision
435 // Desc:
436 //------------------------------------------------------------------------------
437 void KCalcDisplay::setFixedPrecision(int precision)
438 {
439 if (fixed_precision_ > precision_) {
440 fixed_precision_ = -1;
441 } else {
442 fixed_precision_ = precision;
443 }
444 }
445
446 //------------------------------------------------------------------------------
447 // Name: setBeep
448 // Desc:
449 //------------------------------------------------------------------------------
450 void KCalcDisplay::setBeep(bool flag)
451 {
452 beep_ = flag;
453 }
454
455 //------------------------------------------------------------------------------
456 // Name: setGroupDigits
457 // Desc:
458 //------------------------------------------------------------------------------
459 void KCalcDisplay::setGroupDigits(bool flag)
460 {
461 groupdigits_ = flag;
462 }
463
464 //------------------------------------------------------------------------------
465 // Name: setTwosComplement
466 // Desc:
467 //------------------------------------------------------------------------------
468 void KCalcDisplay::setTwosComplement(bool flag)
469 {
470 twoscomplement_ = flag;
471 }
472
473 //------------------------------------------------------------------------------
474 // Name: setBinaryGrouping
475 // Desc:
476 //------------------------------------------------------------------------------
477 void KCalcDisplay::setBinaryGrouping(int digits)
478 {
479 binaryGrouping_ = digits;
480 }
481
482 //------------------------------------------------------------------------------
483 // Name: setOctalGrouping
484 // Desc:
485 //------------------------------------------------------------------------------
486 void KCalcDisplay::setOctalGrouping(int digits)
487 {
488 octalGrouping_ = digits;
489 }
490
491 //------------------------------------------------------------------------------
492 // Name: setHexadecimalGrouping
493 // Desc:
494 //------------------------------------------------------------------------------
495 void KCalcDisplay::setHexadecimalGrouping(int digits)
496 {
497 hexadecimalGrouping_ = digits;
498 }
499
500 //------------------------------------------------------------------------------
501 // Name: getAmount
502 // Desc:
503 //------------------------------------------------------------------------------
504 const KNumber &KCalcDisplay::getAmount() const
505 {
506 return display_amount_;
507 }
508
509 //------------------------------------------------------------------------------
510 // Name: setAmount
511 // Desc:
512 //------------------------------------------------------------------------------
513 bool KCalcDisplay::setAmount(const KNumber &new_amount)
514 {
515 QString display_str;
516
517 str_int_ = QStringLiteral("0");
518 str_int_exp_.clear();
519 period_ = false;
520 neg_sign_ = false;
521 eestate_ = false;
522
523 if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) {
524 display_amount_ = new_amount.integerPart();
525
526 if (twoscomplement_) {
527 // treat number as 64-bit unsigned
528 const quint64 tmp_workaround = display_amount_.toUint64();
529 display_str = QString::number(tmp_workaround, num_base_).toUpper();
530 } else {
531 // QString::number treats non-decimal as unsigned
532 qint64 tmp_workaround = display_amount_.toInt64();
533 const bool neg = tmp_workaround < 0;
534 if (neg) {
535 tmp_workaround = qAbs(tmp_workaround);
536 }
537
538 display_str = QString::number(tmp_workaround, num_base_).toUpper();
539 if (neg) {
540 display_str.prepend(QLocale().negativeSign());
541 }
542 }
543 } else {
544 // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR
545 display_amount_ = new_amount;
546 display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_);
547 }
548
549 setText(display_str);
550 Q_EMIT changedAmount(display_amount_);
551 return true;
552 }
553
554 //------------------------------------------------------------------------------
555 // Name: setText
556 // Desc:
557 //------------------------------------------------------------------------------
558 void KCalcDisplay::setText(const QString &string)
559 {
560 // note that "C" locale is being used internally
561 text_ = string;
562
563 // don't mess with special numbers
564 const bool special = (string.contains(QLatin1String("nan")) || string.contains(QLatin1String("inf")));
565
566 // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized
567 // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal.
568
569 if ((groupdigits_ || num_base_ == NB_DECIMAL) && !special) {
570 switch (num_base_) {
571 case NB_DECIMAL:
572 text_ = formatDecimalNumber(text_);
573 break;
574
575 case NB_BINARY:
576 text_ = groupDigits(text_, binaryGrouping_);
577 break;
578
579 case NB_OCTAL:
580 text_ = groupDigits(text_, octalGrouping_);
581 break;
582
583 case NB_HEX:
584 text_ = groupDigits(text_, hexadecimalGrouping_);
585 break;
586 }
587 } else if (special) {
588 #if 0
589 // TODO: enable this code, it replaces the "inf" with an actual infinity
590 // symbol, but what should be put into the clip board when they copy?
591 if(string.contains(QLatin1String("inf"))) {
592 text_.replace("inf", QChar(0x221e));
593 }
594 #endif
595 }
596
597 update();
598 Q_EMIT changedText(text_);
599 }
600
601 //------------------------------------------------------------------------------
602 // Name: formatDecimalNumber
603 // Desc: Convert decimal number to locale-dependent format.
604 // We cannot use QLocale::formatNumber(), because the
605 // precision is limited to "double".
606 //------------------------------------------------------------------------------
607 QString KCalcDisplay::formatDecimalNumber(QString string)
608 {
609 QLocale locale;
610
611 string.replace(QLatin1Char('.'), locale.decimalPoint());
612
613 if (groupdigits_ && !(locale.numberOptions() & QLocale::OmitGroupSeparator)) {
614 // find position after last digit
615 int pos = string.indexOf(locale.decimalPoint());
616 if (pos < 0) {
617 // do not group digits after the exponent part
618 const int expPos = string.indexOf(QLatin1Char('e'));
619 if (expPos > 0) {
620 pos = expPos;
621 } else {
622 pos = string.length();
623 }
624 }
625
626 // find first digit to not group leading spaces or signs
627 int firstDigitPos = 0;
628 for (int i = 0, total = string.length(); i < total; ++i) {
629 if (string.at(i).isDigit()) {
630 firstDigitPos = i;
631 break;
632 }
633 }
634
635 const QChar groupSeparator = locale.groupSeparator();
636 const int groupSize = 3;
637
638 string.reserve(string.length() + (pos - 1) / groupSize);
639 while ((pos -= groupSize) > firstDigitPos) {
640 string.insert(pos, groupSeparator);
641 }
642 }
643
644 string.replace(QLatin1Char('-'), locale.negativeSign());
645 string.replace(QLatin1Char('+'), locale.positiveSign());
646
647 unsigned short zero = locale.zeroDigit().unicode();
648 for (int i = 0; i < string.length(); ++i) {
649 const auto stringAtI = string.at(i);
650 if (stringAtI.isDigit()) {
651 string[i] = QChar(zero + stringAtI.digitValue());
652 }
653 }
654
655 return string;
656 }
657
658 //------------------------------------------------------------------------------
659 // Name: groupDigits
660 // Desc:
661 //------------------------------------------------------------------------------
662 QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits)
663 {
664 QString tmpDisplayString;
665 const int stringLength = displayString.length();
666
667 for (int i = stringLength; i > 0; i--) {
668 if (i % numDigits == 0 && i != stringLength) {
669 tmpDisplayString = tmpDisplayString + QLatin1Char(' ');
670 }
671
672 tmpDisplayString = tmpDisplayString + displayString[stringLength - i];
673 }
674
675 return tmpDisplayString;
676 }
677
678 //------------------------------------------------------------------------------
679 // Name: text
680 // Desc:
681 //------------------------------------------------------------------------------
682 QString KCalcDisplay::text() const
683 {
684 return text_;
685 }
686
687 //------------------------------------------------------------------------------
688 // Name: setBase
689 // Desc: change representation of display to new base (i.e. binary, decimal,
690 // octal, hexadecimal). The amount being displayed is changed to this
691 // base, but for now this amount can not be modified anymore (like
692 // being set with "setAmount"). Return value is the new base.
693 //------------------------------------------------------------------------------
694 int KCalcDisplay::setBase(NumBase new_base)
695 {
696 switch (new_base) {
697 case NB_HEX:
698 num_base_ = NB_HEX;
699 period_ = false;
700 break;
701 case NB_DECIMAL:
702 num_base_ = NB_DECIMAL;
703 break;
704 case NB_OCTAL:
705 num_base_ = NB_OCTAL;
706 period_ = false;
707 break;
708 case NB_BINARY:
709 num_base_ = NB_BINARY;
710 period_ = false;
711 break;
712 default:
713 Q_ASSERT(0);
714 }
715
716 // reset amount
717 setAmount(display_amount_);
718 return num_base_;
719 }
720
721 //------------------------------------------------------------------------------
722 // Name: setStatusText
723 // Desc:
724 //------------------------------------------------------------------------------
725 void KCalcDisplay::setStatusText(int i, const QString &text)
726 {
727 if (i < NUM_STATUS_TEXT) {
728 str_status_[i] = text;
729 }
730
731 update();
732 }
733
734 //------------------------------------------------------------------------------
735 // Name: updateDisplay
736 // Desc:
737 //------------------------------------------------------------------------------
738 void KCalcDisplay::updateDisplay()
739 {
740 // Put sign in front.
741 QString tmp_string;
742 if (neg_sign_) {
743 tmp_string = QLatin1Char('-') + str_int_;
744 } else {
745 tmp_string = str_int_;
746 }
747
748 bool ok;
749
750 switch (num_base_) {
751 case NB_BINARY:
752 Q_ASSERT(!period_ && !eestate_);
753 setText(tmp_string);
754 display_amount_ = KNumber(str_int_.toULongLong(&ok, 2));
755 if (neg_sign_) {
756 display_amount_ = -display_amount_;
757 }
758 break;
759
760 case NB_OCTAL:
761 Q_ASSERT(!period_ && !eestate_);
762 setText(tmp_string);
763 display_amount_ = KNumber(str_int_.toULongLong(&ok, 8));
764 if (neg_sign_) {
765 display_amount_ = -display_amount_;
766 }
767 break;
768
769 case NB_HEX:
770 Q_ASSERT(!period_ && !eestate_);
771 setText(tmp_string);
772 display_amount_ = KNumber(str_int_.toULongLong(&ok, 16));
773 if (neg_sign_) {
774 display_amount_ = -display_amount_;
775 }
776 break;
777
778 case NB_DECIMAL:
779 if (!eestate_) {
780 setText(tmp_string);
781 display_amount_ = KNumber(tmp_string);
782 } else {
783 if (str_int_exp_.isNull()) {
784 // add 'e0' to display but not to conversion
785 display_amount_ = KNumber(tmp_string);
786 setText(tmp_string + QLatin1String("e0"));
787 } else {
788 tmp_string += QLatin1Char('e') + str_int_exp_;
789 setText(tmp_string);
790 display_amount_ = KNumber(tmp_string);
791 }
792 }
793 break;
794
795 default:
796 Q_ASSERT(0);
797 }
798
799 Q_EMIT changedAmount(display_amount_);
800 }
801
802 //------------------------------------------------------------------------------
803 // Name: newCharacter
804 // Desc:
805 //------------------------------------------------------------------------------
806 void KCalcDisplay::newCharacter(const QChar new_char)
807 {
808 // test if character is valid
809 switch (new_char.toLatin1()) {
810 case 'e':
811 // EE can be set only once and in decimal mode
812 if (num_base_ != NB_DECIMAL || eestate_) {
813 if (beep_) {
814 KNotification::beep();
815 }
816 return;
817 }
818 eestate_ = true;
819 break;
820
821 case 'F':
822 case 'E':
823 case 'D':
824 case 'C':
825 case 'B':
826 case 'A':
827 if (num_base_ == NB_DECIMAL) {
828 if (beep_) {
829 KNotification::beep();
830 }
831 return;
832 }
833 Q_FALLTHROUGH();
834 case '9':
835 case '8':
836 if (num_base_ == NB_OCTAL) {
837 if (beep_) {
838 KNotification::beep();
839 }
840 return;
841 }
842 Q_FALLTHROUGH();
843 case '7':
844 case '6':
845 case '5':
846 case '4':
847 case '3':
848 case '2':
849 if (num_base_ == NB_BINARY) {
850 if (beep_) {
851 KNotification::beep();
852 }
853 return;
854 }
855 Q_FALLTHROUGH();
856 case '1':
857 case '0':
858 break;
859
860 default:
861 if (new_char == QLocale().decimalPoint()) {
862 // Period can be set only once and only in decimal
863 // mode, also not in EE-mode
864 if (num_base_ != NB_DECIMAL || period_ || eestate_) {
865 if (beep_) {
866 KNotification::beep();
867 }
868 return;
869 }
870 period_ = true;
871 } else {
872 if (beep_) {
873 KNotification::beep();
874 }
875 return;
876 }
877 }
878
879 // change exponent or mantissa
880 if (eestate_) {
881 // ignore '.' before 'e'. turn e.g. '123.e' into '123e'
882 if (new_char == QLatin1Char('e') && str_int_.endsWith(QLocale().decimalPoint())) {
883 str_int_.chop(1);
884 period_ = false;
885 }
886
887 // 'e' only starts ee_mode, leaves strings unchanged
888 // do not add '0' if at start of exp
889 if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) {
890 str_int_exp_.append(new_char);
891 }
892 } else {
893 // handle first character
894 if (str_int_ == QLatin1Char('0')) {
895 switch (new_char.toLatin1()) {
896 case 'e':
897 // display "0e" not just "e"
898 // "0e" does not make sense either, but...
899 str_int_.append(new_char);
900 break;
901 default:
902 if (new_char == QLocale().decimalPoint()) {
903 // display "0." not just "."
904 str_int_.append(new_char);
905 } else {
906 // no leading '0's
907 str_int_[0] = new_char;
908 }
909 }
910 } else {
911 str_int_.append(new_char);
912 }
913 }
914
915 updateDisplay();
916 }
917
918 //------------------------------------------------------------------------------
919 // Name: deleteLastDigit
920 // Desc:
921 //------------------------------------------------------------------------------
922 void KCalcDisplay::deleteLastDigit()
923 {
924 // Only partially implemented !!
925 if (eestate_) {
926 if (str_int_exp_.isNull()) {
927 eestate_ = false;
928 } else {
929 const int length = str_int_exp_.length();
930 if (length > 1) {
931 str_int_exp_.chop(1);
932 } else {
933 str_int_exp_ = QLatin1String((const char *)nullptr);
934 }
935 }
936 } else {
937 const int length = str_int_.length();
938 if (length > 1) {
939 if (str_int_[length - 1] == QLocale().decimalPoint()) {
940 period_ = false;
941 }
942 str_int_.chop(1);
943 } else {
944 Q_ASSERT(!period_);
945 str_int_[0] = QLatin1Char('0');
946 }
947 }
948
949 updateDisplay();
950 }
951
952 //------------------------------------------------------------------------------
953 // Name: changeSign
954 // Desc: change Sign of display. Problem: Only possible here, when in input
955 // mode. Otherwise return 'false' so that the kcalc_core can handle
956 // things.
957 //------------------------------------------------------------------------------
958 bool KCalcDisplay::changeSign()
959 {
960 // stupid way, to see if in input_mode or display_mode
961 if (str_int_ == QLatin1Char('0')) {
962 return false;
963 }
964
965 if (eestate_) {
966 if (!str_int_exp_.isNull()) {
967 if (str_int_exp_[0] != QLatin1Char('-')) {
968 str_int_exp_.prepend(QLatin1Char('-'));
969 } else {
970 str_int_exp_.remove(QLatin1Char('-'));
971 }
972 }
973 } else {
974 neg_sign_ = !neg_sign_;
975 }
976
977 updateDisplay();
978 return true;
979 }
980
981 //------------------------------------------------------------------------------
982 // Name: initStyleOption
983 // Desc:
984 //------------------------------------------------------------------------------
985 void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const
986 {
987 if (!option) {
988 return;
989 }
990
991 option->initFrom(this);
992 option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight
993
994 if (frameShadow() == QFrame::Sunken) {
995 option->state |= QStyle::State_Sunken;
996 } else if (frameShadow() == QFrame::Raised) {
997 option->state |= QStyle::State_Raised;
998 }
999
1000 option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
1001 option->midLineWidth = 0;
1002 }
1003
1004 //------------------------------------------------------------------------------
1005 // Name: paintEvent
1006 // Desc:
1007 //------------------------------------------------------------------------------
1008 void KCalcDisplay::paintEvent(QPaintEvent *)
1009 {
1010 QPainter painter(this);
1011
1012 QStyleOptionFrame option;
1013 initStyleOption(&option);
1014
1015 style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this);
1016
1017 // draw display text
1018 const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, nullptr);
1019 QRect cr = contentsRect();
1020 cr.adjust(margin * 2, 0, -margin * 2, 0); // provide a margin
1021
1022 const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter);
1023 painter.drawText(cr, align | Qt::TextSingleLine, text_);
1024
1025 // draw the status texts using half of the normal
1026 // font size but not smaller than 7pt
1027 QFont fnt(font());
1028 fnt.setPointSize(qMax((fnt.pointSize() / 2), 7));
1029 painter.setFont(fnt);
1030
1031 QFontMetrics fm(fnt);
1032 const uint w = fm.boundingRect(QStringLiteral("________")).width();
1033 const uint h = fm.height();
1034
1035 for (int n = 0; n < NUM_STATUS_TEXT; ++n) {
1036 painter.drawText(5 + n * w, h, str_status_[n]);
1037 }
1038 }
1039
1040 //------------------------------------------------------------------------------
1041 // Name: sizeHint
1042 // Desc:
1043 //------------------------------------------------------------------------------
1044 QSize KCalcDisplay::sizeHint() const
1045 {
1046 // basic size
1047 QSize sz = fontMetrics().size(Qt::TextSingleLine, text_);
1048
1049 // expanded by 3/4 font height to make room for the status texts
1050 QFont fnt(font());
1051 fnt.setPointSize(qMax(((fnt.pointSize() * 3) / 4), 7));
1052
1053 const QFontMetrics fm(fnt);
1054 sz.setHeight(sz.height() + fm.height());
1055
1056 QStyleOptionFrame option;
1057 initStyleOption(&option);
1058
1059 return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz, this));
1060 }
1061