File indexing completed on 2024-04-28 09:45:27

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: slotPaste
0299 // Desc:
0300 //------------------------------------------------------------------------------
0301 void KCalcDisplay::slotPaste(bool bClipboard)
0302 {
0303     QString tmp_str = (QApplication::clipboard())->text(bClipboard ? QClipboard::Clipboard : QClipboard::Selection);
0304 
0305     if (tmp_str.isNull()) {
0306         if (beep_) {
0307             KNotification::beep();
0308         }
0309         return;
0310     }
0311 
0312     NumBase tmp_num_base = num_base_;
0313 
0314     // fix up string
0315     tmp_str = tmp_str.trimmed();
0316 
0317     if (groupdigits_) {
0318         tmp_str.remove(QLocale().groupSeparator());
0319     }
0320 
0321     tmp_str = tmp_str.toLower();
0322 
0323     // determine base
0324     if (tmp_str.startsWith(QLatin1String("0x"))) {
0325         tmp_num_base = NB_HEX;
0326         tmp_str.remove(0, 2);
0327     } else if (tmp_str.startsWith(QLatin1String("0b"))) {
0328         tmp_num_base = NB_BINARY;
0329         tmp_str.remove(0, 2);
0330     } else if (tmp_str.startsWith(QLatin1Char('0'))) {
0331         // we don't want this to trigger on "0.xxxxxx" cases
0332         if (tmp_str.length() < 2 || QString(tmp_str[1]) != KNumber::decimalSeparator()) {
0333             tmp_num_base = NB_OCTAL;
0334             tmp_str.remove(0, 1);
0335         }
0336     }
0337 
0338     // for locales where the groups separator is not a comma (,) but a non breaking space
0339     // accept (and correct) both decimal separators (comma and dot) for convenience
0340     if (KNumber::decimalSeparator() == QChar::fromLatin1(',')
0341         && (QLocale().groupSeparator() != QChar::fromLatin1(',') && QLocale().groupSeparator() != QChar::fromLatin1('.'))
0342         && tmp_str.count(QChar::fromLatin1('.')) == 1) {
0343         tmp_str.replace(QChar::fromLatin1('.'), QChar::fromLatin1(','));
0344     }
0345 
0346     if (tmp_num_base != NB_DECIMAL) {
0347         bool was_ok;
0348         const qint64 tmp_result = tmp_str.toULongLong(&was_ok, tmp_num_base);
0349 
0350         if (!was_ok) {
0351             setAmount(KNumber::NaN);
0352             if (beep_) {
0353                 KNotification::beep();
0354             }
0355             return;
0356         }
0357         setAmount(KNumber(tmp_result));
0358     } else {
0359         setAmount(KNumber(tmp_str));
0360         if (beep_ && display_amount_ == KNumber::NaN) {
0361             KNotification::beep();
0362         }
0363     }
0364 }
0365 
0366 //------------------------------------------------------------------------------
0367 // Name: slotDisplaySelected
0368 // Desc:
0369 //------------------------------------------------------------------------------
0370 void KCalcDisplay::slotDisplaySelected()
0371 {
0372     if (button_ == Qt::LeftButton) {
0373         if (lit_) {
0374             slotCopy();
0375             selection_timer_->start(100);
0376         } else {
0377             selection_timer_->stop();
0378         }
0379 
0380         invertColors();
0381     } else {
0382         slotPaste(false); // Selection
0383     }
0384 }
0385 
0386 //------------------------------------------------------------------------------
0387 // Name: slotSelectionTimedOut
0388 // Desc:
0389 //------------------------------------------------------------------------------
0390 void KCalcDisplay::slotSelectionTimedOut()
0391 {
0392     lit_ = false;
0393     invertColors();
0394     selection_timer_->stop();
0395 }
0396 
0397 //------------------------------------------------------------------------------
0398 // Name: invertColors
0399 // Desc:
0400 //------------------------------------------------------------------------------
0401 void KCalcDisplay::invertColors()
0402 {
0403     QPalette tmp_palette = palette();
0404     tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text));
0405     tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base));
0406     setPalette(tmp_palette);
0407 }
0408 
0409 //------------------------------------------------------------------------------
0410 // Name: mousePressEvent
0411 // Desc:
0412 //------------------------------------------------------------------------------
0413 void KCalcDisplay::mousePressEvent(QMouseEvent *e)
0414 {
0415     if (e->button() == Qt::LeftButton) {
0416         lit_ = !lit_;
0417         button_ = Qt::LeftButton;
0418     } else {
0419         button_ = Qt::MiddleButton;
0420     }
0421 
0422     Q_EMIT clicked();
0423 }
0424 
0425 //------------------------------------------------------------------------------
0426 // Name: setPrecision
0427 // Desc:
0428 //------------------------------------------------------------------------------
0429 void KCalcDisplay::setPrecision(int precision)
0430 {
0431     precision_ = precision;
0432 }
0433 
0434 //------------------------------------------------------------------------------
0435 // Name: setFixedPrecision
0436 // Desc:
0437 //------------------------------------------------------------------------------
0438 void KCalcDisplay::setFixedPrecision(int precision)
0439 {
0440     if (fixed_precision_ > precision_) {
0441         fixed_precision_ = -1;
0442     } else {
0443         fixed_precision_ = precision;
0444     }
0445 }
0446 
0447 //------------------------------------------------------------------------------
0448 // Name: setBeep
0449 // Desc:
0450 //------------------------------------------------------------------------------
0451 void KCalcDisplay::setBeep(bool flag)
0452 {
0453     beep_ = flag;
0454 }
0455 
0456 //------------------------------------------------------------------------------
0457 // Name: setGroupDigits
0458 // Desc:
0459 //------------------------------------------------------------------------------
0460 void KCalcDisplay::setGroupDigits(bool flag)
0461 {
0462     groupdigits_ = flag;
0463 }
0464 
0465 //------------------------------------------------------------------------------
0466 // Name: setTwosComplement
0467 // Desc:
0468 //------------------------------------------------------------------------------
0469 void KCalcDisplay::setTwosComplement(bool flag)
0470 {
0471     twoscomplement_ = flag;
0472 }
0473 
0474 //------------------------------------------------------------------------------
0475 // Name: setBinaryGrouping
0476 // Desc:
0477 //------------------------------------------------------------------------------
0478 void KCalcDisplay::setBinaryGrouping(int digits)
0479 {
0480     binaryGrouping_ = digits;
0481 }
0482 
0483 //------------------------------------------------------------------------------
0484 // Name: setOctalGrouping
0485 // Desc:
0486 //------------------------------------------------------------------------------
0487 void KCalcDisplay::setOctalGrouping(int digits)
0488 {
0489     octalGrouping_ = digits;
0490 }
0491 
0492 //------------------------------------------------------------------------------
0493 // Name: setHexadecimalGrouping
0494 // Desc:
0495 //------------------------------------------------------------------------------
0496 void KCalcDisplay::setHexadecimalGrouping(int digits)
0497 {
0498     hexadecimalGrouping_ = digits;
0499 }
0500 
0501 //------------------------------------------------------------------------------
0502 // Name: getAmount
0503 // Desc:
0504 //------------------------------------------------------------------------------
0505 const KNumber &KCalcDisplay::getAmount() const
0506 {
0507     return display_amount_;
0508 }
0509 
0510 //------------------------------------------------------------------------------
0511 // Name: setAmount
0512 // Desc:
0513 //------------------------------------------------------------------------------
0514 bool KCalcDisplay::setAmount(const KNumber &new_amount)
0515 {
0516     QString display_str;
0517 
0518     str_int_ = QStringLiteral("0");
0519     str_int_exp_.clear();
0520     period_ = false;
0521     neg_sign_ = false;
0522     eestate_ = false;
0523 
0524     if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) {
0525         display_amount_ = new_amount.integerPart();
0526 
0527         if (twoscomplement_) {
0528             // treat number as 64-bit unsigned
0529             const quint64 tmp_workaround = display_amount_.toUint64();
0530             display_str = QString::number(tmp_workaround, num_base_).toUpper();
0531         } else {
0532             // QString::number treats non-decimal as unsigned
0533             qint64 tmp_workaround = display_amount_.toInt64();
0534             const bool neg = tmp_workaround < 0;
0535             if (neg) {
0536                 tmp_workaround = qAbs(tmp_workaround);
0537             }
0538 
0539             display_str = QString::number(tmp_workaround, num_base_).toUpper();
0540             if (neg) {
0541                 display_str.prepend(QLocale().negativeSign());
0542             }
0543         }
0544     } else {
0545         // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR
0546         display_amount_ = new_amount;
0547         display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_);
0548     }
0549 
0550     setText(display_str);
0551     Q_EMIT changedAmount(display_amount_);
0552     return true;
0553 }
0554 
0555 //------------------------------------------------------------------------------
0556 // Name: setText
0557 // Desc:
0558 //------------------------------------------------------------------------------
0559 void KCalcDisplay::setText(const QString &string)
0560 {
0561     // note that "C" locale is being used internally
0562     text_ = string;
0563 
0564     // don't mess with special numbers
0565     const bool special = (string.contains(QLatin1String("nan")) || string.contains(QLatin1String("inf")));
0566 
0567     // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized
0568     // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal.
0569 
0570     if ((groupdigits_ || num_base_ == NB_DECIMAL) && !special) {
0571         switch (num_base_) {
0572         case NB_DECIMAL:
0573             text_ = formatDecimalNumber(text_);
0574             break;
0575 
0576         case NB_BINARY:
0577             text_ = groupDigits(text_, binaryGrouping_);
0578             break;
0579 
0580         case NB_OCTAL:
0581             text_ = groupDigits(text_, octalGrouping_);
0582             break;
0583 
0584         case NB_HEX:
0585             text_ = groupDigits(text_, hexadecimalGrouping_);
0586             break;
0587         }
0588     } else if (special) {
0589 #if 0
0590         // TODO: enable this code, it replaces the "inf" with an actual infinity
0591         //       symbol, but what should be put into the clip board when they copy?
0592         if(string.contains(QLatin1String("inf"))) {
0593             text_.replace("inf", QChar(0x221e));
0594         }
0595 #endif
0596     }
0597 
0598     update();
0599     setAccessibleName(text_); // "Labels should be represented by only QAccessibleInterface and return their text as name"
0600     Q_EMIT changedText(text_);
0601 }
0602 
0603 //------------------------------------------------------------------------------
0604 // Name: setFont
0605 // Desc: Set the base font and recalculate the font size to better fit
0606 //------------------------------------------------------------------------------
0607 void KCalcDisplay::setFont(const QFont &font)
0608 {
0609     // Overwrite current baseFont
0610     baseFont_ = font;
0611     updateFont();
0612 }
0613 
0614 //------------------------------------------------------------------------------
0615 // Name: updateFont
0616 // Desc: Update font using baseFont to better fit
0617 //------------------------------------------------------------------------------
0618 void KCalcDisplay::updateFont()
0619 {
0620     // Make a working copy of the font
0621     QFont* newFont = new QFont(baseFont());
0622 
0623     // Calculate ideal font size
0624     // constants arbitrarily chosen, adjust/increase if scaling issues arise
0625     newFont->setPointSizeF(qMax(double(baseFont().pointSize()), qMin(contentsRect().height() / 4.5, contentsRect().width() / 24.6)));
0626 
0627     // Apply font
0628     QFrame::setFont(*newFont);
0629     
0630     // Free the memory
0631     delete newFont;
0632 }
0633 
0634 //------------------------------------------------------------------------------
0635 // Name: baseFont
0636 // Desc:
0637 //------------------------------------------------------------------------------
0638 const QFont& KCalcDisplay::baseFont() const
0639 {
0640     return baseFont_;
0641 }
0642 
0643 //------------------------------------------------------------------------------
0644 // Name: formatDecimalNumber
0645 // Desc: Convert decimal number to locale-dependent format.
0646 //      We cannot use QLocale::formatNumber(), because the
0647 //      precision is limited to "double".
0648 //------------------------------------------------------------------------------
0649 QString KCalcDisplay::formatDecimalNumber(QString string)
0650 {
0651     QLocale locale;
0652 
0653     string.replace(QLatin1Char('.'), locale.decimalPoint());
0654 
0655     if (groupdigits_ && !(locale.numberOptions() & QLocale::OmitGroupSeparator)) {
0656         // find position after last digit
0657         int pos = string.indexOf(locale.decimalPoint());
0658         if (pos < 0) {
0659             // do not group digits after the exponent part
0660             const int expPos = string.indexOf(QLatin1Char('e'));
0661             if (expPos > 0) {
0662                 pos = expPos;
0663             } else {
0664                 pos = string.length();
0665             }
0666         }
0667 
0668         // find first digit to not group leading spaces or signs
0669         int firstDigitPos = 0;
0670         for (int i = 0, total = string.length(); i < total; ++i) {
0671             if (string.at(i).isDigit()) {
0672                 firstDigitPos = i;
0673                 break;
0674             }
0675         }
0676 
0677         const auto groupSeparator = locale.groupSeparator();
0678         const int groupSize = 3;
0679 
0680         string.reserve(string.length() + (pos - 1) / groupSize);
0681         while ((pos -= groupSize) > firstDigitPos) {
0682             string.insert(pos, groupSeparator);
0683         }
0684     }
0685 
0686     string.replace(QLatin1Char('-'), locale.negativeSign());
0687     string.replace(QLatin1Char('+'), locale.positiveSign());
0688 
0689     // Digits in unicode is encoded in contiguous range and with the digit zero as the first.
0690     // To convert digits to other locales,
0691     // just add the digit value and the leading zero's code point.
0692     // ref: Unicode15 chapter 4.6 Numeric Value https://www.unicode.org/versions/Unicode15.0.0/ch04.pdf
0693 
0694     // QLocale switched return type of many functions from QChar to QString,
0695     // because UTF-16 may need surrogate pairs to represent these fields.
0696     // We only need digits, thus we only need the first QChar with Qt>=6.
0697 
0698     auto zero = locale.zeroDigit().at(0).unicode();
0699 
0700     for (auto &i : string) {
0701         if (i.isDigit()) {
0702             i = QChar(zero + i.digitValue());
0703         }
0704     }
0705 
0706     return string;
0707 }
0708 
0709 //------------------------------------------------------------------------------
0710 // Name: groupDigits
0711 // Desc:
0712 //------------------------------------------------------------------------------
0713 QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits)
0714 {
0715     QString tmpDisplayString;
0716     const int stringLength = displayString.length();
0717 
0718     for (int i = stringLength; i > 0; i--) {
0719         if (i % numDigits == 0 && i != stringLength) {
0720             tmpDisplayString = tmpDisplayString + QLatin1Char(' ');
0721         }
0722 
0723         tmpDisplayString = tmpDisplayString + displayString[stringLength - i];
0724     }
0725 
0726     return tmpDisplayString;
0727 }
0728 
0729 //------------------------------------------------------------------------------
0730 // Name: text
0731 // Desc:
0732 //------------------------------------------------------------------------------
0733 QString KCalcDisplay::text() const
0734 {
0735     return text_;
0736 }
0737 
0738 //------------------------------------------------------------------------------
0739 // Name: setBase
0740 // Desc: change representation of display to new base (i.e. binary, decimal,
0741 //       octal, hexadecimal). The amount being displayed is changed to this
0742 //       base, but for now this amount can not be modified anymore (like
0743 //       being set with "setAmount"). Return value is the new base.
0744 //------------------------------------------------------------------------------
0745 int KCalcDisplay::setBase(NumBase new_base)
0746 {
0747     switch (new_base) {
0748     case NB_HEX:
0749         num_base_ = NB_HEX;
0750         period_ = false;
0751         break;
0752     case NB_DECIMAL:
0753         num_base_ = NB_DECIMAL;
0754         break;
0755     case NB_OCTAL:
0756         num_base_ = NB_OCTAL;
0757         period_ = false;
0758         break;
0759     case NB_BINARY:
0760         num_base_ = NB_BINARY;
0761         period_ = false;
0762         break;
0763     default:
0764         Q_ASSERT(0);
0765     }
0766 
0767     // reset amount
0768     setAmount(display_amount_);
0769     return num_base_;
0770 }
0771 
0772 //------------------------------------------------------------------------------
0773 // Name: setStatusText
0774 // Desc:
0775 //------------------------------------------------------------------------------
0776 void KCalcDisplay::setStatusText(int i, const QString &text)
0777 {
0778     if (i < NUM_STATUS_TEXT) {
0779         str_status_[i] = text;
0780     }
0781 
0782     update();
0783 }
0784 
0785 //------------------------------------------------------------------------------
0786 // Name: updateDisplay
0787 // Desc:
0788 //------------------------------------------------------------------------------
0789 void KCalcDisplay::updateDisplay()
0790 {
0791     // Put sign in front.
0792     QString tmp_string;
0793     if (neg_sign_) {
0794         tmp_string = QLatin1Char('-') + str_int_;
0795     } else {
0796         tmp_string = str_int_;
0797     }
0798 
0799     bool ok;
0800 
0801     switch (num_base_) {
0802     case NB_BINARY:
0803         Q_ASSERT(!period_ && !eestate_);
0804         setText(tmp_string);
0805         display_amount_ = KNumber(str_int_.toULongLong(&ok, 2));
0806         if (neg_sign_) {
0807             display_amount_ = -display_amount_;
0808         }
0809         break;
0810 
0811     case NB_OCTAL:
0812         Q_ASSERT(!period_ && !eestate_);
0813         setText(tmp_string);
0814         display_amount_ = KNumber(str_int_.toULongLong(&ok, 8));
0815         if (neg_sign_) {
0816             display_amount_ = -display_amount_;
0817         }
0818         break;
0819 
0820     case NB_HEX:
0821         Q_ASSERT(!period_ && !eestate_);
0822         setText(tmp_string);
0823         display_amount_ = KNumber(str_int_.toULongLong(&ok, 16));
0824         if (neg_sign_) {
0825             display_amount_ = -display_amount_;
0826         }
0827         break;
0828 
0829     case NB_DECIMAL:
0830         if (!eestate_) {
0831             setText(tmp_string);
0832             display_amount_ = KNumber(tmp_string);
0833         } else {
0834             if (str_int_exp_.isNull()) {
0835                 // add 'e0' to display but not to conversion
0836                 display_amount_ = KNumber(tmp_string);
0837                 setText(tmp_string + QLatin1String("e0"));
0838             } else {
0839                 tmp_string += QLatin1Char('e') + str_int_exp_;
0840                 setText(tmp_string);
0841                 display_amount_ = KNumber(tmp_string);
0842             }
0843         }
0844         break;
0845 
0846     default:
0847         Q_ASSERT(0);
0848     }
0849 
0850     Q_EMIT changedAmount(display_amount_);
0851 }
0852 
0853 //------------------------------------------------------------------------------
0854 // Name: newCharacter
0855 // Desc:
0856 //------------------------------------------------------------------------------
0857 void KCalcDisplay::newCharacter(const QChar new_char)
0858 {
0859     // test if character is valid
0860     switch (new_char.toLatin1()) {
0861     case 'e':
0862         // EE can be set only once and in decimal mode
0863         if (num_base_ != NB_DECIMAL || eestate_) {
0864             if (beep_) {
0865                 KNotification::beep();
0866             }
0867             return;
0868         }
0869         eestate_ = true;
0870         break;
0871 
0872     case 'F':
0873     case 'E':
0874     case 'D':
0875     case 'C':
0876     case 'B':
0877     case 'A':
0878         if (num_base_ == NB_DECIMAL) {
0879             if (beep_) {
0880                 KNotification::beep();
0881             }
0882             return;
0883         }
0884         Q_FALLTHROUGH();
0885     case '9':
0886     case '8':
0887         if (num_base_ == NB_OCTAL) {
0888             if (beep_) {
0889                 KNotification::beep();
0890             }
0891             return;
0892         }
0893         Q_FALLTHROUGH();
0894     case '7':
0895     case '6':
0896     case '5':
0897     case '4':
0898     case '3':
0899     case '2':
0900         if (num_base_ == NB_BINARY) {
0901             if (beep_) {
0902                 KNotification::beep();
0903             }
0904             return;
0905         }
0906         Q_FALLTHROUGH();
0907     case '1':
0908     case '0':
0909         break;
0910 
0911     default:
0912         if (new_char == QLocale().decimalPoint()) {
0913             // Period can be set only once and only in decimal
0914             // mode, also not in EE-mode
0915             if (num_base_ != NB_DECIMAL || period_ || eestate_) {
0916                 if (beep_) {
0917                     KNotification::beep();
0918                 }
0919                 return;
0920             }
0921             period_ = true;
0922         } else {
0923             if (beep_) {
0924                 KNotification::beep();
0925             }
0926             return;
0927         }
0928     }
0929 
0930     // change exponent or mantissa
0931     if (eestate_) {
0932         // ignore '.' before 'e'. turn e.g. '123.e' into '123e'
0933         if (new_char == QLatin1Char('e') && str_int_.endsWith(QLocale().decimalPoint())) {
0934             str_int_.chop(1);
0935             period_ = false;
0936         }
0937 
0938         // 'e' only starts ee_mode, leaves strings unchanged
0939         // do not add '0' if at start of exp
0940         if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) {
0941             str_int_exp_.append(new_char);
0942         }
0943     } else {
0944         // handle first character
0945         if (str_int_ == QLatin1Char('0')) {
0946             switch (new_char.toLatin1()) {
0947             case 'e':
0948                 // display "0e" not just "e"
0949                 // "0e" does not make sense either, but...
0950                 str_int_.append(new_char);
0951                 break;
0952             default:
0953                 if (new_char == QLocale().decimalPoint()) {
0954                     // display "0." not just "."
0955                     str_int_.append(new_char);
0956                 } else {
0957                     // no leading '0's
0958                     str_int_[0] = new_char;
0959                 }
0960             }
0961         } else {
0962             str_int_.append(new_char);
0963         }
0964     }
0965 
0966     updateDisplay();
0967 }
0968 
0969 //------------------------------------------------------------------------------
0970 // Name: deleteLastDigit
0971 // Desc:
0972 //------------------------------------------------------------------------------
0973 void KCalcDisplay::deleteLastDigit()
0974 {
0975     // Only partially implemented !!
0976     if (eestate_) {
0977         if (str_int_exp_.isNull()) {
0978             eestate_ = false;
0979         } else {
0980             const int length = str_int_exp_.length();
0981             if (length > 1) {
0982                 str_int_exp_.chop(1);
0983             } else {
0984                 str_int_exp_ = QLatin1String((const char *)nullptr);
0985             }
0986         }
0987     } else {
0988         const int length = str_int_.length();
0989         if (length > 1) {
0990             if (str_int_[length - 1] == QLocale().decimalPoint()) {
0991                 period_ = false;
0992             }
0993             str_int_.chop(1);
0994         } else {
0995             Q_ASSERT(!period_);
0996             str_int_[0] = QLatin1Char('0');
0997         }
0998     }
0999 
1000     updateDisplay();
1001 }
1002 
1003 //------------------------------------------------------------------------------
1004 // Name: changeSign
1005 // Desc: change Sign of display. Problem: Only possible here, when in input
1006 //       mode. Otherwise return 'false' so that the kcalc_core can handle
1007 //       things.
1008 //------------------------------------------------------------------------------
1009 bool KCalcDisplay::changeSign()
1010 {
1011     // stupid way, to see if in input_mode or display_mode
1012     if (str_int_ == QLatin1Char('0')) {
1013         return false;
1014     }
1015 
1016     if (eestate_) {
1017         if (!str_int_exp_.isNull()) {
1018             if (str_int_exp_[0] != QLatin1Char('-')) {
1019                 str_int_exp_.prepend(QLatin1Char('-'));
1020             } else {
1021                 str_int_exp_.remove(QLatin1Char('-'));
1022             }
1023         }
1024     } else {
1025         neg_sign_ = !neg_sign_;
1026     }
1027 
1028     updateDisplay();
1029     return true;
1030 }
1031 
1032 //------------------------------------------------------------------------------
1033 // Name: initStyleOption
1034 // Desc:
1035 //------------------------------------------------------------------------------
1036 void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const
1037 {
1038     if (!option) {
1039         return;
1040     }
1041 
1042     option->initFrom(this);
1043     option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight
1044 
1045     if (frameShadow() == QFrame::Sunken) {
1046         option->state |= QStyle::State_Sunken;
1047     } else if (frameShadow() == QFrame::Raised) {
1048         option->state |= QStyle::State_Raised;
1049     }
1050 
1051     option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
1052     option->midLineWidth = 0;
1053 }
1054 
1055 //------------------------------------------------------------------------------
1056 // Name: paintEvent
1057 // Desc:
1058 //------------------------------------------------------------------------------
1059 void KCalcDisplay::paintEvent(QPaintEvent *)
1060 {
1061     QPainter painter(this);
1062 
1063     QStyleOptionFrame option;
1064     initStyleOption(&option);
1065 
1066     style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this);
1067 
1068     // draw display text
1069     const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, nullptr);
1070     QRect cr = contentsRect();
1071     cr.adjust(margin * 2, 0, -margin * 2, 0); // provide a margin
1072 
1073     const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter);
1074     painter.drawText(cr, align | Qt::TextSingleLine, text_);
1075 
1076     // draw the status texts using half of the normal
1077     // font size but not smaller than 7pt
1078     QFont fnt(font());
1079     fnt.setPointSizeF(qMax((fnt.pointSize() / 2.0), 7.0));
1080     painter.setFont(fnt);
1081 
1082     QFontMetrics fm(fnt);
1083     const uint w = fm.boundingRect(QStringLiteral("________")).width();
1084     const uint h = fm.height();
1085 
1086     for (int n = 0; n < NUM_STATUS_TEXT; ++n) {
1087         painter.drawText(5 + n * w, h, str_status_[n]);
1088     }
1089 }
1090 
1091 //------------------------------------------------------------------------------
1092 // Name: resizeEvent
1093 // Desc: resize display and adjust font size
1094 //------------------------------------------------------------------------------
1095 void KCalcDisplay::resizeEvent(QResizeEvent* event)
1096 {
1097     QFrame::resizeEvent(event);
1098 
1099     // Update font size
1100     updateFont();
1101 }
1102 
1103 //------------------------------------------------------------------------------
1104 // Name: sizeHint
1105 // Desc:
1106 //------------------------------------------------------------------------------
1107 QSize KCalcDisplay::sizeHint() const
1108 {
1109     // font metrics of base font
1110     const QFontMetrics fmBase(baseFont());
1111     
1112     // basic size
1113     QSize sz = fmBase.size(0, QStringLiteral("M"));
1114 
1115     // expanded by 3/4 font height to make room for the status texts
1116     QFont fnt(baseFont());
1117     fnt.setPointSize(qMax(((fnt.pointSize() * 3) / 4), 7));
1118 
1119     const QFontMetrics fm(fnt);
1120     sz.setHeight(sz.height() + fm.height());
1121 
1122     QStyleOptionFrame option;
1123     initStyleOption(&option);
1124 
1125     return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz, this));
1126 }
1127 
1128 #include "moc_kcalcdisplay.cpp"