2 Copyright (C) 2001 - 2013 Evan Teran
5 Copyright (C) 1996 - 2000 Bernd Johannes Wuebben
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of
11 the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "kcalcdisplay.h"
25 #include <QtGui/qevent.h>
28 #include <QStyleOption>
33 #include <knotification.h>
35 #include "kcalc_core.h"
36 #include "kcalc_settings.h"
38 #include "moc_kcalcdisplay.cpp"
40 //------------------------------------------------------------------------------
43 //------------------------------------------------------------------------------
44 KCalcDisplay::KCalcDisplay(QWidget *parent) : QFrame(parent), beep_(false),
45 groupdigits_(true), twoscomplement_(true), button_(0), lit_(false),
46 num_base_(NB_DECIMAL), precision_(9), fixed_precision_(-1), display_amount_(0),
47 history_index_(0), selection_timer_(new QTimer(this)) {
49 setFocusPolicy(Qt::StrongFocus);
51 setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
53 setBackgroundRole(QPalette::Base);
54 setForegroundRole(QPalette::Text);
55 setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // set in kalc.ui
57 KNumber::setDefaultFloatOutput(true);
58 KNumber::setDefaultFractionalInput(true);
60 connect(this, SIGNAL(clicked()), this, SLOT(slotDisplaySelected()));
61 connect(selection_timer_, SIGNAL(timeout()), this, SLOT(slotSelectionTimedOut()));
63 sendEvent(EventReset);
66 //------------------------------------------------------------------------------
67 // Name: ~KCalcDisplay
69 //------------------------------------------------------------------------------
70 KCalcDisplay::~KCalcDisplay() {
73 //------------------------------------------------------------------------------
74 // Name: changeSettings
76 //------------------------------------------------------------------------------
77 void KCalcDisplay::changeSettings() {
78 QPalette pal = palette();
80 pal.setColor(QPalette::Text, KCalcSettings::foreColor());
81 pal.setColor(QPalette::Base, KCalcSettings::backColor());
85 setFont(KCalcSettings::displayFont());
87 setPrecision(KCalcSettings::precision());
89 if (!KCalcSettings::fixed()) {
90 setFixedPrecision(-1);
92 setFixedPrecision(KCalcSettings::fixedPrecision());
95 setBeep(KCalcSettings::beep());
96 setGroupDigits(KCalcSettings::groupDigits());
97 setTwosComplement(KCalcSettings::twosComplement());
98 setBinaryGrouping(KCalcSettings::binaryGrouping());
99 setOctalGrouping(KCalcSettings::octalGrouping());
100 setHexadecimalGrouping(KCalcSettings::hexadecimalGrouping());
104 //------------------------------------------------------------------------------
107 //------------------------------------------------------------------------------
108 void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history) {
111 const KNumber &output = core.lastOutput(tmp_error);
114 // TODO: do we really need explicit error tracking?
115 // isn't the type of the KNumber good enough?
116 // I think it is and that this error tracking is cruft
117 // left over from a LONG time ago...
118 if(output.type() == KNumber::TYPE_ERROR) {
122 sendEvent(EventError);
125 if (setAmount(output) && store_result_in_history && (output != KNumber::Zero)) {
126 // add this latest value to our history
127 history_list_.insert(history_list_.begin(), output);
132 //------------------------------------------------------------------------------
135 //------------------------------------------------------------------------------
136 void KCalcDisplay::enterDigit(int data) {
139 case 0: newCharacter(QLatin1Char('0')); break;
140 case 1: newCharacter(QLatin1Char('1')); break;
141 case 2: newCharacter(QLatin1Char('2')); break;
142 case 3: newCharacter(QLatin1Char('3')); break;
143 case 4: newCharacter(QLatin1Char('4')); break;
144 case 5: newCharacter(QLatin1Char('5')); break;
145 case 6: newCharacter(QLatin1Char('6')); break;
146 case 7: newCharacter(QLatin1Char('7')); break;
147 case 8: newCharacter(QLatin1Char('8')); break;
148 case 9: newCharacter(QLatin1Char('9')); break;
149 case 0xa: newCharacter(QLatin1Char('A')); break;
150 case 0xb: newCharacter(QLatin1Char('B')); break;
151 case 0xc: newCharacter(QLatin1Char('C')); break;
152 case 0xd: newCharacter(QLatin1Char('D')); break;
153 case 0xe: newCharacter(QLatin1Char('E')); break;
154 case 0xf: newCharacter(QLatin1Char('F')); break;
161 //------------------------------------------------------------------------------
162 // Name: slotHistoryForward
164 //------------------------------------------------------------------------------
165 void KCalcDisplay::slotHistoryForward() {
167 if (history_list_.empty()) {
171 if (history_index_ <= 0) {
176 setAmount(history_list_[history_index_]);
179 //------------------------------------------------------------------------------
180 // Name: slotHistoryBack
182 //------------------------------------------------------------------------------
183 void KCalcDisplay::slotHistoryBack() {
185 if (history_list_.empty()) {
189 if (history_index_ >= history_list_.size()) {
193 setAmount(history_list_[history_index_]);
197 //------------------------------------------------------------------------------
200 //------------------------------------------------------------------------------
201 bool KCalcDisplay::sendEvent(Event event) {
206 display_amount_ = KNumber::Zero;
207 str_int_ = QLatin1String("0");
208 str_int_exp_.clear();
218 case EventChangeSign:
230 //------------------------------------------------------------------------------
233 //------------------------------------------------------------------------------
234 void KCalcDisplay::slotCut() {
237 sendEvent(EventReset);
240 //------------------------------------------------------------------------------
243 //------------------------------------------------------------------------------
244 void KCalcDisplay::slotCopy() {
250 txt.prepend(QLatin1String("0x"));
251 txt.remove(QLatin1Char(' '));
254 txt.prepend(QLatin1String("0b"));
255 txt.remove(QLatin1Char(' '));
258 txt.prepend(QLatin1String("0"));
259 txt.remove(QLatin1Char(' '));
265 (QApplication::clipboard())->setText(txt, QClipboard::Clipboard);
266 (QApplication::clipboard())->setText(txt, QClipboard::Selection);
269 //------------------------------------------------------------------------------
272 //------------------------------------------------------------------------------
273 void KCalcDisplay::slotPaste(bool bClipboard) {
275 QString tmp_str = (QApplication::clipboard())->text(bClipboard ? QClipboard::Clipboard : QClipboard::Selection);
277 if (tmp_str.isNull()) {
279 KNotification::beep();
284 NumBase tmp_num_base = num_base_;
287 tmp_str = tmp_str.trimmed();
290 tmp_str.remove(KGlobal::locale()->thousandsSeparator());
293 tmp_str = tmp_str.toLower();
296 if (tmp_str.startsWith(QLatin1String("0x"))) {
297 tmp_num_base = NB_HEX;
298 tmp_str.remove(0, 2);
299 } else if (tmp_str.startsWith(QLatin1String("0b"))) {
300 tmp_num_base = NB_BINARY;
301 tmp_str.remove(0, 2);
302 } else if (tmp_str.startsWith(QLatin1String("0"))) {
303 // we don't want this to trigger on "0.xxxxxx" cases
304 if(tmp_str.length() < 2 || QString(tmp_str[1]) != KNumber::decimalSeparator()) {
305 tmp_num_base = NB_OCTAL;
306 tmp_str.remove(0, 1);
310 if (tmp_num_base != NB_DECIMAL) {
312 const qint64 tmp_result = tmp_str.toULongLong(&was_ok, tmp_num_base);
315 setAmount(KNumber::NaN);
317 KNotification::beep();
321 setAmount(KNumber(tmp_result));
323 setAmount(KNumber(tmp_str));
324 if (beep_ && display_amount_ == KNumber::NaN) {
325 KNotification::beep();
330 //------------------------------------------------------------------------------
331 // Name: slotDisplaySelected
333 //------------------------------------------------------------------------------
334 void KCalcDisplay::slotDisplaySelected() {
336 if (button_ == Qt::LeftButton) {
339 selection_timer_->start(100);
341 selection_timer_->stop();
346 slotPaste(false); // Selection
350 //------------------------------------------------------------------------------
351 // Name: slotSelectionTimedOut
353 //------------------------------------------------------------------------------
354 void KCalcDisplay::slotSelectionTimedOut() {
358 selection_timer_->stop();
361 //------------------------------------------------------------------------------
362 // Name: invertColors
364 //------------------------------------------------------------------------------
365 void KCalcDisplay::invertColors() {
367 QPalette tmp_palette = palette();
368 tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text));
369 tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base));
370 setPalette(tmp_palette);
373 //------------------------------------------------------------------------------
374 // Name: mousePressEvent
376 //------------------------------------------------------------------------------
377 void KCalcDisplay::mousePressEvent(QMouseEvent *e) {
379 if (e->button() == Qt::LeftButton) {
381 button_ = Qt::LeftButton;
383 button_ = Qt::MiddleButton;
389 //------------------------------------------------------------------------------
390 // Name: setPrecision
392 //------------------------------------------------------------------------------
393 void KCalcDisplay::setPrecision(int precision) {
395 precision_ = precision;
398 //------------------------------------------------------------------------------
399 // Name: setFixedPrecision
401 //------------------------------------------------------------------------------
402 void KCalcDisplay::setFixedPrecision(int precision) {
404 if (fixed_precision_ > precision_) {
405 fixed_precision_ = -1;
407 fixed_precision_ = precision;
411 //------------------------------------------------------------------------------
414 //------------------------------------------------------------------------------
415 void KCalcDisplay::setBeep(bool flag) {
419 //------------------------------------------------------------------------------
420 // Name: setGroupDigits
422 //------------------------------------------------------------------------------
423 void KCalcDisplay::setGroupDigits(bool flag) {
427 //------------------------------------------------------------------------------
428 // Name: setTwosComplement
430 //------------------------------------------------------------------------------
431 void KCalcDisplay::setTwosComplement(bool flag) {
432 twoscomplement_ = flag;
435 //------------------------------------------------------------------------------
436 // Name: setBinaryGrouping
438 //------------------------------------------------------------------------------
439 void KCalcDisplay::setBinaryGrouping(int digits) {
440 binaryGrouping_ = digits;
443 //------------------------------------------------------------------------------
444 // Name: setOctalGrouping
446 //------------------------------------------------------------------------------
447 void KCalcDisplay::setOctalGrouping(int digits) {
448 octalGrouping_ = digits;
451 //------------------------------------------------------------------------------
452 // Name: setHexadecimalGrouping
454 //------------------------------------------------------------------------------
455 void KCalcDisplay::setHexadecimalGrouping(int digits) {
456 hexadecimalGrouping_ = digits;
459 //------------------------------------------------------------------------------
462 //------------------------------------------------------------------------------
463 const KNumber &KCalcDisplay::getAmount() const {
464 return display_amount_;
467 //------------------------------------------------------------------------------
470 //------------------------------------------------------------------------------
471 bool KCalcDisplay::setAmount(const KNumber &new_amount) {
475 str_int_ = QLatin1String("0");
476 str_int_exp_.clear();
481 if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) {
482 display_amount_ = new_amount.integerPart();
484 if (twoscomplement_) {
485 // treat number as 64-bit unsigned
486 const quint64 tmp_workaround = display_amount_.toUint64();
487 display_str = QString::number(tmp_workaround, num_base_).toUpper();
489 // QString::number treats non-decimal as unsigned
490 qint64 tmp_workaround = display_amount_.toInt64();
491 const bool neg = tmp_workaround < 0;
493 tmp_workaround = qAbs(tmp_workaround);
496 display_str = QString::number(tmp_workaround, num_base_).toUpper();
498 display_str.prepend(KGlobal::locale()->negativeSign());
502 // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR
503 display_amount_ = new_amount;
504 display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_);
507 setText(display_str);
508 emit changedAmount(display_amount_);
512 //------------------------------------------------------------------------------
515 //------------------------------------------------------------------------------
516 void KCalcDisplay::setText(const QString &string)
518 // note that "C" locale is being used internally
521 // don't mess with special numbers
522 const bool special = (string.contains(QLatin1String("nan")) || string.contains(QLatin1String("inf")));
524 // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized
525 // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal.
527 if (groupdigits_ && !special){
530 if (string.endsWith(QLatin1Char('.'))) {
532 // Note: rounding happened already above!
533 text_ = KGlobal::locale()->formatNumber(text_, false, 0);
534 text_.append(KGlobal::locale()->decimalSymbol());
536 // Note: rounding happened already above!
537 text_ = KGlobal::locale()->formatNumber(text_, false, 0);
542 text_ = groupDigits(text_, binaryGrouping_);
546 text_ = groupDigits(text_, octalGrouping_);
550 text_ = groupDigits(text_, hexadecimalGrouping_);
555 // TODO: enable this code, it replaces the "inf" with an actual infinity
556 // symbol, but what should be put into the clip board when they copy?
557 if(string.contains(QLatin1String("inf"))) {
558 text_.replace("inf", QChar(0x221e));
564 emit changedText(text_);
567 //------------------------------------------------------------------------------
570 //------------------------------------------------------------------------------
571 QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits) {
573 QString tmpDisplayString;
574 const int stringLength = displayString.length();
576 for (int i = stringLength; i > 0 ; i--){
577 if(i % numDigits == 0 && i != stringLength) {
578 tmpDisplayString = tmpDisplayString + ' ';
581 tmpDisplayString = tmpDisplayString + displayString[stringLength - i];
584 return tmpDisplayString;
587 //------------------------------------------------------------------------------
590 //------------------------------------------------------------------------------
591 QString KCalcDisplay::text() const {
595 //------------------------------------------------------------------------------
597 // Desc: change representation of display to new base (i.e. binary, decimal,
598 // octal, hexadecimal). The amount being displayed is changed to this
599 // base, but for now this amount can not be modified anymore (like
600 // being set with "setAmount"). Return value is the new base.
601 //------------------------------------------------------------------------------
602 int KCalcDisplay::setBase(NumBase new_base) {
610 num_base_ = NB_DECIMAL;
613 num_base_ = NB_OCTAL;
617 num_base_ = NB_BINARY;
625 setAmount(display_amount_);
629 //------------------------------------------------------------------------------
630 // Name: setStatusText
632 //------------------------------------------------------------------------------
633 void KCalcDisplay::setStatusText(int i, const QString &text) {
635 if (i < NUM_STATUS_TEXT) {
636 str_status_[i] = text;
642 //------------------------------------------------------------------------------
643 // Name: updateDisplay
645 //------------------------------------------------------------------------------
646 void KCalcDisplay::updateDisplay() {
648 // Put sign in front.
651 tmp_string = QLatin1Char('-') + str_int_;
653 tmp_string = str_int_;
660 Q_ASSERT(!period_ && !eestate_);
662 display_amount_ = KNumber(str_int_.toULongLong(&ok, 2));
664 display_amount_ = -display_amount_;
669 Q_ASSERT(!period_ && !eestate_);
671 display_amount_ = KNumber(str_int_.toULongLong(&ok, 8));
673 display_amount_ = -display_amount_;
678 Q_ASSERT(!period_ && !eestate_);
680 display_amount_ = KNumber(str_int_.toULongLong(&ok, 16));
682 display_amount_ = -display_amount_;
689 display_amount_ = KNumber(tmp_string);
691 if (str_int_exp_.isNull()) {
692 // add 'e0' to display but not to conversion
693 display_amount_ = KNumber(tmp_string);
694 setText(tmp_string + QLatin1String("e0"));
696 tmp_string += QLatin1Char('e') + str_int_exp_;
698 display_amount_ = KNumber(tmp_string);
707 emit changedAmount(display_amount_);
710 //------------------------------------------------------------------------------
711 // Name: newCharacter
713 //------------------------------------------------------------------------------
714 void KCalcDisplay::newCharacter(const QChar new_char) {
716 // test if character is valid
717 switch (new_char.toLatin1()) {
719 // EE can be set only once and in decimal mode
720 if (num_base_ != NB_DECIMAL || eestate_) {
722 KNotification::beep();
735 if (num_base_ == NB_DECIMAL) {
737 KNotification::beep();
744 if (num_base_ == NB_OCTAL) {
746 KNotification::beep();
757 if (num_base_ == NB_BINARY) {
759 KNotification::beep();
769 if(new_char == KGlobal::locale()->decimalSymbol()[0]) {
770 // Period can be set only once and only in decimal
771 // mode, also not in EE-mode
772 if (num_base_ != NB_DECIMAL || period_ || eestate_) {
774 KNotification::beep();
781 KNotification::beep();
787 // change exponent or mantissa
789 // ignore '.' before 'e'. turn e.g. '123.e' into '123e'
790 if (new_char == QLatin1Char('e') && str_int_.endsWith(KGlobal::locale()->decimalSymbol())) {
795 // 'e' only starts ee_mode, leaves strings unchanged
796 // do not add '0' if at start of exp
797 if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) {
798 str_int_exp_.append(new_char);
801 // handle first character
802 if (str_int_ == QLatin1String("0")) {
803 switch (new_char.toLatin1()) {
805 // display "0e" not just "e"
806 // "0e" does not make sense either, but...
807 str_int_.append(new_char);
810 if(new_char == KGlobal::locale()->decimalSymbol()[0]) {
811 // display "0." not just "."
812 str_int_.append(new_char);
815 str_int_[0] = new_char;
819 str_int_.append(new_char);
826 //------------------------------------------------------------------------------
827 // Name: deleteLastDigit
829 //------------------------------------------------------------------------------
830 void KCalcDisplay::deleteLastDigit() {
832 // Only partially implemented !!
834 if (str_int_exp_.isNull()) {
837 const int length = str_int_exp_.length();
839 str_int_exp_.chop(1);
841 str_int_exp_ = QLatin1String((const char *)0);
845 const int length = str_int_.length();
847 if (str_int_[length-1] == KGlobal::locale()->decimalSymbol()[0]) {
853 str_int_[0] = QLatin1Char('0');
860 //------------------------------------------------------------------------------
862 // Desc: change Sign of display. Problem: Only possible here, when in input
863 // mode. Otherwise return 'false' so that the kcalc_core can handle
865 //------------------------------------------------------------------------------
866 bool KCalcDisplay::changeSign() {
868 //stupid way, to see if in input_mode or display_mode
869 if (str_int_ == QLatin1String("0")) {
874 if (!str_int_exp_.isNull()) {
875 if (str_int_exp_[0] != QLatin1Char('-')) {
876 str_int_exp_.prepend(QLatin1Char('-'));
878 str_int_exp_.remove(QLatin1Char('-'));
882 neg_sign_ = !neg_sign_;
889 //------------------------------------------------------------------------------
890 // Name: initStyleOption
892 //------------------------------------------------------------------------------
893 void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const {
899 option->initFrom(this);
900 option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight
902 if (frameShadow() == QFrame::Sunken) {
903 option->state |= QStyle::State_Sunken;
904 } else if (frameShadow() == QFrame::Raised) {
905 option->state |= QStyle::State_Raised;
908 option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
909 option->midLineWidth = 0;
912 //------------------------------------------------------------------------------
915 //------------------------------------------------------------------------------
916 void KCalcDisplay::paintEvent(QPaintEvent *) {
918 QPainter painter(this);
920 QStyleOptionFrame option;
921 initStyleOption(&option);
923 style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this);
926 const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, 0);
927 QRect cr = contentsRect();
928 cr.adjust(margin*2, 0, -margin*2, 0); // provide a margin
930 const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter);
931 painter.drawText(cr, align | Qt::TextSingleLine, text_);
933 // draw the status texts using half of the normal
934 // font size but not smaller than 7pt
936 fnt.setPointSize(qMax((fnt.pointSize() / 2), 7));
937 painter.setFont(fnt);
939 QFontMetrics fm(fnt);
940 const uint w = fm.width(QLatin1String("________"));
941 const uint h = fm.height();
943 for (int n = 0; n < NUM_STATUS_TEXT; ++n) {
944 painter.drawText(5 + n * w, h, str_status_[n]);
948 //------------------------------------------------------------------------------
951 //------------------------------------------------------------------------------
952 QSize KCalcDisplay::sizeHint() const {
955 QSize sz = fontMetrics().size(Qt::TextSingleLine, text_);
957 // expanded by half font height to make room for the status texts
959 fnt.setPointSize(qMax((fnt.pointSize() / 2), 7));
961 const QFontMetrics fm(fnt);
962 sz.setHeight(sz.height() + fm.height());
964 QStyleOptionFrame option;
965 initStyleOption(&option);
967 return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz.expandedTo(QApplication::globalStrut()), this));