diff options
author | Karol Baraniecki <karol@baraniecki.eu> | 2022-12-26 17:12:46 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-12-31 00:07:13 +0100 |
commit | 21cc8f65f5c28a39843e1fa94ae819db74b1e5c8 (patch) | |
tree | ced80bd579134dc2392f72dcd83cb81d47590ec4 | |
parent | ef9fd6c2863fb393b422675d8121293c98e44f51 (diff) | |
download | serenity-21cc8f65f5c28a39843e1fa94ae819db74b1e5c8.zip |
Calculator: Support chaining and repeating operations
The calculator now supports chaining (hitting "1+2+3=" shows "6"
instead of "5") and repeating ("2+2===" shows "8") operations. :^)
-rw-r--r-- | Userland/Applications/Calculator/Calculator.cpp | 103 | ||||
-rw-r--r-- | Userland/Applications/Calculator/Calculator.h | 21 | ||||
-rw-r--r-- | Userland/Applications/Calculator/CalculatorWidget.cpp | 67 | ||||
-rw-r--r-- | Userland/Applications/Calculator/Keypad.cpp | 5 | ||||
-rw-r--r-- | Userland/Applications/Calculator/Keypad.h | 2 |
5 files changed, 126 insertions, 72 deletions
diff --git a/Userland/Applications/Calculator/Calculator.cpp b/Userland/Applications/Calculator/Calculator.cpp index 451aa5ea01..9e68d1882a 100644 --- a/Userland/Applications/Calculator/Calculator.cpp +++ b/Userland/Applications/Calculator/Calculator.cpp @@ -10,9 +10,13 @@ #include <AK/Math.h> #include <LibCrypto/BigFraction/BigFraction.h> -Crypto::BigFraction Calculator::begin_operation(Operation operation, Crypto::BigFraction argument) +Optional<Crypto::BigFraction> Calculator::operation_with_literal_argument(Operation operation, Crypto::BigFraction argument) { - Crypto::BigFraction res {}; + // If a previous operation is still in progress, finish it + // Makes hitting "1+2+3=" equivalent to hitting "1+2=+3=" + if (m_binary_operation_in_progress != Operation::None) { + argument = finish_binary_operation(m_binary_operation_saved_left_side, m_binary_operation_in_progress, argument); + } switch (operation) { case Operation::None: @@ -22,78 +26,117 @@ Crypto::BigFraction Calculator::begin_operation(Operation operation, Crypto::Big case Operation::Subtract: case Operation::Multiply: case Operation::Divide: - m_saved_argument = argument; - m_operation_in_progress = operation; - return argument; + m_binary_operation_saved_left_side = argument; + m_binary_operation_in_progress = operation; + m_current_value = argument; + break; case Operation::Sqrt: if (argument < Crypto::BigFraction {}) { m_has_error = true; - return argument; + m_current_value = argument; + break; } - res = argument.sqrt(); + m_current_value = argument.sqrt(); clear_operation(); break; case Operation::Inverse: if (argument == Crypto::BigFraction {}) { m_has_error = true; - return argument; + m_current_value = argument; + break; } - res = argument.invert(); + m_current_value = argument.invert(); clear_operation(); break; case Operation::Percent: - res = argument * Crypto::BigFraction { 1, 100 }; + m_current_value = argument * Crypto::BigFraction { 1, 100 }; break; case Operation::ToggleSign: - res = -argument; + m_current_value = -argument; break; case Operation::MemClear: m_mem.set_to_0(); - res = argument; + m_current_value = argument; break; case Operation::MemRecall: - res = m_mem; + m_current_value = m_mem; break; case Operation::MemSave: m_mem = argument; - res = argument; + m_current_value = argument; break; case Operation::MemAdd: m_mem = m_mem + argument; // avoids the need for operator+=() - res = m_mem; + m_current_value = m_mem; + break; + case Operation::Equals: + m_current_value = argument; break; } - return res; + return m_current_value; +} + +static bool operation_is_binary(Calculator::Operation operation) +{ + switch (operation) { + + case Calculator::Operation::Add: + case Calculator::Operation::Subtract: + case Calculator::Operation::Multiply: + case Calculator::Operation::Divide: + return true; + + default: + return false; + } } -Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument) +Optional<Crypto::BigFraction> Calculator::operation_without_argument(Operation operation) +{ + bool in_binary_operation = m_binary_operation_in_progress != Operation::None; + bool entering_new_binary_operation = operation_is_binary(operation); + bool previous_operation_was_binary = operation_is_binary(m_previous_operation); + + if (in_binary_operation && entering_new_binary_operation) { + m_binary_operation_in_progress = operation; + return {}; + } + if (!in_binary_operation && previous_operation_was_binary && operation == Operation::Equals) { + m_current_value = finish_binary_operation(m_current_value, m_previous_operation, m_previous_binary_operation_right_side); + return m_current_value; + } + return operation_with_literal_argument(operation, m_current_value); +} + +Crypto::BigFraction Calculator::finish_binary_operation(Crypto::BigFraction const& left_side, Operation operation, Crypto::BigFraction const& right_side) { Crypto::BigFraction res {}; - switch (m_operation_in_progress) { - case Operation::None: - return argument; + m_previous_binary_operation_right_side = right_side; + + switch (operation) { case Operation::Add: - res = m_saved_argument + argument; + res = left_side + right_side; break; case Operation::Subtract: - res = m_saved_argument - argument; + res = left_side - right_side; break; case Operation::Multiply: - res = m_saved_argument * argument; + res = left_side * right_side; break; case Operation::Divide: - if (argument == Crypto::BigFraction {}) { + if (right_side == Crypto::BigFraction {}) { m_has_error = true; - return argument; + } else { + res = left_side / right_side; } - res = m_saved_argument / argument; break; + case Operation::None: case Operation::Sqrt: case Operation::Inverse: case Operation::Percent: @@ -102,6 +145,7 @@ Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument) case Operation::MemRecall: case Operation::MemSave: case Operation::MemAdd: + case Operation::Equals: VERIFY_NOT_REACHED(); } @@ -111,7 +155,10 @@ Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument) void Calculator::clear_operation() { - m_operation_in_progress = Operation::None; - m_saved_argument.set_to_0(); + if (m_binary_operation_in_progress != Operation::None) { + m_previous_operation = m_binary_operation_in_progress; + m_binary_operation_in_progress = Operation::None; + } + m_binary_operation_saved_left_side.set_to_0(); clear_error(); } diff --git a/Userland/Applications/Calculator/Calculator.h b/Userland/Applications/Calculator/Calculator.h index 5d995bf7e1..7d249a4539 100644 --- a/Userland/Applications/Calculator/Calculator.h +++ b/Userland/Applications/Calculator/Calculator.h @@ -7,6 +7,7 @@ #pragma once +#include <AK/Optional.h> #include <LibCrypto/BigFraction/BigFraction.h> // This type implements the regular calculator @@ -36,11 +37,13 @@ public: MemClear, MemRecall, MemSave, - MemAdd + MemAdd, + + Equals }; - Crypto::BigFraction begin_operation(Operation, Crypto::BigFraction); - Crypto::BigFraction finish_operation(Crypto::BigFraction); + Optional<Crypto::BigFraction> operation_with_literal_argument(Operation, Crypto::BigFraction); + Optional<Crypto::BigFraction> operation_without_argument(Operation); bool has_error() const { return m_has_error; } @@ -48,8 +51,16 @@ public: void clear_error() { m_has_error = false; } private: - Operation m_operation_in_progress { Operation::None }; - Crypto::BigFraction m_saved_argument {}; Crypto::BigFraction m_mem {}; + + Crypto::BigFraction m_current_value {}; + + Operation m_binary_operation_in_progress { Operation::None }; + Crypto::BigFraction m_binary_operation_saved_left_side {}; + + Operation m_previous_operation { Operation::None }; + Crypto::BigFraction m_previous_binary_operation_right_side {}; bool m_has_error { false }; + + Crypto::BigFraction finish_binary_operation(Crypto::BigFraction const& left_side, Operation operation, Crypto::BigFraction const& right_side); }; diff --git a/Userland/Applications/Calculator/CalculatorWidget.cpp b/Userland/Applications/Calculator/CalculatorWidget.cpp index c1129e5e07..a6615211a4 100644 --- a/Userland/Applications/Calculator/CalculatorWidget.cpp +++ b/Userland/Applications/Calculator/CalculatorWidget.cpp @@ -97,19 +97,22 @@ CalculatorWidget::CalculatorWidget() add_operation_button(*m_percent_button, Calculator::Operation::Percent); m_equals_button = *find_descendant_of_type_named<GUI::Button>("equal_button"); - m_equals_button->on_click = [this](auto) { - Crypto::BigFraction argument = m_keypad.value(); - Crypto::BigFraction res = m_calculator.finish_operation(move(argument)); - m_keypad.set_value(move(res)); - update_display(); - }; + add_operation_button(*m_equals_button, Calculator::Operation::Equals); } void CalculatorWidget::perform_operation(Calculator::Operation operation) { - Crypto::BigFraction argument = m_keypad.value(); - Crypto::BigFraction res = m_calculator.begin_operation(operation, move(argument)); - m_keypad.set_value(move(res)); + Optional<Crypto::BigFraction> res; + if (m_keypad.in_typing_state()) { + Crypto::BigFraction argument = m_keypad.value(); + res = m_calculator.operation_with_literal_argument(operation, move(argument)); + } else { + res = m_calculator.operation_without_argument(operation); + } + + if (res.has_value()) { + m_keypad.set_value(move(res.value())); + } update_display(); } @@ -156,7 +159,7 @@ void CalculatorWidget::update_display() void CalculatorWidget::keydown_event(GUI::KeyEvent& event) { if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Equal) { - m_keypad.set_value(m_calculator.finish_operation(m_keypad.value())); + perform_operation(Calculator::Operation::Equals); mimic_pressed_button(m_equals_button); } else if (event.code_point() >= '0' && event.code_point() <= '9') { auto const digit = event.code_point() - '0'; @@ -184,35 +187,21 @@ void CalculatorWidget::keydown_event(GUI::KeyEvent& event) } else if (event.key() == KeyCode::Key_I) { perform_operation(Calculator::Operation::Inverse); mimic_pressed_button(m_inverse_button); - } else { - Calculator::Operation operation; - - switch (event.code_point()) { - case '+': - operation = Calculator::Operation::Add; - mimic_pressed_button(m_add_button); - break; - case '-': - operation = Calculator::Operation::Subtract; - mimic_pressed_button(m_subtract_button); - break; - case '*': - operation = Calculator::Operation::Multiply; - mimic_pressed_button(m_multiply_button); - break; - case '/': - operation = Calculator::Operation::Divide; - mimic_pressed_button(m_divide_button); - break; - case '%': - operation = Calculator::Operation::Percent; - mimic_pressed_button(m_percent_button); - break; - default: - return; - } - - m_keypad.set_value(m_calculator.begin_operation(operation, m_keypad.value())); + } else if (event.code_point() == '+') { + perform_operation(Calculator::Operation::Add); + mimic_pressed_button(m_add_button); + } else if (event.code_point() == '-') { + perform_operation(Calculator::Operation::Subtract); + mimic_pressed_button(m_subtract_button); + } else if (event.code_point() == '*') { + perform_operation(Calculator::Operation::Multiply); + mimic_pressed_button(m_multiply_button); + } else if (event.code_point() == '/') { + perform_operation(Calculator::Operation::Divide); + mimic_pressed_button(m_divide_button); + } else if (event.code_point() == '%') { + perform_operation(Calculator::Operation::Percent); + mimic_pressed_button(m_percent_button); } update_display(); diff --git a/Userland/Applications/Calculator/Keypad.cpp b/Userland/Applications/Calculator/Keypad.cpp index c116c02580..66c911a771 100644 --- a/Userland/Applications/Calculator/Keypad.cpp +++ b/Userland/Applications/Calculator/Keypad.cpp @@ -135,6 +135,11 @@ DeprecatedString Keypad::to_deprecated_string() const return builder.to_deprecated_string(); } +bool Keypad::in_typing_state() const +{ + return m_state == State::TypingDecimal || m_state == State::TypingInteger; +} + void Keypad::set_rounding_length(unsigned rounding_threshold) { m_displayed_fraction_length = rounding_threshold; diff --git a/Userland/Applications/Calculator/Keypad.h b/Userland/Applications/Calculator/Keypad.h index 62a993a76d..af32de260f 100644 --- a/Userland/Applications/Calculator/Keypad.h +++ b/Userland/Applications/Calculator/Keypad.h @@ -35,6 +35,8 @@ public: DeprecatedString to_deprecated_string() const; + bool in_typing_state() const; + private: // Internal representation of the current decimal value. // Those variables are only used when the user is entering a value. |