1 | /* |
2 | SPDX-FileCopyrightText: 2001-2013 Evan Teran <evan.teran@gmail.com> |
3 | SPDX-FileCopyrightText: 1996-2000 Bernd Johannes Wuebben <wuebben@kde.org> |
4 | SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> |
5 | |
6 | SPDX-License-Identifier: GPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "kcalcdisplay.h" |
10 | |
11 | #include <QApplication> |
12 | #include <QClipboard> |
13 | #include <QMouseEvent> |
14 | #include <QPainter> |
15 | #include <QStyle> |
16 | #include <QStyleOption> |
17 | #include <QTimer> |
18 | |
19 | #include <KNotification> |
20 | |
21 | #include "kcalc_core.h" |
22 | #include "kcalc_settings.h" |
23 | |
24 | //------------------------------------------------------------------------------ |
25 | // Name: KCalcDisplay |
26 | // Desc: constructor |
27 | //------------------------------------------------------------------------------ |
28 | KCalcDisplay::KCalcDisplay(QWidget *parent) |
29 | : QFrame(parent) |
30 | , beep_(false) |
31 | , groupdigits_(true) |
32 | , twoscomplement_(true) |
33 | , button_(0) |
34 | , lit_(false) |
35 | , num_base_(NB_DECIMAL) |
36 | , precision_(9) |
37 | , fixed_precision_(-1) |
38 | , display_amount_(0) |
39 | , history_index_(0) |
40 | , selection_timer_(new QTimer(this)) |
41 | { |
42 | setAccessibleDescription(i18nc("@label accessibility description of the calculation result display" , "Result Display" )); |
43 | setFocusPolicy(Qt::StrongFocus); |
44 | |
45 | setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); |
46 | |
47 | setBackgroundRole(QPalette::Base); |
48 | setForegroundRole(QPalette::Text); |
49 | setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // set in kalc.ui |
50 | |
51 | KNumber::setDefaultFloatOutput(true); |
52 | KNumber::setDefaultFractionalInput(true); |
53 | |
54 | connect(this, &KCalcDisplay::clicked, this, &KCalcDisplay::slotDisplaySelected); |
55 | connect(selection_timer_, &QTimer::timeout, this, &KCalcDisplay::slotSelectionTimedOut); |
56 | |
57 | sendEvent(event: EventReset); |
58 | } |
59 | |
60 | //------------------------------------------------------------------------------ |
61 | // Name: ~KCalcDisplay |
62 | // Desc: destructor |
63 | //------------------------------------------------------------------------------ |
64 | KCalcDisplay::~KCalcDisplay() = default; |
65 | |
66 | //------------------------------------------------------------------------------ |
67 | // Name: changeSettings |
68 | // Desc: |
69 | //------------------------------------------------------------------------------ |
70 | void KCalcDisplay::changeSettings() |
71 | { |
72 | QPalette pal = palette(); |
73 | |
74 | pal.setColor(QPalette::Text, KCalcSettings::foreColor()); |
75 | pal.setColor(QPalette::Base, KCalcSettings::backColor()); |
76 | |
77 | setPalette(pal); |
78 | |
79 | setFont(KCalcSettings::displayFont()); |
80 | |
81 | setPrecision(KCalcSettings::precision()); |
82 | |
83 | if (!KCalcSettings::fixed()) { |
84 | setFixedPrecision(-1); |
85 | } else { |
86 | setFixedPrecision(KCalcSettings::fixedPrecision()); |
87 | } |
88 | |
89 | setBeep(KCalcSettings::beep()); |
90 | setGroupDigits(KCalcSettings::groupDigits()); |
91 | setTwosComplement(KCalcSettings::twosComplement()); |
92 | setBinaryGrouping(KCalcSettings::binaryGrouping()); |
93 | setOctalGrouping(KCalcSettings::octalGrouping()); |
94 | setHexadecimalGrouping(KCalcSettings::hexadecimalGrouping()); |
95 | updateDisplay(); |
96 | } |
97 | |
98 | //------------------------------------------------------------------------------ |
99 | // Name: |
100 | // Desc: |
101 | //------------------------------------------------------------------------------ |
102 | void KCalcDisplay::updateFromCore(const CalcEngine &core, bool store_result_in_history) |
103 | { |
104 | bool tmp_error; |
105 | const KNumber &output = core.lastOutput(error&: tmp_error); |
106 | |
107 | #if 0 |
108 | // TODO: do we really need explicit error tracking? |
109 | // isn't the type of the KNumber good enough? |
110 | // I think it is and that this error tracking is cruft |
111 | // left over from a LONG time ago... |
112 | if(output.type() == KNumber::TYPE_ERROR) { |
113 | #else |
114 | if (tmp_error) { |
115 | #endif |
116 | sendEvent(event: EventError); |
117 | } |
118 | |
119 | if (setAmount(output) && store_result_in_history && (output != KNumber::Zero)) { |
120 | // add this latest value to our history |
121 | history_list_.insert(history_list_.begin(), output); |
122 | history_index_ = 0; |
123 | } |
124 | } |
125 | |
126 | //------------------------------------------------------------------------------ |
127 | // Name: enterDigit |
128 | // Desc: |
129 | //------------------------------------------------------------------------------ |
130 | void KCalcDisplay::enterDigit(int data) |
131 | { |
132 | switch (data) { |
133 | case 0: |
134 | newCharacter(QLatin1Char('0')); |
135 | break; |
136 | case 1: |
137 | newCharacter(QLatin1Char('1')); |
138 | break; |
139 | case 2: |
140 | newCharacter(QLatin1Char('2')); |
141 | break; |
142 | case 3: |
143 | newCharacter(QLatin1Char('3')); |
144 | break; |
145 | case 4: |
146 | newCharacter(QLatin1Char('4')); |
147 | break; |
148 | case 5: |
149 | newCharacter(QLatin1Char('5')); |
150 | break; |
151 | case 6: |
152 | newCharacter(QLatin1Char('6')); |
153 | break; |
154 | case 7: |
155 | newCharacter(QLatin1Char('7')); |
156 | break; |
157 | case 8: |
158 | newCharacter(QLatin1Char('8')); |
159 | break; |
160 | case 9: |
161 | newCharacter(QLatin1Char('9')); |
162 | break; |
163 | case 0xa: |
164 | newCharacter(QLatin1Char('A')); |
165 | break; |
166 | case 0xb: |
167 | newCharacter(QLatin1Char('B')); |
168 | break; |
169 | case 0xc: |
170 | newCharacter(QLatin1Char('C')); |
171 | break; |
172 | case 0xd: |
173 | newCharacter(QLatin1Char('D')); |
174 | break; |
175 | case 0xe: |
176 | newCharacter(QLatin1Char('E')); |
177 | break; |
178 | case 0xf: |
179 | newCharacter(QLatin1Char('F')); |
180 | break; |
181 | default: |
182 | Q_ASSERT(0); |
183 | break; |
184 | } |
185 | } |
186 | |
187 | //------------------------------------------------------------------------------ |
188 | // Name: slotHistoryForward |
189 | // Desc: |
190 | //------------------------------------------------------------------------------ |
191 | void KCalcDisplay::slotHistoryForward() |
192 | { |
193 | if (history_list_.empty()) { |
194 | return; |
195 | } |
196 | |
197 | if (history_index_ <= 0) { |
198 | return; |
199 | } |
200 | |
201 | history_index_--; |
202 | setAmount(history_list_[history_index_]); |
203 | } |
204 | |
205 | //------------------------------------------------------------------------------ |
206 | // Name: slotHistoryBack |
207 | // Desc: |
208 | //------------------------------------------------------------------------------ |
209 | void KCalcDisplay::slotHistoryBack() |
210 | { |
211 | if (history_list_.empty()) { |
212 | return; |
213 | } |
214 | |
215 | if (history_index_ >= history_list_.size()) { |
216 | return; |
217 | } |
218 | |
219 | setAmount(history_list_[history_index_]); |
220 | history_index_++; |
221 | } |
222 | |
223 | //------------------------------------------------------------------------------ |
224 | // Name: sendEvent |
225 | // Desc: |
226 | //------------------------------------------------------------------------------ |
227 | bool KCalcDisplay::sendEvent(Event event) |
228 | { |
229 | switch (event) { |
230 | case EventClear: |
231 | case EventReset: |
232 | display_amount_ = KNumber::Zero; |
233 | str_int_ = QStringLiteral("0" ); |
234 | str_int_exp_.clear(); |
235 | |
236 | eestate_ = false; |
237 | period_ = false; |
238 | neg_sign_ = false; |
239 | |
240 | updateDisplay(); |
241 | |
242 | return true; |
243 | |
244 | case EventChangeSign: |
245 | return changeSign(); |
246 | |
247 | case EventError: |
248 | updateDisplay(); |
249 | return true; |
250 | |
251 | default: |
252 | return false; |
253 | } |
254 | } |
255 | |
256 | //------------------------------------------------------------------------------ |
257 | // Name: slotCut |
258 | // Desc: |
259 | //------------------------------------------------------------------------------ |
260 | void KCalcDisplay::slotCut() |
261 | { |
262 | slotCopy(); |
263 | sendEvent(event: EventReset); |
264 | } |
265 | |
266 | //------------------------------------------------------------------------------ |
267 | // Name: slotCopy |
268 | // Desc: |
269 | //------------------------------------------------------------------------------ |
270 | void KCalcDisplay::slotCopy() |
271 | { |
272 | QString txt = text_; |
273 | |
274 | switch (num_base_) { |
275 | case NB_HEX: |
276 | txt.prepend(QLatin1String("0x" )); |
277 | txt.remove(QLatin1Char(' ')); |
278 | break; |
279 | case NB_BINARY: |
280 | txt.prepend(QLatin1String("0b" )); |
281 | txt.remove(QLatin1Char(' ')); |
282 | break; |
283 | case NB_OCTAL: |
284 | txt.prepend(QLatin1String("0" )); |
285 | txt.remove(QLatin1Char(' ')); |
286 | break; |
287 | case NB_DECIMAL: |
288 | txt.remove(QLocale().groupSeparator()); |
289 | break; |
290 | } |
291 | |
292 | QApplication::clipboard()->setText(txt, QClipboard::Clipboard); |
293 | QApplication::clipboard()->setText(txt, QClipboard::Selection); |
294 | } |
295 | |
296 | //------------------------------------------------------------------------------ |
297 | // Name: slotPaste |
298 | // Desc: |
299 | //------------------------------------------------------------------------------ |
300 | void KCalcDisplay::slotPaste(bool bClipboard) |
301 | { |
302 | QString tmp_str = (QApplication::clipboard())->text(bClipboard ? QClipboard::Clipboard : QClipboard::Selection); |
303 | |
304 | if (tmp_str.isNull()) { |
305 | if (beep_) { |
306 | KNotification::beep(); |
307 | } |
308 | return; |
309 | } |
310 | |
311 | NumBase tmp_num_base = num_base_; |
312 | |
313 | // fix up string |
314 | tmp_str = tmp_str.trimmed(); |
315 | |
316 | if (groupdigits_) { |
317 | tmp_str.remove(QLocale().groupSeparator()); |
318 | } |
319 | |
320 | tmp_str = tmp_str.toLower(); |
321 | |
322 | // determine base |
323 | if (tmp_str.startsWith(QLatin1String("0x" ))) { |
324 | tmp_num_base = NB_HEX; |
325 | tmp_str.remove(0, 2); |
326 | } else if (tmp_str.startsWith(QLatin1String("0b" ))) { |
327 | tmp_num_base = NB_BINARY; |
328 | tmp_str.remove(0, 2); |
329 | } else if (tmp_str.startsWith(QLatin1Char('0'))) { |
330 | // we don't want this to trigger on "0.xxxxxx" cases |
331 | if (tmp_str.length() < 2 || QString(tmp_str[1]) != KNumber::decimalSeparator()) { |
332 | tmp_num_base = NB_OCTAL; |
333 | tmp_str.remove(0, 1); |
334 | } |
335 | } |
336 | |
337 | // for locales where the groups separator is not a comma (,) but a non breaking space |
338 | // accept (and correct) both decimal separators (comma and dot) for convenience |
339 | if (KNumber::decimalSeparator() == QChar::fromLatin1(',') |
340 | && (QLocale().groupSeparator() != QChar::fromLatin1(',') && QLocale().groupSeparator() != QChar::fromLatin1('.')) |
341 | && tmp_str.count(QChar::fromLatin1('.')) == 1) { |
342 | tmp_str.replace(QChar::fromLatin1('.'), QChar::fromLatin1(',')); |
343 | } |
344 | |
345 | if (tmp_num_base != NB_DECIMAL) { |
346 | bool was_ok; |
347 | const qint64 tmp_result = tmp_str.toULongLong(&was_ok, tmp_num_base); |
348 | |
349 | if (!was_ok) { |
350 | setAmount(KNumber::NaN); |
351 | if (beep_) { |
352 | KNotification::beep(); |
353 | } |
354 | return; |
355 | } |
356 | setAmount(KNumber(tmp_result)); |
357 | } else { |
358 | setAmount(KNumber(tmp_str)); |
359 | if (beep_ && display_amount_ == KNumber::NaN) { |
360 | KNotification::beep(); |
361 | } |
362 | } |
363 | } |
364 | |
365 | //------------------------------------------------------------------------------ |
366 | // Name: slotDisplaySelected |
367 | // Desc: |
368 | //------------------------------------------------------------------------------ |
369 | void KCalcDisplay::slotDisplaySelected() |
370 | { |
371 | if (button_ == Qt::LeftButton) { |
372 | if (lit_) { |
373 | slotCopy(); |
374 | selection_timer_->start(100); |
375 | } else { |
376 | selection_timer_->stop(); |
377 | } |
378 | |
379 | invertColors(); |
380 | } else { |
381 | slotPaste(bClipboard: false); // Selection |
382 | } |
383 | } |
384 | |
385 | //------------------------------------------------------------------------------ |
386 | // Name: slotSelectionTimedOut |
387 | // Desc: |
388 | //------------------------------------------------------------------------------ |
389 | void KCalcDisplay::slotSelectionTimedOut() |
390 | { |
391 | lit_ = false; |
392 | invertColors(); |
393 | selection_timer_->stop(); |
394 | } |
395 | |
396 | //------------------------------------------------------------------------------ |
397 | // Name: invertColors |
398 | // Desc: |
399 | //------------------------------------------------------------------------------ |
400 | void KCalcDisplay::invertColors() |
401 | { |
402 | QPalette tmp_palette = palette(); |
403 | tmp_palette.setColor(QPalette::Base, palette().color(QPalette::Text)); |
404 | tmp_palette.setColor(QPalette::Text, palette().color(QPalette::Base)); |
405 | setPalette(tmp_palette); |
406 | } |
407 | |
408 | //------------------------------------------------------------------------------ |
409 | // Name: mousePressEvent |
410 | // Desc: |
411 | //------------------------------------------------------------------------------ |
412 | void KCalcDisplay::mousePressEvent(QMouseEvent *e) |
413 | { |
414 | if (e->button() == Qt::LeftButton) { |
415 | lit_ = !lit_; |
416 | button_ = Qt::LeftButton; |
417 | } else { |
418 | button_ = Qt::MiddleButton; |
419 | } |
420 | |
421 | Q_EMIT clicked(); |
422 | } |
423 | |
424 | //------------------------------------------------------------------------------ |
425 | // Name: setPrecision |
426 | // Desc: |
427 | //------------------------------------------------------------------------------ |
428 | void KCalcDisplay::setPrecision(int precision) |
429 | { |
430 | precision_ = precision; |
431 | } |
432 | |
433 | //------------------------------------------------------------------------------ |
434 | // Name: setFixedPrecision |
435 | // Desc: |
436 | //------------------------------------------------------------------------------ |
437 | void KCalcDisplay::setFixedPrecision(int precision) |
438 | { |
439 | if (fixed_precision_ > precision_) { |
440 | fixed_precision_ = -1; |
441 | } else { |
442 | fixed_precision_ = precision; |
443 | } |
444 | } |
445 | |
446 | //------------------------------------------------------------------------------ |
447 | // Name: setBeep |
448 | // Desc: |
449 | //------------------------------------------------------------------------------ |
450 | void KCalcDisplay::setBeep(bool flag) |
451 | { |
452 | beep_ = flag; |
453 | } |
454 | |
455 | //------------------------------------------------------------------------------ |
456 | // Name: setGroupDigits |
457 | // Desc: |
458 | //------------------------------------------------------------------------------ |
459 | void KCalcDisplay::setGroupDigits(bool flag) |
460 | { |
461 | groupdigits_ = flag; |
462 | } |
463 | |
464 | //------------------------------------------------------------------------------ |
465 | // Name: setTwosComplement |
466 | // Desc: |
467 | //------------------------------------------------------------------------------ |
468 | void KCalcDisplay::setTwosComplement(bool flag) |
469 | { |
470 | twoscomplement_ = flag; |
471 | } |
472 | |
473 | //------------------------------------------------------------------------------ |
474 | // Name: setBinaryGrouping |
475 | // Desc: |
476 | //------------------------------------------------------------------------------ |
477 | void KCalcDisplay::setBinaryGrouping(int digits) |
478 | { |
479 | binaryGrouping_ = digits; |
480 | } |
481 | |
482 | //------------------------------------------------------------------------------ |
483 | // Name: setOctalGrouping |
484 | // Desc: |
485 | //------------------------------------------------------------------------------ |
486 | void KCalcDisplay::setOctalGrouping(int digits) |
487 | { |
488 | octalGrouping_ = digits; |
489 | } |
490 | |
491 | //------------------------------------------------------------------------------ |
492 | // Name: setHexadecimalGrouping |
493 | // Desc: |
494 | //------------------------------------------------------------------------------ |
495 | void KCalcDisplay::setHexadecimalGrouping(int digits) |
496 | { |
497 | hexadecimalGrouping_ = digits; |
498 | } |
499 | |
500 | //------------------------------------------------------------------------------ |
501 | // Name: getAmount |
502 | // Desc: |
503 | //------------------------------------------------------------------------------ |
504 | const KNumber &KCalcDisplay::getAmount() const |
505 | { |
506 | return display_amount_; |
507 | } |
508 | |
509 | //------------------------------------------------------------------------------ |
510 | // Name: setAmount |
511 | // Desc: |
512 | //------------------------------------------------------------------------------ |
513 | bool KCalcDisplay::setAmount(const KNumber &new_amount) |
514 | { |
515 | QString display_str; |
516 | |
517 | str_int_ = QStringLiteral("0" ); |
518 | str_int_exp_.clear(); |
519 | period_ = false; |
520 | neg_sign_ = false; |
521 | eestate_ = false; |
522 | |
523 | if ((num_base_ != NB_DECIMAL) && (new_amount.type() != KNumber::TYPE_ERROR)) { |
524 | display_amount_ = new_amount.integerPart(); |
525 | |
526 | if (twoscomplement_) { |
527 | // treat number as 64-bit unsigned |
528 | const quint64 tmp_workaround = display_amount_.toUint64(); |
529 | display_str = QString::number(tmp_workaround, num_base_).toUpper(); |
530 | } else { |
531 | // QString::number treats non-decimal as unsigned |
532 | qint64 tmp_workaround = display_amount_.toInt64(); |
533 | const bool neg = tmp_workaround < 0; |
534 | if (neg) { |
535 | tmp_workaround = qAbs(tmp_workaround); |
536 | } |
537 | |
538 | display_str = QString::number(tmp_workaround, num_base_).toUpper(); |
539 | if (neg) { |
540 | display_str.prepend(QLocale().negativeSign()); |
541 | } |
542 | } |
543 | } else { |
544 | // num_base_ == NB_DECIMAL || new_amount.type() == KNumber::TYPE_ERROR |
545 | display_amount_ = new_amount; |
546 | display_str = display_amount_.toQString(KCalcSettings::precision(), fixed_precision_); |
547 | } |
548 | |
549 | setText(display_str); |
550 | Q_EMIT changedAmount(display_amount_); |
551 | return true; |
552 | } |
553 | |
554 | //------------------------------------------------------------------------------ |
555 | // Name: setText |
556 | // Desc: |
557 | //------------------------------------------------------------------------------ |
558 | void KCalcDisplay::setText(const QString &string) |
559 | { |
560 | // note that "C" locale is being used internally |
561 | text_ = string; |
562 | |
563 | // don't mess with special numbers |
564 | const bool special = (string.contains(QLatin1String("nan" )) || string.contains(QLatin1String("inf" ))); |
565 | |
566 | // The decimal mode needs special treatment for two reasons, because: a) it uses KGlobal::locale() to get a localized |
567 | // format and b) it has possible numbers after the decimal place. Neither applies to Binary, Hexadecimal or Octal. |
568 | |
569 | if ((groupdigits_ || num_base_ == NB_DECIMAL) && !special) { |
570 | switch (num_base_) { |
571 | case NB_DECIMAL: |
572 | text_ = formatDecimalNumber(text_); |
573 | break; |
574 | |
575 | case NB_BINARY: |
576 | text_ = groupDigits(text_, binaryGrouping_); |
577 | break; |
578 | |
579 | case NB_OCTAL: |
580 | text_ = groupDigits(text_, octalGrouping_); |
581 | break; |
582 | |
583 | case NB_HEX: |
584 | text_ = groupDigits(text_, hexadecimalGrouping_); |
585 | break; |
586 | } |
587 | } else if (special) { |
588 | #if 0 |
589 | // TODO: enable this code, it replaces the "inf" with an actual infinity |
590 | // symbol, but what should be put into the clip board when they copy? |
591 | if(string.contains(QLatin1String("inf" ))) { |
592 | text_.replace("inf" , QChar(0x221e)); |
593 | } |
594 | #endif |
595 | } |
596 | |
597 | update(); |
598 | setAccessibleName(text_); // "Labels should be represented by only QAccessibleInterface and return their text as name" |
599 | Q_EMIT changedText(text_); |
600 | } |
601 | |
602 | //------------------------------------------------------------------------------ |
603 | // Name: setFont |
604 | // Desc: Set the base font and recalculate the font size to better fit |
605 | //------------------------------------------------------------------------------ |
606 | void KCalcDisplay::setFont(const QFont &font) |
607 | { |
608 | // Overwrite current baseFont |
609 | baseFont_ = font; |
610 | updateFont(); |
611 | } |
612 | |
613 | //------------------------------------------------------------------------------ |
614 | // Name: updateFont |
615 | // Desc: Update font using baseFont to better fit |
616 | //------------------------------------------------------------------------------ |
617 | void KCalcDisplay::updateFont() |
618 | { |
619 | // Make a working copy of the font |
620 | QFont* newFont = new QFont(baseFont()); |
621 | |
622 | // Calculate ideal font size |
623 | // constants arbitrarily chosen, adjust/increase if scaling issues arise |
624 | newFont->setPointSizeF(qMax(double(baseFont().pointSize()), qMin(contentsRect().height() / 4.5, contentsRect().width() / 24.6))); |
625 | |
626 | // Apply font |
627 | QFrame::setFont(*newFont); |
628 | |
629 | // Free the memory |
630 | delete newFont; |
631 | } |
632 | |
633 | //------------------------------------------------------------------------------ |
634 | // Name: baseFont |
635 | // Desc: |
636 | //------------------------------------------------------------------------------ |
637 | const QFont& KCalcDisplay::baseFont() const |
638 | { |
639 | return baseFont_; |
640 | } |
641 | |
642 | //------------------------------------------------------------------------------ |
643 | // Name: formatDecimalNumber |
644 | // Desc: Convert decimal number to locale-dependent format. |
645 | // We cannot use QLocale::formatNumber(), because the |
646 | // precision is limited to "double". |
647 | //------------------------------------------------------------------------------ |
648 | QString KCalcDisplay::formatDecimalNumber(QString string) |
649 | { |
650 | QLocale locale; |
651 | |
652 | string.replace(QLatin1Char('.'), locale.decimalPoint()); |
653 | |
654 | if (groupdigits_ && !(locale.numberOptions() & QLocale::OmitGroupSeparator)) { |
655 | // find position after last digit |
656 | int pos = string.indexOf(locale.decimalPoint()); |
657 | if (pos < 0) { |
658 | // do not group digits after the exponent part |
659 | const int expPos = string.indexOf(QLatin1Char('e')); |
660 | if (expPos > 0) { |
661 | pos = expPos; |
662 | } else { |
663 | pos = string.length(); |
664 | } |
665 | } |
666 | |
667 | // find first digit to not group leading spaces or signs |
668 | int firstDigitPos = 0; |
669 | for (int i = 0, total = string.length(); i < total; ++i) { |
670 | if (string.at(i).isDigit()) { |
671 | firstDigitPos = i; |
672 | break; |
673 | } |
674 | } |
675 | |
676 | const auto groupSeparator = locale.groupSeparator(); |
677 | const int groupSize = 3; |
678 | |
679 | string.reserve(string.length() + (pos - 1) / groupSize); |
680 | while ((pos -= groupSize) > firstDigitPos) { |
681 | string.insert(pos, groupSeparator); |
682 | } |
683 | } |
684 | |
685 | string.replace(QLatin1Char('-'), locale.negativeSign()); |
686 | string.replace(QLatin1Char('+'), locale.positiveSign()); |
687 | |
688 | // Digits in unicode is encoded in contiguous range and with the digit zero as the first. |
689 | // To convert digits to other locales, |
690 | // just add the digit value and the leading zero's code point. |
691 | // ref: Unicode15 chapter 4.6 Numeric Value https://www.unicode.org/versions/Unicode15.0.0/ch04.pdf |
692 | |
693 | // QLocale switched return type of many functions from QChar to QString, |
694 | // because UTF-16 may need surrogate pairs to represent these fields. |
695 | // We only need digits, thus we only need the first QChar with Qt>=6. |
696 | |
697 | auto zero = locale.zeroDigit().at(0).unicode(); |
698 | |
699 | for (auto &i : string) { |
700 | if (i.isDigit()) { |
701 | i = QChar(zero + i.digitValue()); |
702 | } |
703 | } |
704 | |
705 | return string; |
706 | } |
707 | |
708 | //------------------------------------------------------------------------------ |
709 | // Name: groupDigits |
710 | // Desc: |
711 | //------------------------------------------------------------------------------ |
712 | QString KCalcDisplay::groupDigits(const QString &displayString, int numDigits) |
713 | { |
714 | QString tmpDisplayString; |
715 | const int stringLength = displayString.length(); |
716 | |
717 | for (int i = stringLength; i > 0; i--) { |
718 | if (i % numDigits == 0 && i != stringLength) { |
719 | tmpDisplayString = tmpDisplayString + QLatin1Char(' '); |
720 | } |
721 | |
722 | tmpDisplayString = tmpDisplayString + displayString[stringLength - i]; |
723 | } |
724 | |
725 | return tmpDisplayString; |
726 | } |
727 | |
728 | //------------------------------------------------------------------------------ |
729 | // Name: text |
730 | // Desc: |
731 | //------------------------------------------------------------------------------ |
732 | QString KCalcDisplay::text() const |
733 | { |
734 | return text_; |
735 | } |
736 | |
737 | //------------------------------------------------------------------------------ |
738 | // Name: setBase |
739 | // Desc: change representation of display to new base (i.e. binary, decimal, |
740 | // octal, hexadecimal). The amount being displayed is changed to this |
741 | // base, but for now this amount can not be modified anymore (like |
742 | // being set with "setAmount"). Return value is the new base. |
743 | //------------------------------------------------------------------------------ |
744 | int KCalcDisplay::setBase(NumBase new_base) |
745 | { |
746 | switch (new_base) { |
747 | case NB_HEX: |
748 | num_base_ = NB_HEX; |
749 | period_ = false; |
750 | break; |
751 | case NB_DECIMAL: |
752 | num_base_ = NB_DECIMAL; |
753 | break; |
754 | case NB_OCTAL: |
755 | num_base_ = NB_OCTAL; |
756 | period_ = false; |
757 | break; |
758 | case NB_BINARY: |
759 | num_base_ = NB_BINARY; |
760 | period_ = false; |
761 | break; |
762 | default: |
763 | Q_ASSERT(0); |
764 | } |
765 | |
766 | // reset amount |
767 | setAmount(display_amount_); |
768 | return num_base_; |
769 | } |
770 | |
771 | //------------------------------------------------------------------------------ |
772 | // Name: setStatusText |
773 | // Desc: |
774 | //------------------------------------------------------------------------------ |
775 | void KCalcDisplay::setStatusText(int i, const QString &text) |
776 | { |
777 | if (i < NUM_STATUS_TEXT) { |
778 | str_status_[i] = text; |
779 | } |
780 | |
781 | update(); |
782 | } |
783 | |
784 | //------------------------------------------------------------------------------ |
785 | // Name: updateDisplay |
786 | // Desc: |
787 | //------------------------------------------------------------------------------ |
788 | void KCalcDisplay::updateDisplay() |
789 | { |
790 | // Put sign in front. |
791 | QString tmp_string; |
792 | if (neg_sign_) { |
793 | tmp_string = QLatin1Char('-') + str_int_; |
794 | } else { |
795 | tmp_string = str_int_; |
796 | } |
797 | |
798 | bool ok; |
799 | |
800 | switch (num_base_) { |
801 | case NB_BINARY: |
802 | Q_ASSERT(!period_ && !eestate_); |
803 | setText(tmp_string); |
804 | display_amount_ = KNumber(str_int_.toULongLong(&ok, 2)); |
805 | if (neg_sign_) { |
806 | display_amount_ = -display_amount_; |
807 | } |
808 | break; |
809 | |
810 | case NB_OCTAL: |
811 | Q_ASSERT(!period_ && !eestate_); |
812 | setText(tmp_string); |
813 | display_amount_ = KNumber(str_int_.toULongLong(&ok, 8)); |
814 | if (neg_sign_) { |
815 | display_amount_ = -display_amount_; |
816 | } |
817 | break; |
818 | |
819 | case NB_HEX: |
820 | Q_ASSERT(!period_ && !eestate_); |
821 | setText(tmp_string); |
822 | display_amount_ = KNumber(str_int_.toULongLong(&ok, 16)); |
823 | if (neg_sign_) { |
824 | display_amount_ = -display_amount_; |
825 | } |
826 | break; |
827 | |
828 | case NB_DECIMAL: |
829 | if (!eestate_) { |
830 | setText(tmp_string); |
831 | display_amount_ = KNumber(tmp_string); |
832 | } else { |
833 | if (str_int_exp_.isNull()) { |
834 | // add 'e0' to display but not to conversion |
835 | display_amount_ = KNumber(tmp_string); |
836 | setText(tmp_string + QLatin1String("e0" )); |
837 | } else { |
838 | tmp_string += QLatin1Char('e') + str_int_exp_; |
839 | setText(tmp_string); |
840 | display_amount_ = KNumber(tmp_string); |
841 | } |
842 | } |
843 | break; |
844 | |
845 | default: |
846 | Q_ASSERT(0); |
847 | } |
848 | |
849 | Q_EMIT changedAmount(display_amount_); |
850 | } |
851 | |
852 | //------------------------------------------------------------------------------ |
853 | // Name: newCharacter |
854 | // Desc: |
855 | //------------------------------------------------------------------------------ |
856 | void KCalcDisplay::newCharacter(const QChar new_char) |
857 | { |
858 | // test if character is valid |
859 | switch (new_char.toLatin1()) { |
860 | case 'e': |
861 | // EE can be set only once and in decimal mode |
862 | if (num_base_ != NB_DECIMAL || eestate_) { |
863 | if (beep_) { |
864 | KNotification::beep(); |
865 | } |
866 | return; |
867 | } |
868 | eestate_ = true; |
869 | break; |
870 | |
871 | case 'F': |
872 | case 'E': |
873 | case 'D': |
874 | case 'C': |
875 | case 'B': |
876 | case 'A': |
877 | if (num_base_ == NB_DECIMAL) { |
878 | if (beep_) { |
879 | KNotification::beep(); |
880 | } |
881 | return; |
882 | } |
883 | Q_FALLTHROUGH(); |
884 | case '9': |
885 | case '8': |
886 | if (num_base_ == NB_OCTAL) { |
887 | if (beep_) { |
888 | KNotification::beep(); |
889 | } |
890 | return; |
891 | } |
892 | Q_FALLTHROUGH(); |
893 | case '7': |
894 | case '6': |
895 | case '5': |
896 | case '4': |
897 | case '3': |
898 | case '2': |
899 | if (num_base_ == NB_BINARY) { |
900 | if (beep_) { |
901 | KNotification::beep(); |
902 | } |
903 | return; |
904 | } |
905 | Q_FALLTHROUGH(); |
906 | case '1': |
907 | case '0': |
908 | break; |
909 | |
910 | default: |
911 | if (new_char == QLocale().decimalPoint()) { |
912 | // Period can be set only once and only in decimal |
913 | // mode, also not in EE-mode |
914 | if (num_base_ != NB_DECIMAL || period_ || eestate_) { |
915 | if (beep_) { |
916 | KNotification::beep(); |
917 | } |
918 | return; |
919 | } |
920 | period_ = true; |
921 | } else { |
922 | if (beep_) { |
923 | KNotification::beep(); |
924 | } |
925 | return; |
926 | } |
927 | } |
928 | |
929 | // change exponent or mantissa |
930 | if (eestate_) { |
931 | // ignore '.' before 'e'. turn e.g. '123.e' into '123e' |
932 | if (new_char == QLatin1Char('e') && str_int_.endsWith(QLocale().decimalPoint())) { |
933 | str_int_.chop(1); |
934 | period_ = false; |
935 | } |
936 | |
937 | // 'e' only starts ee_mode, leaves strings unchanged |
938 | // do not add '0' if at start of exp |
939 | if (new_char != QLatin1Char('e') && !(str_int_exp_.isNull() && new_char == QLatin1Char('0'))) { |
940 | str_int_exp_.append(new_char); |
941 | } |
942 | } else { |
943 | // handle first character |
944 | if (str_int_ == QLatin1Char('0')) { |
945 | switch (new_char.toLatin1()) { |
946 | case 'e': |
947 | // display "0e" not just "e" |
948 | // "0e" does not make sense either, but... |
949 | str_int_.append(new_char); |
950 | break; |
951 | default: |
952 | if (new_char == QLocale().decimalPoint()) { |
953 | // display "0." not just "." |
954 | str_int_.append(new_char); |
955 | } else { |
956 | // no leading '0's |
957 | str_int_[0] = new_char; |
958 | } |
959 | } |
960 | } else { |
961 | str_int_.append(new_char); |
962 | } |
963 | } |
964 | |
965 | updateDisplay(); |
966 | } |
967 | |
968 | //------------------------------------------------------------------------------ |
969 | // Name: deleteLastDigit |
970 | // Desc: |
971 | //------------------------------------------------------------------------------ |
972 | void KCalcDisplay::deleteLastDigit() |
973 | { |
974 | // Only partially implemented !! |
975 | if (eestate_) { |
976 | if (str_int_exp_.isNull()) { |
977 | eestate_ = false; |
978 | } else { |
979 | const int length = str_int_exp_.length(); |
980 | if (length > 1) { |
981 | str_int_exp_.chop(1); |
982 | } else { |
983 | str_int_exp_ = QLatin1String((const char *)nullptr); |
984 | } |
985 | } |
986 | } else { |
987 | const int length = str_int_.length(); |
988 | if (length > 1) { |
989 | if (str_int_[length - 1] == QLocale().decimalPoint()) { |
990 | period_ = false; |
991 | } |
992 | str_int_.chop(1); |
993 | } else { |
994 | Q_ASSERT(!period_); |
995 | str_int_[0] = QLatin1Char('0'); |
996 | } |
997 | } |
998 | |
999 | updateDisplay(); |
1000 | } |
1001 | |
1002 | //------------------------------------------------------------------------------ |
1003 | // Name: changeSign |
1004 | // Desc: change Sign of display. Problem: Only possible here, when in input |
1005 | // mode. Otherwise return 'false' so that the kcalc_core can handle |
1006 | // things. |
1007 | //------------------------------------------------------------------------------ |
1008 | bool KCalcDisplay::changeSign() |
1009 | { |
1010 | // stupid way, to see if in input_mode or display_mode |
1011 | if (str_int_ == QLatin1Char('0')) { |
1012 | return false; |
1013 | } |
1014 | |
1015 | if (eestate_) { |
1016 | if (!str_int_exp_.isNull()) { |
1017 | if (str_int_exp_[0] != QLatin1Char('-')) { |
1018 | str_int_exp_.prepend(QLatin1Char('-')); |
1019 | } else { |
1020 | str_int_exp_.remove(QLatin1Char('-')); |
1021 | } |
1022 | } |
1023 | } else { |
1024 | neg_sign_ = !neg_sign_; |
1025 | } |
1026 | |
1027 | updateDisplay(); |
1028 | return true; |
1029 | } |
1030 | |
1031 | //------------------------------------------------------------------------------ |
1032 | // Name: initStyleOption |
1033 | // Desc: |
1034 | //------------------------------------------------------------------------------ |
1035 | void KCalcDisplay::initStyleOption(QStyleOptionFrame *option) const |
1036 | { |
1037 | if (!option) { |
1038 | return; |
1039 | } |
1040 | |
1041 | option->initFrom(this); |
1042 | option->state &= ~QStyle::State_HasFocus; // don't draw focus highlight |
1043 | |
1044 | if (frameShadow() == QFrame::Sunken) { |
1045 | option->state |= QStyle::State_Sunken; |
1046 | } else if (frameShadow() == QFrame::Raised) { |
1047 | option->state |= QStyle::State_Raised; |
1048 | } |
1049 | |
1050 | option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this); |
1051 | option->midLineWidth = 0; |
1052 | } |
1053 | |
1054 | //------------------------------------------------------------------------------ |
1055 | // Name: paintEvent |
1056 | // Desc: |
1057 | //------------------------------------------------------------------------------ |
1058 | void KCalcDisplay::paintEvent(QPaintEvent *) |
1059 | { |
1060 | QPainter painter(this); |
1061 | |
1062 | QStyleOptionFrame option; |
1063 | initStyleOption(option: &option); |
1064 | |
1065 | style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &painter, this); |
1066 | |
1067 | // draw display text |
1068 | const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, nullptr); |
1069 | QRect cr = contentsRect(); |
1070 | cr.adjust(margin * 2, 0, -margin * 2, 0); // provide a margin |
1071 | |
1072 | const int align = QStyle::visualAlignment(layoutDirection(), Qt::AlignRight | Qt::AlignVCenter); |
1073 | painter.drawText(cr, align | Qt::TextSingleLine, text_); |
1074 | |
1075 | // draw the status texts using half of the normal |
1076 | // font size but not smaller than 7pt |
1077 | QFont fnt(font()); |
1078 | fnt.setPointSizeF(qMax((fnt.pointSize() / 2.0), 7.0)); |
1079 | painter.setFont(fnt); |
1080 | |
1081 | QFontMetrics fm(fnt); |
1082 | const uint w = fm.boundingRect(QStringLiteral("________" )).width(); |
1083 | const uint h = fm.height(); |
1084 | |
1085 | for (int n = 0; n < NUM_STATUS_TEXT; ++n) { |
1086 | painter.drawText(5 + n * w, h, str_status_[n]); |
1087 | } |
1088 | } |
1089 | |
1090 | //------------------------------------------------------------------------------ |
1091 | // Name: resizeEvent |
1092 | // Desc: resize display and adjust font size |
1093 | //------------------------------------------------------------------------------ |
1094 | void KCalcDisplay::resizeEvent(QResizeEvent* event) |
1095 | { |
1096 | QFrame::resizeEvent(event); |
1097 | |
1098 | // Update font size |
1099 | updateFont(); |
1100 | } |
1101 | |
1102 | //------------------------------------------------------------------------------ |
1103 | // Name: sizeHint |
1104 | // Desc: |
1105 | //------------------------------------------------------------------------------ |
1106 | QSize KCalcDisplay::sizeHint() const |
1107 | { |
1108 | // font metrics of base font |
1109 | const QFontMetrics fmBase(baseFont()); |
1110 | |
1111 | // basic size |
1112 | QSize sz = fmBase.size(0, QStringLiteral("M" )); |
1113 | |
1114 | // expanded by 3/4 font height to make room for the status texts |
1115 | QFont fnt(baseFont()); |
1116 | fnt.setPointSize(qMax(((fnt.pointSize() * 3) / 4), 7)); |
1117 | |
1118 | const QFontMetrics fm(fnt); |
1119 | sz.setHeight(sz.height() + fm.height()); |
1120 | |
1121 | QStyleOptionFrame option; |
1122 | initStyleOption(option: &option); |
1123 | |
1124 | return (style()->sizeFromContents(QStyle::CT_LineEdit, &option, sz, this)); |
1125 | } |
1126 | |
1127 | #include "moc_kcalcdisplay.cpp" |
1128 | |