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