Warning, file /utilities/kcalc/kcalcdisplay.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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