File indexing completed on 2024-04-21 05:50:08

0001 /*
0002     SPDX-FileCopyrightText: 2001-2013 Evan Teran <evan.teran@gmail.com>
0003     SPDX-FileCopyrightText: 1996-2000 Bernd Johannes Wuebben <wuebben@kde.org>
0004     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "kcalcdisplay.h"
0010 
0011 #include <QApplication>
0012 #include <QClipboard>
0013 #include <QMouseEvent>
0014 #include <QPainter>
0015 #include <QStyle>
0016 #include <QStyleOption>
0017 #include <QTimer>
0018 
0019 #include <KNotification>
0020 
0021 #include "kcalc_core.h"
0022 #include "kcalc_settings.h"
0023 
0024 //------------------------------------------------------------------------------
0025 // Name: KCalcDisplay
0026 // Desc: constructor
0027 //------------------------------------------------------------------------------
0028 KCalcDisplay::KCalcDisplay(QWidget *parent)
0029     : QFrame(parent)
0030     , beep_(false)
0031     , groupdigits_(true)
0032     , twoscomplement_(true)
0033     , button_(0)
0034     , lit_(false)
0035     , num_base_(NB_DECIMAL)
0036     , precision_(9)
0037     , fixed_precision_(-1)
0038     , display_amount_(0)
0039     , history_index_(0)
0040     , selection_timer_(new QTimer(this))
0041 {
0042     setAccessibleDescription(i18nc("@label accessibility description of the calculation result display", "Result Display"));
0043     setFocusPolicy(Qt::StrongFocus);
0044 
0045     setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0046 
0047     setBackgroundRole(QPalette::Base);
0048     setForegroundRole(QPalette::Text);
0049     setFrameStyle(QFrame::NoFrame); // set in kalc.ui
0050     setContentsMargins(6, 0, 6, 6);
0051 
0052     KNumber::setDefaultFloatOutput(true);
0053     KNumber::setDefaultFractionalInput(true);
0054 
0055     connect(this, &KCalcDisplay::clicked, this, &KCalcDisplay::slotDisplaySelected);
0056     connect(selection_timer_, &QTimer::timeout, this, &KCalcDisplay::slotSelectionTimedOut);
0057 
0058     sendEvent(EventReset);
0059 }
0060 
0061 //------------------------------------------------------------------------------
0062 // Name: ~KCalcDisplay
0063 // Desc: destructor
0064 //------------------------------------------------------------------------------
0065 KCalcDisplay::~KCalcDisplay() = default;
0066 
0067 //------------------------------------------------------------------------------
0068 // Name: changeSettings
0069 // Desc:
0070 //------------------------------------------------------------------------------
0071 void KCalcDisplay::changeSettings()
0072 {
0073     QPalette pal = palette();
0074 
0075     pal.setColor(QPalette::Text, KCalcSettings::foreColor());
0076     pal.setColor(QPalette::Base, KCalcSettings::backColor());
0077 
0078     setPalette(pal);
0079 
0080     setFont(KCalcSettings::displayFont());
0081 
0082     setPrecision(KCalcSettings::precision());
0083 
0084     if (!KCalcSettings::fixed()) {
0085         setFixedPrecision(-1);
0086     } else {
0087         setFixedPrecision(KCalcSettings::fixedPrecision());
0088     }
0089 
0090     setBeep(KCalcSettings::beep());
0091     setGroupDigits(KCalcSettings::groupDigits());
0092     setTwosComplement(KCalcSettings::twosComplement());
0093     setBinaryGrouping(KCalcSettings::binaryGrouping());
0094     setOctalGrouping(KCalcSettings::octalGrouping());
0095     setHexadecimalGrouping(KCalcSettings::hexadecimalGrouping());
0096     updateDisplay();
0097 }
0098 
0099 //------------------------------------------------------------------------------
0100 // Name:
0101 // Desc:
0102 //------------------------------------------------------------------------------
0103 void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history)
0104 {
0105     bool tmp_error;
0106     const KNumber &output = core.lastOutput(tmp_error);
0107 
0108 #if 0
0109     // TODO: do we really need explicit error tracking?
0110     // isn't the type of the KNumber good enough?
0111     // I think it is and that this error tracking is cruft
0112     // left over from a LONG time ago...
0113     if(output.type() == KNumber::TYPE_ERROR) {
0114 #else
0115     if (tmp_error) {
0116 #endif
0117     sendEvent(EventError);
0118 }
0119 
0120 if (setAmount(output) && store_result_in_history && (output != KNumber::Zero)) {
0121     // add this latest value to our history
0122     history_list_.insert(history_list_.begin(), output);
0123     history_index_ = 0;
0124 }
0125 }
0126 
0127 //------------------------------------------------------------------------------
0128 // Name: enterDigit
0129 // Desc:
0130 //------------------------------------------------------------------------------
0131 void KCalcDisplay::enterDigit(int data)
0132 {
0133     switch (data) {
0134     case 0:
0135         newCharacter(QLatin1Char('0'));
0136         break;
0137     case 1:
0138         newCharacter(QLatin1Char('1'));
0139         break;
0140     case 2:
0141         newCharacter(QLatin1Char('2'));
0142         break;
0143     case 3:
0144         newCharacter(QLatin1Char('3'));
0145         break;
0146     case 4:
0147         newCharacter(QLatin1Char('4'));
0148         break;
0149     case 5:
0150         newCharacter(QLatin1Char('5'));
0151         break;
0152     case 6:
0153         newCharacter(QLatin1Char('6'));
0154         break;
0155     case 7:
0156         newCharacter(QLatin1Char('7'));
0157         break;
0158     case 8:
0159         newCharacter(QLatin1Char('8'));
0160         break;
0161     case 9:
0162         newCharacter(QLatin1Char('9'));
0163         break;
0164     case 0xa:
0165         newCharacter(QLatin1Char('A'));
0166         break;
0167     case 0xb:
0168         newCharacter(QLatin1Char('B'));
0169         break;
0170     case 0xc:
0171         newCharacter(QLatin1Char('C'));
0172         break;
0173     case 0xd:
0174         newCharacter(QLatin1Char('D'));
0175         break;
0176     case 0xe:
0177         newCharacter(QLatin1Char('E'));
0178         break;
0179     case 0xf:
0180         newCharacter(QLatin1Char('F'));
0181         break;
0182     default:
0183         Q_ASSERT(0);
0184         break;
0185     }
0186 }
0187 
0188 //------------------------------------------------------------------------------
0189 // Name: slotHistoryForward
0190 // Desc:
0191 //------------------------------------------------------------------------------
0192 void KCalcDisplay::slotHistoryForward()
0193 {
0194     if (history_list_.empty()) {
0195         return;
0196     }
0197 
0198     if (history_index_ <= 0) {
0199         return;
0200     }
0201 
0202     history_index_--;
0203     setAmount(history_list_[history_index_]);
0204 }
0205 
0206 //------------------------------------------------------------------------------
0207 // Name: slotHistoryBack
0208 // Desc:
0209 //------------------------------------------------------------------------------
0210 void KCalcDisplay::slotHistoryBack()
0211 {
0212     if (history_list_.empty()) {
0213         return;
0214     }
0215 
0216     if (history_index_ >= history_list_.size()) {
0217         return;
0218     }
0219 
0220     setAmount(history_list_[history_index_]);
0221     history_index_++;
0222 }
0223 
0224 //------------------------------------------------------------------------------
0225 // Name: sendEvent
0226 // Desc:
0227 //------------------------------------------------------------------------------
0228 bool KCalcDisplay::sendEvent(Event event)
0229 {
0230     switch (event) {
0231     case EventClear:
0232     case EventReset:
0233         display_amount_ = KNumber::Zero;
0234         str_int_ = QStringLiteral("0");
0235         str_int_exp_.clear();
0236 
0237         eestate_ = false;
0238         period_ = false;
0239         neg_sign_ = false;
0240 
0241         updateDisplay();
0242 
0243         return true;
0244 
0245     case EventChangeSign:
0246         return changeSign();
0247 
0248     case EventError:
0249         updateDisplay();
0250         return true;
0251 
0252     default:
0253         return false;
0254     }
0255 }
0256 
0257 //------------------------------------------------------------------------------
0258 // Name: slotCut
0259 // Desc:
0260 //------------------------------------------------------------------------------
0261 void KCalcDisplay::slotCut()
0262 {
0263     slotCopy();
0264     sendEvent(EventReset);
0265 }
0266 
0267 //------------------------------------------------------------------------------
0268 // Name: slotCopy
0269 // Desc:
0270 //------------------------------------------------------------------------------
0271 void KCalcDisplay::slotCopy()
0272 {
0273     QString txt = text_;
0274 
0275     switch (num_base_) {
0276     case NB_HEX:
0277         txt.prepend(QLatin1String("0x"));
0278         txt.remove(QLatin1Char(' '));
0279         break;
0280     case NB_BINARY:
0281         txt.prepend(QLatin1String("0b"));
0282         txt.remove(QLatin1Char(' '));
0283         break;
0284     case NB_OCTAL:
0285         txt.prepend(QLatin1String("0"));
0286         txt.remove(QLatin1Char(' '));
0287         break;
0288     case NB_DECIMAL:
0289         txt.remove(QLocale().groupSeparator());
0290         break;
0291     }
0292 
0293     QApplication::clipboard()->setText(txt, QClipboard::Clipboard);
0294     QApplication::clipboard()->setText(txt, QClipboard::Selection);
0295 }
0296 
0297 //------------------------------------------------------------------------------
0298 // Name: slotDisplaySelected
0299 // Desc:
0300 //------------------------------------------------------------------------------
0301 void KCalcDisplay::slotDisplaySelected()
0302 {
0303     if (button_ == Qt::LeftButton) {
0304         if (lit_) {
0305             slotCopy();
0306             selection_timer_->start(100);
0307         } else {
0308             selection_timer_->stop();
0309         }
0310 
0311         invertColors();
0312     }
0313 }
0314 
0315 //------------------------------------------------------------------------------
0316 // Name: slotSelectionTimedOut
0317 // Desc:
0318 //------------------------------------------------------------------------------
0319 void KCalcDisplay::slotSelectionTimedOut()
0320 {
0321     lit_ = false;
0322     invertColors();
0323     selection_timer_->stop();
0324 }
0325 
0326 //------------------------------------------------------------------------------
0327 // Name: invertColors
0328 // Desc:
0329 //------------------------------------------------------------------------------
0330 void KCalcDisplay::invertColors()
0331 {
0332     QPalette tmp_palette = palette();
0333     tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text));
0334     tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base));
0335     setPalette(tmp_palette);
0336 }
0337 
0338 //------------------------------------------------------------------------------
0339 // Name: mousePressEvent
0340 // Desc:
0341 //------------------------------------------------------------------------------
0342 void KCalcDisplay::mousePressEvent(QMouseEvent *e)
0343 {
0344     if (e->button() == Qt::LeftButton) {
0345         lit_ = !lit_;
0346         button_ = Qt::LeftButton;
0347     } else {
0348         button_ = Qt::MiddleButton;
0349     }
0350 
0351     Q_EMIT clicked();
0352 }
0353 
0354 //------------------------------------------------------------------------------
0355 // Name: setPrecision
0356 // Desc:
0357 //------------------------------------------------------------------------------
0358 void KCalcDisplay::setPrecision(int precision)
0359 {
0360     precision_ = precision;
0361 }
0362 
0363 //------------------------------------------------------------------------------
0364 // Name: setFixedPrecision
0365 // Desc:
0366 //------------------------------------------------------------------------------
0367 void KCalcDisplay::setFixedPrecision(int precision)
0368 {
0369     if (fixed_precision_ > precision_) {
0370         fixed_precision_ = -1;
0371     } else {
0372         fixed_precision_ = precision;
0373     }
0374 }
0375 
0376 //------------------------------------------------------------------------------
0377 // Name: setBeep
0378 // Desc:
0379 //------------------------------------------------------------------------------
0380 void KCalcDisplay::setBeep(bool flag)
0381 {
0382     beep_ = flag;
0383 }
0384 
0385 //------------------------------------------------------------------------------
0386 // Name: setGroupDigits
0387 // Desc:
0388 //------------------------------------------------------------------------------
0389 void KCalcDisplay::setGroupDigits(bool flag)
0390 {
0391     groupdigits_ = flag;
0392 }
0393 
0394 //------------------------------------------------------------------------------
0395 // Name: setTwosComplement
0396 // Desc:
0397 //------------------------------------------------------------------------------
0398 void KCalcDisplay::setTwosComplement(bool flag)
0399 {
0400     twoscomplement_ = flag;
0401 }
0402 
0403 //------------------------------------------------------------------------------
0404 // Name: setBinaryGrouping
0405 // Desc:
0406 //------------------------------------------------------------------------------
0407 void KCalcDisplay::setBinaryGrouping(int digits)
0408 {
0409     binaryGrouping_ = digits;
0410 }
0411 
0412 //------------------------------------------------------------------------------
0413 // Name: setOctalGrouping
0414 // Desc:
0415 //------------------------------------------------------------------------------
0416 void KCalcDisplay::setOctalGrouping(int digits)
0417 {
0418     octalGrouping_ = digits;
0419 }
0420 
0421 //------------------------------------------------------------------------------
0422 // Name: setHexadecimalGrouping
0423 // Desc:
0424 //------------------------------------------------------------------------------
0425 void KCalcDisplay::setHexadecimalGrouping(int digits)
0426 {
0427     hexadecimalGrouping_ = digits;
0428 }
0429 
0430 //------------------------------------------------------------------------------
0431 // Name: getAmount
0432 // Desc:
0433 //------------------------------------------------------------------------------
0434 const KNumber &KCalcDisplay::getAmount() const
0435 {
0436     return display_amount_;
0437 }
0438 
0439 //------------------------------------------------------------------------------
0440 // Name: setAmount
0441 // Desc:
0442 //------------------------------------------------------------------------------
0443 bool KCalcDisplay::setAmount(const KNumber &new_amount)
0444 {
0445     QString display_str;
0446 
0447     str_int_ = QStringLiteral("0");
0448     str_int_exp_.clear();
0449     period_ = false;
0450     neg_sign_ = false;
0451     eestate_ = false;
0452 
0453     if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) {
0454         display_amount_ = new_amount.integerPart();
0455 
0456         if (twoscomplement_) {
0457             // treat number as 64-bit unsigned
0458             const quint64 tmp_workaround = display_amount_.toUint64();
0459             display_str = QString::number(tmp_workaround, num_base_).toUpper();
0460         } else {
0461             // QString::number treats non-decimal as unsigned
0462             qint64 tmp_workaround = display_amount_.toInt64();
0463             const bool neg = tmp_workaround < 0;
0464             if (neg) {
0465                 tmp_workaround = qAbs(tmp_workaround);
0466             }
0467 
0468             display_str = QString::number(tmp_workaround, num_base_).toUpper();
0469             if (neg) {
0470                 display_str.prepend(QLocale().negativeSign());
0471             }
0472         }
0473     } else {
0474         // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR
0475         display_amount_ = new_amount;
0476         display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_);
0477     }
0478 
0479     setText(display_str);
0480     Q_EMIT changedAmount(display_amount_);
0481     return true;
0482 }
0483 
0484 //------------------------------------------------------------------------------
0485 // Name: setText
0486 // Desc:
0487 //------------------------------------------------------------------------------
0488 void KCalcDisplay::setText(const QString &string)
0489 {
0490     // note that "C" locale is being used internally
0491     text_ = string;
0492 
0493     // don't mess with special numbers
0494     const bool special = (string.contains(QLatin1String("nan")) || string.contains(QLatin1String("inf")));
0495 
0496     const bool error = (string.contains(QLatin1String("error")) || string.contains(QLatin1String("malformed")));
0497 
0498     // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized
0499     // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal.
0500 
0501     if ((groupdigits_ || num_base_ == NB_DECIMAL) && !special && !error) {
0502         switch (num_base_) {
0503         case NB_DECIMAL:
0504             text_ = formatDecimalNumber(text_);
0505             break;
0506 
0507         case NB_BINARY:
0508             text_ = groupDigits(text_, binaryGrouping_);
0509             break;
0510 
0511         case NB_OCTAL:
0512             text_ = groupDigits(text_, octalGrouping_);
0513             break;
0514 
0515         case NB_HEX:
0516             text_ = groupDigits(text_, hexadecimalGrouping_);
0517             break;
0518         }
0519     } else if (special) {
0520 #if 0
0521         // TODO: enable this code, it replaces the "inf" with an actual infinity
0522         //       symbol, but what should be put into the clip board when they copy?
0523         if(string.contains(QLatin1String("inf"))) {
0524             text_.replace("inf", QChar(0x221e));
0525         }
0526 #endif
0527     }
0528 
0529     update();
0530     setAccessibleName(text_); // "Labels should be represented by only QAccessibleInterface and return their text as name"
0531     Q_EMIT changedText(text_);
0532 }
0533 
0534 //------------------------------------------------------------------------------
0535 // Name: setFont
0536 // Desc: Set the base font and recalculate the font size to better fit
0537 //------------------------------------------------------------------------------
0538 void KCalcDisplay::setFont(const QFont &font)
0539 {
0540     // Overwrite current baseFont
0541     baseFont_ = font;
0542     updateFont();
0543 }
0544 
0545 //------------------------------------------------------------------------------
0546 // Name: updateFont
0547 // Desc: Update font using baseFont to better fit
0548 //------------------------------------------------------------------------------
0549 void KCalcDisplay::updateFont()
0550 {
0551     // Make a working copy of the font
0552     QFont* newFont = new QFont(baseFont());
0553 
0554     // Calculate ideal font size
0555     // constants arbitrarily chosen, adjust/increase if scaling issues arise
0556     newFont->setPointSizeF(qMax(double(baseFont().pointSize()), qMin(contentsRect().height() / 4.5, contentsRect().width() / 24.6)));
0557 
0558     // Apply font
0559     QFrame::setFont(*newFont);
0560     
0561     // Free the memory
0562     delete newFont;
0563 }
0564 
0565 //------------------------------------------------------------------------------
0566 // Name: baseFont
0567 // Desc:
0568 //------------------------------------------------------------------------------
0569 const QFont& KCalcDisplay::baseFont() const
0570 {
0571     return baseFont_;
0572 }
0573 
0574 //------------------------------------------------------------------------------
0575 // Name: formatDecimalNumber
0576 // Desc: Convert decimal number to locale-dependent format.
0577 //      We cannot use QLocale::formatNumber(), because the
0578 //      precision is limited to "double".
0579 //------------------------------------------------------------------------------
0580 QString KCalcDisplay::formatDecimalNumber(QString string)
0581 {
0582     QLocale locale;
0583 
0584     string.replace(QLatin1Char('.'), locale.decimalPoint());
0585 
0586     if (groupdigits_ && !(locale.numberOptions() & QLocale::OmitGroupSeparator)) {
0587         // find position after last digit
0588         int pos = string.indexOf(locale.decimalPoint());
0589         if (pos < 0) {
0590             // do not group digits after the exponent part
0591             const int expPos = string.indexOf(QLatin1Char('e'));
0592             if (expPos > 0) {
0593                 pos = expPos;
0594             } else {
0595                 pos = string.length();
0596             }
0597         }
0598 
0599         // find first digit to not group leading spaces or signs
0600         int firstDigitPos = 0;
0601         for (int i = 0, total = string.length(); i < total; ++i) {
0602             if (string.at(i).isDigit()) {
0603                 firstDigitPos = i;
0604                 break;
0605             }
0606         }
0607 
0608         const auto groupSeparator = locale.groupSeparator();
0609         const int groupSize = 3;
0610 
0611         string.reserve(string.length() + (pos - 1) / groupSize);
0612         while ((pos -= groupSize) > firstDigitPos) {
0613             string.insert(pos, groupSeparator);
0614         }
0615     }
0616 
0617     string.replace(QLatin1Char('-'), locale.negativeSign());
0618     string.replace(QLatin1Char('+'), locale.positiveSign());
0619 
0620     // Digits in unicode is encoded in contiguous range and with the digit zero as the first.
0621     // To convert digits to other locales,
0622     // just add the digit value and the leading zero's code point.
0623     // ref: Unicode15 chapter 4.6 Numeric Value https://www.unicode.org/versions/Unicode15.0.0/ch04.pdf
0624 
0625     // QLocale switched return type of many functions from QChar to QString,
0626     // because UTF-16 may need surrogate pairs to represent these fields.
0627     // We only need digits, thus we only need the first QChar with Qt>=6.
0628 
0629     auto zero = locale.zeroDigit().at(0).unicode();
0630 
0631     for (auto &i : string) {
0632         if (i.isDigit()) {
0633             i = QChar(zero + i.digitValue());
0634         }
0635     }
0636 
0637     return string;
0638 }
0639 
0640 //------------------------------------------------------------------------------
0641 // Name: groupDigits
0642 // Desc:
0643 //------------------------------------------------------------------------------
0644 QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits)
0645 {
0646     QString tmpDisplayString;
0647     const int stringLength = displayString.length();
0648 
0649     for (int i = stringLength; i > 0; i--) {
0650         if (i % numDigits == 0 && i != stringLength) {
0651             tmpDisplayString = tmpDisplayString + QLatin1Char(' ');
0652         }
0653 
0654         tmpDisplayString = tmpDisplayString + displayString[stringLength - i];
0655     }
0656 
0657     return tmpDisplayString;
0658 }
0659 
0660 //------------------------------------------------------------------------------
0661 // Name: text
0662 // Desc:
0663 //------------------------------------------------------------------------------
0664 QString KCalcDisplay::text() const
0665 {
0666     return text_;
0667 }
0668 
0669 //------------------------------------------------------------------------------
0670 // Name: setBase
0671 // Desc: change representation of display to new base (i.e. binary, decimal,
0672 //       octal, hexadecimal). The amount being displayed is changed to this
0673 //       base, but for now this amount can not be modified anymore (like
0674 //       being set with "setAmount"). Return value is the new base.
0675 //------------------------------------------------------------------------------
0676 int KCalcDisplay::setBase(NumBase new_base)
0677 {
0678     switch (new_base) {
0679     case NB_HEX:
0680         num_base_ = NB_HEX;
0681         period_ = false;
0682         break;
0683     case NB_DECIMAL:
0684         num_base_ = NB_DECIMAL;
0685         break;
0686     case NB_OCTAL:
0687         num_base_ = NB_OCTAL;
0688         period_ = false;
0689         break;
0690     case NB_BINARY:
0691         num_base_ = NB_BINARY;
0692         period_ = false;
0693         break;
0694     default:
0695         Q_ASSERT(0);
0696     }
0697 
0698     // reset amount
0699     setAmount(display_amount_);
0700     return num_base_;
0701 }
0702 
0703 //------------------------------------------------------------------------------
0704 // Name: setStatusText
0705 // Desc:
0706 //------------------------------------------------------------------------------
0707 void KCalcDisplay::setStatusText(int i, const QString &text)
0708 {
0709     if (i < NUM_STATUS_TEXT) {
0710         str_status_[i] = text;
0711     }
0712 
0713     update();
0714 }
0715 
0716 //------------------------------------------------------------------------------
0717 // Name: updateDisplay
0718 // Desc:
0719 //------------------------------------------------------------------------------
0720 void KCalcDisplay::updateDisplay()
0721 {
0722     // Put sign in front.
0723     QString tmp_string;
0724     if (neg_sign_) {
0725         tmp_string = QLatin1Char('-') + str_int_;
0726     } else {
0727         tmp_string = str_int_;
0728     }
0729 
0730     bool ok;
0731 
0732     switch (num_base_) {
0733     case NB_BINARY:
0734         Q_ASSERT(!period_ && !eestate_);
0735         setText(tmp_string);
0736         display_amount_ = KNumber(str_int_.toULongLong(&ok, 2));
0737         if (neg_sign_) {
0738             display_amount_ = -display_amount_;
0739         }
0740         break;
0741 
0742     case NB_OCTAL:
0743         Q_ASSERT(!period_ && !eestate_);
0744         setText(tmp_string);
0745         display_amount_ = KNumber(str_int_.toULongLong(&ok, 8));
0746         if (neg_sign_) {
0747             display_amount_ = -display_amount_;
0748         }
0749         break;
0750 
0751     case NB_HEX:
0752         Q_ASSERT(!period_ && !eestate_);
0753         setText(tmp_string);
0754         display_amount_ = KNumber(str_int_.toULongLong(&ok, 16));
0755         if (neg_sign_) {
0756             display_amount_ = -display_amount_;
0757         }
0758         break;
0759 
0760     case NB_DECIMAL:
0761         if (!eestate_) {
0762             setText(tmp_string);
0763             display_amount_ = KNumber(tmp_string);
0764         } else {
0765             if (str_int_exp_.isNull()) {
0766                 // add 'e0' to display but not to conversion
0767                 display_amount_ = KNumber(tmp_string);
0768                 setText(tmp_string + QLatin1String("e0"));
0769             } else {
0770                 tmp_string += QLatin1Char('e') + str_int_exp_;
0771                 setText(tmp_string);
0772                 display_amount_ = KNumber(tmp_string);
0773             }
0774         }
0775         break;
0776 
0777     default:
0778         Q_ASSERT(0);
0779     }
0780 
0781     Q_EMIT changedAmount(display_amount_);
0782 }
0783 
0784 //------------------------------------------------------------------------------
0785 // Name: newCharacter
0786 // Desc:
0787 //------------------------------------------------------------------------------
0788 void KCalcDisplay::newCharacter(const QChar new_char)
0789 {
0790     // test if character is valid
0791     switch (new_char.toLatin1()) {
0792     case 'e':
0793         // EE can be set only once and in decimal mode
0794         if (num_base_ != NB_DECIMAL || eestate_) {
0795             if (beep_) {
0796                 KNotification::beep();
0797             }
0798             return;
0799         }
0800         eestate_ = true;
0801         break;
0802 
0803     case 'F':
0804     case 'E':
0805     case 'D':
0806     case 'C':
0807     case 'B':
0808     case 'A':
0809         if (num_base_ == NB_DECIMAL) {
0810             if (beep_) {
0811                 KNotification::beep();
0812             }
0813             return;
0814         }
0815         Q_FALLTHROUGH();
0816     case '9':
0817     case '8':
0818         if (num_base_ == NB_OCTAL) {
0819             if (beep_) {
0820                 KNotification::beep();
0821             }
0822             return;
0823         }
0824         Q_FALLTHROUGH();
0825     case '7':
0826     case '6':
0827     case '5':
0828     case '4':
0829     case '3':
0830     case '2':
0831         if (num_base_ == NB_BINARY) {
0832             if (beep_) {
0833                 KNotification::beep();
0834             }
0835             return;
0836         }
0837         Q_FALLTHROUGH();
0838     case '1':
0839     case '0':
0840         break;
0841 
0842     default:
0843         if (new_char == QLocale().decimalPoint()) {
0844             // Period can be set only once and only in decimal
0845             // mode, also not in EE-mode
0846             if (num_base_ != NB_DECIMAL || period_ || eestate_) {
0847                 if (beep_) {
0848                     KNotification::beep();
0849                 }
0850                 return;
0851             }
0852             period_ = true;
0853         } else {
0854             if (beep_) {
0855                 KNotification::beep();
0856             }
0857             return;
0858         }
0859     }
0860 
0861     // change exponent or mantissa
0862     if (eestate_) {
0863         // ignore '.' before 'e'. turn e.g. '123.e' into '123e'
0864         if (new_char == QLatin1Char('e') && str_int_.endsWith(QLocale().decimalPoint())) {
0865             str_int_.chop(1);
0866             period_ = false;
0867         }
0868 
0869         // 'e' only starts ee_mode, leaves strings unchanged
0870         // do not add '0' if at start of exp
0871         if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) {
0872             str_int_exp_.append(new_char);
0873         }
0874     } else {
0875         // handle first character
0876         if (str_int_ == QLatin1Char('0')) {
0877             switch (new_char.toLatin1()) {
0878             case 'e':
0879                 // display "0e" not just "e"
0880                 // "0e" does not make sense either, but...
0881                 str_int_.append(new_char);
0882                 break;
0883             default:
0884                 if (new_char == QLocale().decimalPoint()) {
0885                     // display "0." not just "."
0886                     str_int_.append(new_char);
0887                 } else {
0888                     // no leading '0's
0889                     str_int_[0] = new_char;
0890                 }
0891             }
0892         } else {
0893             str_int_.append(new_char);
0894         }
0895     }
0896 
0897     updateDisplay();
0898 }
0899 
0900 //------------------------------------------------------------------------------
0901 // Name: deleteLastDigit
0902 // Desc:
0903 //------------------------------------------------------------------------------
0904 void KCalcDisplay::deleteLastDigit()
0905 {
0906     // Only partially implemented !!
0907     if (eestate_) {
0908         if (str_int_exp_.isNull()) {
0909             eestate_ = false;
0910         } else {
0911             const int length = str_int_exp_.length();
0912             if (length > 1) {
0913                 str_int_exp_.chop(1);
0914             } else {
0915                 str_int_exp_ = QLatin1String((const char *)nullptr);
0916             }
0917         }
0918     } else {
0919         const int length = str_int_.length();
0920         if (length > 1) {
0921             if (str_int_[length - 1] == QLocale().decimalPoint()) {
0922                 period_ = false;
0923             }
0924             str_int_.chop(1);
0925         } else {
0926             Q_ASSERT(!period_);
0927             str_int_[0] = QLatin1Char('0');
0928         }
0929     }
0930 
0931     updateDisplay();
0932 }
0933 
0934 //------------------------------------------------------------------------------
0935 // Name: changeSign
0936 // Desc: change Sign of display. Problem: Only possible here, when in input
0937 //       mode. Otherwise return 'false' so that the kcalc_core can handle
0938 //       things.
0939 //------------------------------------------------------------------------------
0940 bool KCalcDisplay::changeSign()
0941 {
0942     // stupid way, to see if in input_mode or display_mode
0943     if (str_int_ == QLatin1Char('0')) {
0944         return false;
0945     }
0946 
0947     if (eestate_) {
0948         if (!str_int_exp_.isNull()) {
0949             if (str_int_exp_[0] != QLatin1Char('-')) {
0950                 str_int_exp_.prepend(QLatin1Char('-'));
0951             } else {
0952                 str_int_exp_.remove(QLatin1Char('-'));
0953             }
0954         }
0955     } else {
0956         neg_sign_ = !neg_sign_;
0957     }
0958 
0959     updateDisplay();
0960     return true;
0961 }
0962 
0963 //------------------------------------------------------------------------------
0964 // Name: initStyleOption
0965 // Desc:
0966 //------------------------------------------------------------------------------
0967 void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const
0968 {
0969     if (!option) {
0970         return;
0971     }
0972 
0973     option->initFrom(this);
0974     option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight
0975 
0976     if (frameShadow() == QFrame::Sunken) {
0977         option->state |= QStyle::State_Sunken;
0978     } else if (frameShadow() == QFrame::Raised) {
0979         option->state |= QStyle::State_Raised;
0980     }
0981 
0982     option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
0983     option->midLineWidth = 0;
0984 }
0985 
0986 //------------------------------------------------------------------------------
0987 // Name: paintEvent
0988 // Desc:
0989 //------------------------------------------------------------------------------
0990 void KCalcDisplay::paintEvent(QPaintEvent *)
0991 {
0992     QPainter painter(this);
0993 
0994     QStyleOptionFrame option;
0995     initStyleOption(&option);
0996 
0997     style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this);
0998 
0999     // draw display text
1000     const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, nullptr);
1001     QRect cr = contentsRect();
1002     cr.adjust(margin * 2, 0, -margin * 2, 0); // provide a margin
1003 
1004     const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter);
1005     painter.drawText(cr, align | Qt::TextSingleLine, text_);
1006 
1007     // draw the status texts using half of the normal
1008     // font size but not smaller than 7pt
1009     QFont fnt(font());
1010     fnt.setPointSizeF(qMax((fnt.pointSize() / 2.0), 7.0));
1011     painter.setFont(fnt);
1012 
1013     QFontMetrics fm(fnt);
1014     const uint w = fm.boundingRect(QStringLiteral("________")).width();
1015     const uint h = fm.height();
1016 
1017     for (int n = 0; n < NUM_STATUS_TEXT; ++n) {
1018         painter.drawText(5 + n * w, h, str_status_[n]);
1019     }
1020 }
1021 
1022 //------------------------------------------------------------------------------
1023 // Name: resizeEvent
1024 // Desc: resize display and adjust font size
1025 //------------------------------------------------------------------------------
1026 void KCalcDisplay::resizeEvent(QResizeEvent* event)
1027 {
1028     QFrame::resizeEvent(event);
1029 
1030     // Update font size
1031     updateFont();
1032 }
1033 
1034 //------------------------------------------------------------------------------
1035 // Name: sizeHint
1036 // Desc:
1037 //------------------------------------------------------------------------------
1038 QSize KCalcDisplay::sizeHint() const
1039 {
1040     // font metrics of base font
1041     const QFontMetrics fmBase(baseFont());
1042     
1043     // basic size
1044     QSize sz = fmBase.size(0, QStringLiteral("M"));
1045 
1046     // expanded by 3/4 font height to make room for the status texts
1047     QFont fnt(baseFont());
1048     fnt.setPointSize(qMax(((fnt.pointSize() * 3) / 4), 7));
1049 
1050     const QFontMetrics fm(fnt);
1051     sz.setHeight(sz.height() + fm.height());
1052 
1053     QStyleOptionFrame option;
1054     initStyleOption(&option);
1055 
1056     return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz, this));
1057 }
1058 
1059 #include "moc_kcalcdisplay.cpp"