1/*
2 SPDX-FileCopyrightText: 2001-2013 Evan Teran <evan.teran@gmail.com>
3 SPDX-FileCopyrightText: 1996-2000 Bernd Johannes Wuebben <wuebben@kde.org>
4 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "kcalcdisplay.h"
10
11#include <QApplication>
12#include <QClipboard>
13#include <QMouseEvent>
14#include <QPainter>
15#include <QStyle>
16#include <QStyleOption>
17#include <QTimer>
18
19#include <KNotification>
20
21#include "kcalc_core.h"
22#include "kcalc_settings.h"
23
24//------------------------------------------------------------------------------
25// Name: KCalcDisplay
26// Desc: constructor
27//------------------------------------------------------------------------------
28KCalcDisplay::KCalcDisplay(QWidget *parent)
29 : QFrame(parent)
30 , beep_(false)
31 , groupdigits_(true)
32 , twoscomplement_(true)
33 , button_(0)
34 , lit_(false)
35 , num_base_(NB_DECIMAL)
36 , precision_(9)
37 , fixed_precision_(-1)
38 , display_amount_(0)
39 , history_index_(0)
40 , selection_timer_(new QTimer(this))
41{
42 setAccessibleDescription(i18nc("@label accessibility description of the calculation result display", "Result Display"));
43 setFocusPolicy(Qt::StrongFocus);
44
45 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
46
47 setBackgroundRole(QPalette::Base);
48 setForegroundRole(QPalette::Text);
49 setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // set in kalc.ui
50
51 KNumber::setDefaultFloatOutput(true);
52 KNumber::setDefaultFractionalInput(true);
53
54 connect(this, &KCalcDisplay::clicked, this, &KCalcDisplay::slotDisplaySelected);
55 connect(selection_timer_, &QTimer::timeout, this, &KCalcDisplay::slotSelectionTimedOut);
56
57 sendEvent(event: EventReset);
58}
59
60//------------------------------------------------------------------------------
61// Name: ~KCalcDisplay
62// Desc: destructor
63//------------------------------------------------------------------------------
64KCalcDisplay::~KCalcDisplay() = default;
65
66//------------------------------------------------------------------------------
67// Name: changeSettings
68// Desc:
69//------------------------------------------------------------------------------
70void 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//------------------------------------------------------------------------------
102void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history)
103{
104 bool tmp_error;
105 const KNumber &output = core.lastOutput(error&: 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(event: EventError);
117}
118
119if (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//------------------------------------------------------------------------------
130void 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//------------------------------------------------------------------------------
191void 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//------------------------------------------------------------------------------
209void 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//------------------------------------------------------------------------------
227bool 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//------------------------------------------------------------------------------
260void KCalcDisplay::slotCut()
261{
262 slotCopy();
263 sendEvent(event: EventReset);
264}
265
266//------------------------------------------------------------------------------
267// Name: slotCopy
268// Desc:
269//------------------------------------------------------------------------------
270void 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//------------------------------------------------------------------------------
300void 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//------------------------------------------------------------------------------
369void 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(bClipboard: false); // Selection
382 }
383}
384
385//------------------------------------------------------------------------------
386// Name: slotSelectionTimedOut
387// Desc:
388//------------------------------------------------------------------------------
389void KCalcDisplay::slotSelectionTimedOut()
390{
391 lit_ = false;
392 invertColors();
393 selection_timer_->stop();
394}
395
396//------------------------------------------------------------------------------
397// Name: invertColors
398// Desc:
399//------------------------------------------------------------------------------
400void 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//------------------------------------------------------------------------------
412void 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//------------------------------------------------------------------------------
428void KCalcDisplay::setPrecision(int precision)
429{
430 precision_ = precision;
431}
432
433//------------------------------------------------------------------------------
434// Name: setFixedPrecision
435// Desc:
436//------------------------------------------------------------------------------
437void 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//------------------------------------------------------------------------------
450void KCalcDisplay::setBeep(bool flag)
451{
452 beep_ = flag;
453}
454
455//------------------------------------------------------------------------------
456// Name: setGroupDigits
457// Desc:
458//------------------------------------------------------------------------------
459void KCalcDisplay::setGroupDigits(bool flag)
460{
461 groupdigits_ = flag;
462}
463
464//------------------------------------------------------------------------------
465// Name: setTwosComplement
466// Desc:
467//------------------------------------------------------------------------------
468void KCalcDisplay::setTwosComplement(bool flag)
469{
470 twoscomplement_ = flag;
471}
472
473//------------------------------------------------------------------------------
474// Name: setBinaryGrouping
475// Desc:
476//------------------------------------------------------------------------------
477void KCalcDisplay::setBinaryGrouping(int digits)
478{
479 binaryGrouping_ = digits;
480}
481
482//------------------------------------------------------------------------------
483// Name: setOctalGrouping
484// Desc:
485//------------------------------------------------------------------------------
486void KCalcDisplay::setOctalGrouping(int digits)
487{
488 octalGrouping_ = digits;
489}
490
491//------------------------------------------------------------------------------
492// Name: setHexadecimalGrouping
493// Desc:
494//------------------------------------------------------------------------------
495void KCalcDisplay::setHexadecimalGrouping(int digits)
496{
497 hexadecimalGrouping_ = digits;
498}
499
500//------------------------------------------------------------------------------
501// Name: getAmount
502// Desc:
503//------------------------------------------------------------------------------
504const KNumber &KCalcDisplay::getAmount() const
505{
506 return display_amount_;
507}
508
509//------------------------------------------------------------------------------
510// Name: setAmount
511// Desc:
512//------------------------------------------------------------------------------
513bool 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//------------------------------------------------------------------------------
558void 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 setAccessibleName(text_); // "Labels should be represented by only QAccessibleInterface and return their text as name"
599 Q_EMIT changedText(text_);
600}
601
602//------------------------------------------------------------------------------
603// Name: setFont
604// Desc: Set the base font and recalculate the font size to better fit
605//------------------------------------------------------------------------------
606void KCalcDisplay::setFont(const QFont &font)
607{
608 // Overwrite current baseFont
609 baseFont_ = font;
610 updateFont();
611}
612
613//------------------------------------------------------------------------------
614// Name: updateFont
615// Desc: Update font using baseFont to better fit
616//------------------------------------------------------------------------------
617void KCalcDisplay::updateFont()
618{
619 // Make a working copy of the font
620 QFont* newFont = new QFont(baseFont());
621
622 // Calculate ideal font size
623 // constants arbitrarily chosen, adjust/increase if scaling issues arise
624 newFont->setPointSizeF(qMax(double(baseFont().pointSize()), qMin(contentsRect().height() / 4.5, contentsRect().width() / 24.6)));
625
626 // Apply font
627 QFrame::setFont(*newFont);
628
629 // Free the memory
630 delete newFont;
631}
632
633//------------------------------------------------------------------------------
634// Name: baseFont
635// Desc:
636//------------------------------------------------------------------------------
637const QFont& KCalcDisplay::baseFont() const
638{
639 return baseFont_;
640}
641
642//------------------------------------------------------------------------------
643// Name: formatDecimalNumber
644// Desc: Convert decimal number to locale-dependent format.
645// We cannot use QLocale::formatNumber(), because the
646// precision is limited to "double".
647//------------------------------------------------------------------------------
648QString KCalcDisplay::formatDecimalNumber(QString string)
649{
650 QLocale locale;
651
652 string.replace(QLatin1Char('.'), locale.decimalPoint());
653
654 if (groupdigits_ && !(locale.numberOptions() & QLocale::OmitGroupSeparator)) {
655 // find position after last digit
656 int pos = string.indexOf(locale.decimalPoint());
657 if (pos < 0) {
658 // do not group digits after the exponent part
659 const int expPos = string.indexOf(QLatin1Char('e'));
660 if (expPos > 0) {
661 pos = expPos;
662 } else {
663 pos = string.length();
664 }
665 }
666
667 // find first digit to not group leading spaces or signs
668 int firstDigitPos = 0;
669 for (int i = 0, total = string.length(); i < total; ++i) {
670 if (string.at(i).isDigit()) {
671 firstDigitPos = i;
672 break;
673 }
674 }
675
676 const auto groupSeparator = locale.groupSeparator();
677 const int groupSize = 3;
678
679 string.reserve(string.length() + (pos - 1) / groupSize);
680 while ((pos -= groupSize) > firstDigitPos) {
681 string.insert(pos, groupSeparator);
682 }
683 }
684
685 string.replace(QLatin1Char('-'), locale.negativeSign());
686 string.replace(QLatin1Char('+'), locale.positiveSign());
687
688 // Digits in unicode is encoded in contiguous range and with the digit zero as the first.
689 // To convert digits to other locales,
690 // just add the digit value and the leading zero's code point.
691 // ref: Unicode15 chapter 4.6 Numeric Value https://www.unicode.org/versions/Unicode15.0.0/ch04.pdf
692
693 // QLocale switched return type of many functions from QChar to QString,
694 // because UTF-16 may need surrogate pairs to represent these fields.
695 // We only need digits, thus we only need the first QChar with Qt>=6.
696
697 auto zero = locale.zeroDigit().at(0).unicode();
698
699 for (auto &i : string) {
700 if (i.isDigit()) {
701 i = QChar(zero + i.digitValue());
702 }
703 }
704
705 return string;
706}
707
708//------------------------------------------------------------------------------
709// Name: groupDigits
710// Desc:
711//------------------------------------------------------------------------------
712QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits)
713{
714 QString tmpDisplayString;
715 const int stringLength = displayString.length();
716
717 for (int i = stringLength; i > 0; i--) {
718 if (i % numDigits == 0 && i != stringLength) {
719 tmpDisplayString = tmpDisplayString + QLatin1Char(' ');
720 }
721
722 tmpDisplayString = tmpDisplayString + displayString[stringLength - i];
723 }
724
725 return tmpDisplayString;
726}
727
728//------------------------------------------------------------------------------
729// Name: text
730// Desc:
731//------------------------------------------------------------------------------
732QString KCalcDisplay::text() const
733{
734 return text_;
735}
736
737//------------------------------------------------------------------------------
738// Name: setBase
739// Desc: change representation of display to new base (i.e. binary, decimal,
740// octal, hexadecimal). The amount being displayed is changed to this
741// base, but for now this amount can not be modified anymore (like
742// being set with "setAmount"). Return value is the new base.
743//------------------------------------------------------------------------------
744int KCalcDisplay::setBase(NumBase new_base)
745{
746 switch (new_base) {
747 case NB_HEX:
748 num_base_ = NB_HEX;
749 period_ = false;
750 break;
751 case NB_DECIMAL:
752 num_base_ = NB_DECIMAL;
753 break;
754 case NB_OCTAL:
755 num_base_ = NB_OCTAL;
756 period_ = false;
757 break;
758 case NB_BINARY:
759 num_base_ = NB_BINARY;
760 period_ = false;
761 break;
762 default:
763 Q_ASSERT(0);
764 }
765
766 // reset amount
767 setAmount(display_amount_);
768 return num_base_;
769}
770
771//------------------------------------------------------------------------------
772// Name: setStatusText
773// Desc:
774//------------------------------------------------------------------------------
775void KCalcDisplay::setStatusText(int i, const QString &text)
776{
777 if (i < NUM_STATUS_TEXT) {
778 str_status_[i] = text;
779 }
780
781 update();
782}
783
784//------------------------------------------------------------------------------
785// Name: updateDisplay
786// Desc:
787//------------------------------------------------------------------------------
788void KCalcDisplay::updateDisplay()
789{
790 // Put sign in front.
791 QString tmp_string;
792 if (neg_sign_) {
793 tmp_string = QLatin1Char('-') + str_int_;
794 } else {
795 tmp_string = str_int_;
796 }
797
798 bool ok;
799
800 switch (num_base_) {
801 case NB_BINARY:
802 Q_ASSERT(!period_ && !eestate_);
803 setText(tmp_string);
804 display_amount_ = KNumber(str_int_.toULongLong(&ok, 2));
805 if (neg_sign_) {
806 display_amount_ = -display_amount_;
807 }
808 break;
809
810 case NB_OCTAL:
811 Q_ASSERT(!period_ && !eestate_);
812 setText(tmp_string);
813 display_amount_ = KNumber(str_int_.toULongLong(&ok, 8));
814 if (neg_sign_) {
815 display_amount_ = -display_amount_;
816 }
817 break;
818
819 case NB_HEX:
820 Q_ASSERT(!period_ && !eestate_);
821 setText(tmp_string);
822 display_amount_ = KNumber(str_int_.toULongLong(&ok, 16));
823 if (neg_sign_) {
824 display_amount_ = -display_amount_;
825 }
826 break;
827
828 case NB_DECIMAL:
829 if (!eestate_) {
830 setText(tmp_string);
831 display_amount_ = KNumber(tmp_string);
832 } else {
833 if (str_int_exp_.isNull()) {
834 // add 'e0' to display but not to conversion
835 display_amount_ = KNumber(tmp_string);
836 setText(tmp_string + QLatin1String("e0"));
837 } else {
838 tmp_string += QLatin1Char('e') + str_int_exp_;
839 setText(tmp_string);
840 display_amount_ = KNumber(tmp_string);
841 }
842 }
843 break;
844
845 default:
846 Q_ASSERT(0);
847 }
848
849 Q_EMIT changedAmount(display_amount_);
850}
851
852//------------------------------------------------------------------------------
853// Name: newCharacter
854// Desc:
855//------------------------------------------------------------------------------
856void KCalcDisplay::newCharacter(const QChar new_char)
857{
858 // test if character is valid
859 switch (new_char.toLatin1()) {
860 case 'e':
861 // EE can be set only once and in decimal mode
862 if (num_base_ != NB_DECIMAL || eestate_) {
863 if (beep_) {
864 KNotification::beep();
865 }
866 return;
867 }
868 eestate_ = true;
869 break;
870
871 case 'F':
872 case 'E':
873 case 'D':
874 case 'C':
875 case 'B':
876 case 'A':
877 if (num_base_ == NB_DECIMAL) {
878 if (beep_) {
879 KNotification::beep();
880 }
881 return;
882 }
883 Q_FALLTHROUGH();
884 case '9':
885 case '8':
886 if (num_base_ == NB_OCTAL) {
887 if (beep_) {
888 KNotification::beep();
889 }
890 return;
891 }
892 Q_FALLTHROUGH();
893 case '7':
894 case '6':
895 case '5':
896 case '4':
897 case '3':
898 case '2':
899 if (num_base_ == NB_BINARY) {
900 if (beep_) {
901 KNotification::beep();
902 }
903 return;
904 }
905 Q_FALLTHROUGH();
906 case '1':
907 case '0':
908 break;
909
910 default:
911 if (new_char == QLocale().decimalPoint()) {
912 // Period can be set only once and only in decimal
913 // mode, also not in EE-mode
914 if (num_base_ != NB_DECIMAL || period_ || eestate_) {
915 if (beep_) {
916 KNotification::beep();
917 }
918 return;
919 }
920 period_ = true;
921 } else {
922 if (beep_) {
923 KNotification::beep();
924 }
925 return;
926 }
927 }
928
929 // change exponent or mantissa
930 if (eestate_) {
931 // ignore '.' before 'e'. turn e.g. '123.e' into '123e'
932 if (new_char == QLatin1Char('e') && str_int_.endsWith(QLocale().decimalPoint())) {
933 str_int_.chop(1);
934 period_ = false;
935 }
936
937 // 'e' only starts ee_mode, leaves strings unchanged
938 // do not add '0' if at start of exp
939 if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) {
940 str_int_exp_.append(new_char);
941 }
942 } else {
943 // handle first character
944 if (str_int_ == QLatin1Char('0')) {
945 switch (new_char.toLatin1()) {
946 case 'e':
947 // display "0e" not just "e"
948 // "0e" does not make sense either, but...
949 str_int_.append(new_char);
950 break;
951 default:
952 if (new_char == QLocale().decimalPoint()) {
953 // display "0." not just "."
954 str_int_.append(new_char);
955 } else {
956 // no leading '0's
957 str_int_[0] = new_char;
958 }
959 }
960 } else {
961 str_int_.append(new_char);
962 }
963 }
964
965 updateDisplay();
966}
967
968//------------------------------------------------------------------------------
969// Name: deleteLastDigit
970// Desc:
971//------------------------------------------------------------------------------
972void KCalcDisplay::deleteLastDigit()
973{
974 // Only partially implemented !!
975 if (eestate_) {
976 if (str_int_exp_.isNull()) {
977 eestate_ = false;
978 } else {
979 const int length = str_int_exp_.length();
980 if (length > 1) {
981 str_int_exp_.chop(1);
982 } else {
983 str_int_exp_ = QLatin1String((const char *)nullptr);
984 }
985 }
986 } else {
987 const int length = str_int_.length();
988 if (length > 1) {
989 if (str_int_[length - 1] == QLocale().decimalPoint()) {
990 period_ = false;
991 }
992 str_int_.chop(1);
993 } else {
994 Q_ASSERT(!period_);
995 str_int_[0] = QLatin1Char('0');
996 }
997 }
998
999 updateDisplay();
1000}
1001
1002//------------------------------------------------------------------------------
1003// Name: changeSign
1004// Desc: change Sign of display. Problem: Only possible here, when in input
1005// mode. Otherwise return 'false' so that the kcalc_core can handle
1006// things.
1007//------------------------------------------------------------------------------
1008bool KCalcDisplay::changeSign()
1009{
1010 // stupid way, to see if in input_mode or display_mode
1011 if (str_int_ == QLatin1Char('0')) {
1012 return false;
1013 }
1014
1015 if (eestate_) {
1016 if (!str_int_exp_.isNull()) {
1017 if (str_int_exp_[0] != QLatin1Char('-')) {
1018 str_int_exp_.prepend(QLatin1Char('-'));
1019 } else {
1020 str_int_exp_.remove(QLatin1Char('-'));
1021 }
1022 }
1023 } else {
1024 neg_sign_ = !neg_sign_;
1025 }
1026
1027 updateDisplay();
1028 return true;
1029}
1030
1031//------------------------------------------------------------------------------
1032// Name: initStyleOption
1033// Desc:
1034//------------------------------------------------------------------------------
1035void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const
1036{
1037 if (!option) {
1038 return;
1039 }
1040
1041 option->initFrom(this);
1042 option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight
1043
1044 if (frameShadow() == QFrame::Sunken) {
1045 option->state |= QStyle::State_Sunken;
1046 } else if (frameShadow() == QFrame::Raised) {
1047 option->state |= QStyle::State_Raised;
1048 }
1049
1050 option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
1051 option->midLineWidth = 0;
1052}
1053
1054//------------------------------------------------------------------------------
1055// Name: paintEvent
1056// Desc:
1057//------------------------------------------------------------------------------
1058void KCalcDisplay::paintEvent(QPaintEvent *)
1059{
1060 QPainter painter(this);
1061
1062 QStyleOptionFrame option;
1063 initStyleOption(option: &option);
1064
1065 style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this);
1066
1067 // draw display text
1068 const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, nullptr);
1069 QRect cr = contentsRect();
1070 cr.adjust(margin * 2, 0, -margin * 2, 0); // provide a margin
1071
1072 const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter);
1073 painter.drawText(cr, align | Qt::TextSingleLine, text_);
1074
1075 // draw the status texts using half of the normal
1076 // font size but not smaller than 7pt
1077 QFont fnt(font());
1078 fnt.setPointSizeF(qMax((fnt.pointSize() / 2.0), 7.0));
1079 painter.setFont(fnt);
1080
1081 QFontMetrics fm(fnt);
1082 const uint w = fm.boundingRect(QStringLiteral("________")).width();
1083 const uint h = fm.height();
1084
1085 for (int n = 0; n < NUM_STATUS_TEXT; ++n) {
1086 painter.drawText(5 + n * w, h, str_status_[n]);
1087 }
1088}
1089
1090//------------------------------------------------------------------------------
1091// Name: resizeEvent
1092// Desc: resize display and adjust font size
1093//------------------------------------------------------------------------------
1094void KCalcDisplay::resizeEvent(QResizeEvent* event)
1095{
1096 QFrame::resizeEvent(event);
1097
1098 // Update font size
1099 updateFont();
1100}
1101
1102//------------------------------------------------------------------------------
1103// Name: sizeHint
1104// Desc:
1105//------------------------------------------------------------------------------
1106QSize KCalcDisplay::sizeHint() const
1107{
1108 // font metrics of base font
1109 const QFontMetrics fmBase(baseFont());
1110
1111 // basic size
1112 QSize sz = fmBase.size(0, QStringLiteral("M"));
1113
1114 // expanded by 3/4 font height to make room for the status texts
1115 QFont fnt(baseFont());
1116 fnt.setPointSize(qMax(((fnt.pointSize() * 3) / 4), 7));
1117
1118 const QFontMetrics fm(fnt);
1119 sz.setHeight(sz.height() + fm.height());
1120
1121 QStyleOptionFrame option;
1122 initStyleOption(option: &option);
1123
1124 return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz, this));
1125}
1126
1127#include "moc_kcalcdisplay.cpp"
1128

source code of kcalc/kcalcdisplay.cpp