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"