mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 04:57:45 +00:00
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. :^)
This commit is contained in:
parent
ef9fd6c286
commit
21cc8f65f5
5 changed files with 126 additions and 72 deletions
|
@ -10,9 +10,13 @@
|
||||||
#include <AK/Math.h>
|
#include <AK/Math.h>
|
||||||
#include <LibCrypto/BigFraction/BigFraction.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) {
|
switch (operation) {
|
||||||
case Operation::None:
|
case Operation::None:
|
||||||
|
@ -22,78 +26,117 @@ Crypto::BigFraction Calculator::begin_operation(Operation operation, Crypto::Big
|
||||||
case Operation::Subtract:
|
case Operation::Subtract:
|
||||||
case Operation::Multiply:
|
case Operation::Multiply:
|
||||||
case Operation::Divide:
|
case Operation::Divide:
|
||||||
m_saved_argument = argument;
|
m_binary_operation_saved_left_side = argument;
|
||||||
m_operation_in_progress = operation;
|
m_binary_operation_in_progress = operation;
|
||||||
return argument;
|
m_current_value = argument;
|
||||||
|
break;
|
||||||
|
|
||||||
case Operation::Sqrt:
|
case Operation::Sqrt:
|
||||||
if (argument < Crypto::BigFraction {}) {
|
if (argument < Crypto::BigFraction {}) {
|
||||||
m_has_error = true;
|
m_has_error = true;
|
||||||
return argument;
|
m_current_value = argument;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
res = argument.sqrt();
|
m_current_value = argument.sqrt();
|
||||||
clear_operation();
|
clear_operation();
|
||||||
break;
|
break;
|
||||||
case Operation::Inverse:
|
case Operation::Inverse:
|
||||||
if (argument == Crypto::BigFraction {}) {
|
if (argument == Crypto::BigFraction {}) {
|
||||||
m_has_error = true;
|
m_has_error = true;
|
||||||
return argument;
|
m_current_value = argument;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
res = argument.invert();
|
m_current_value = argument.invert();
|
||||||
clear_operation();
|
clear_operation();
|
||||||
break;
|
break;
|
||||||
case Operation::Percent:
|
case Operation::Percent:
|
||||||
res = argument * Crypto::BigFraction { 1, 100 };
|
m_current_value = argument * Crypto::BigFraction { 1, 100 };
|
||||||
break;
|
break;
|
||||||
case Operation::ToggleSign:
|
case Operation::ToggleSign:
|
||||||
res = -argument;
|
m_current_value = -argument;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Operation::MemClear:
|
case Operation::MemClear:
|
||||||
m_mem.set_to_0();
|
m_mem.set_to_0();
|
||||||
res = argument;
|
m_current_value = argument;
|
||||||
break;
|
break;
|
||||||
case Operation::MemRecall:
|
case Operation::MemRecall:
|
||||||
res = m_mem;
|
m_current_value = m_mem;
|
||||||
break;
|
break;
|
||||||
case Operation::MemSave:
|
case Operation::MemSave:
|
||||||
m_mem = argument;
|
m_mem = argument;
|
||||||
res = argument;
|
m_current_value = argument;
|
||||||
break;
|
break;
|
||||||
case Operation::MemAdd:
|
case Operation::MemAdd:
|
||||||
m_mem = m_mem + argument; // avoids the need for operator+=()
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return m_current_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument)
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {};
|
Crypto::BigFraction res {};
|
||||||
|
|
||||||
switch (m_operation_in_progress) {
|
m_previous_binary_operation_right_side = right_side;
|
||||||
case Operation::None:
|
|
||||||
return argument;
|
switch (operation) {
|
||||||
|
|
||||||
case Operation::Add:
|
case Operation::Add:
|
||||||
res = m_saved_argument + argument;
|
res = left_side + right_side;
|
||||||
break;
|
break;
|
||||||
case Operation::Subtract:
|
case Operation::Subtract:
|
||||||
res = m_saved_argument - argument;
|
res = left_side - right_side;
|
||||||
break;
|
break;
|
||||||
case Operation::Multiply:
|
case Operation::Multiply:
|
||||||
res = m_saved_argument * argument;
|
res = left_side * right_side;
|
||||||
break;
|
break;
|
||||||
case Operation::Divide:
|
case Operation::Divide:
|
||||||
if (argument == Crypto::BigFraction {}) {
|
if (right_side == Crypto::BigFraction {}) {
|
||||||
m_has_error = true;
|
m_has_error = true;
|
||||||
return argument;
|
} else {
|
||||||
|
res = left_side / right_side;
|
||||||
}
|
}
|
||||||
res = m_saved_argument / argument;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Operation::None:
|
||||||
case Operation::Sqrt:
|
case Operation::Sqrt:
|
||||||
case Operation::Inverse:
|
case Operation::Inverse:
|
||||||
case Operation::Percent:
|
case Operation::Percent:
|
||||||
|
@ -102,6 +145,7 @@ Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument)
|
||||||
case Operation::MemRecall:
|
case Operation::MemRecall:
|
||||||
case Operation::MemSave:
|
case Operation::MemSave:
|
||||||
case Operation::MemAdd:
|
case Operation::MemAdd:
|
||||||
|
case Operation::Equals:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +155,10 @@ Crypto::BigFraction Calculator::finish_operation(Crypto::BigFraction argument)
|
||||||
|
|
||||||
void Calculator::clear_operation()
|
void Calculator::clear_operation()
|
||||||
{
|
{
|
||||||
m_operation_in_progress = Operation::None;
|
if (m_binary_operation_in_progress != Operation::None) {
|
||||||
m_saved_argument.set_to_0();
|
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();
|
clear_error();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Optional.h>
|
||||||
#include <LibCrypto/BigFraction/BigFraction.h>
|
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||||
|
|
||||||
// This type implements the regular calculator
|
// This type implements the regular calculator
|
||||||
|
@ -36,11 +37,13 @@ public:
|
||||||
MemClear,
|
MemClear,
|
||||||
MemRecall,
|
MemRecall,
|
||||||
MemSave,
|
MemSave,
|
||||||
MemAdd
|
MemAdd,
|
||||||
|
|
||||||
|
Equals
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto::BigFraction begin_operation(Operation, Crypto::BigFraction);
|
Optional<Crypto::BigFraction> operation_with_literal_argument(Operation, Crypto::BigFraction);
|
||||||
Crypto::BigFraction finish_operation(Crypto::BigFraction);
|
Optional<Crypto::BigFraction> operation_without_argument(Operation);
|
||||||
|
|
||||||
bool has_error() const { return m_has_error; }
|
bool has_error() const { return m_has_error; }
|
||||||
|
|
||||||
|
@ -48,8 +51,16 @@ public:
|
||||||
void clear_error() { m_has_error = false; }
|
void clear_error() { m_has_error = false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Operation m_operation_in_progress { Operation::None };
|
|
||||||
Crypto::BigFraction m_saved_argument {};
|
|
||||||
Crypto::BigFraction m_mem {};
|
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 };
|
bool m_has_error { false };
|
||||||
|
|
||||||
|
Crypto::BigFraction finish_binary_operation(Crypto::BigFraction const& left_side, Operation operation, Crypto::BigFraction const& right_side);
|
||||||
};
|
};
|
||||||
|
|
|
@ -97,19 +97,22 @@ CalculatorWidget::CalculatorWidget()
|
||||||
add_operation_button(*m_percent_button, Calculator::Operation::Percent);
|
add_operation_button(*m_percent_button, Calculator::Operation::Percent);
|
||||||
|
|
||||||
m_equals_button = *find_descendant_of_type_named<GUI::Button>("equal_button");
|
m_equals_button = *find_descendant_of_type_named<GUI::Button>("equal_button");
|
||||||
m_equals_button->on_click = [this](auto) {
|
add_operation_button(*m_equals_button, Calculator::Operation::Equals);
|
||||||
Crypto::BigFraction argument = m_keypad.value();
|
|
||||||
Crypto::BigFraction res = m_calculator.finish_operation(move(argument));
|
|
||||||
m_keypad.set_value(move(res));
|
|
||||||
update_display();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CalculatorWidget::perform_operation(Calculator::Operation operation)
|
void CalculatorWidget::perform_operation(Calculator::Operation operation)
|
||||||
{
|
{
|
||||||
|
Optional<Crypto::BigFraction> res;
|
||||||
|
if (m_keypad.in_typing_state()) {
|
||||||
Crypto::BigFraction argument = m_keypad.value();
|
Crypto::BigFraction argument = m_keypad.value();
|
||||||
Crypto::BigFraction res = m_calculator.begin_operation(operation, move(argument));
|
res = m_calculator.operation_with_literal_argument(operation, move(argument));
|
||||||
m_keypad.set_value(move(res));
|
} else {
|
||||||
|
res = m_calculator.operation_without_argument(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.has_value()) {
|
||||||
|
m_keypad.set_value(move(res.value()));
|
||||||
|
}
|
||||||
update_display();
|
update_display();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +159,7 @@ void CalculatorWidget::update_display()
|
||||||
void CalculatorWidget::keydown_event(GUI::KeyEvent& event)
|
void CalculatorWidget::keydown_event(GUI::KeyEvent& event)
|
||||||
{
|
{
|
||||||
if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Equal) {
|
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);
|
mimic_pressed_button(m_equals_button);
|
||||||
} else if (event.code_point() >= '0' && event.code_point() <= '9') {
|
} else if (event.code_point() >= '0' && event.code_point() <= '9') {
|
||||||
auto const digit = event.code_point() - '0';
|
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) {
|
} else if (event.key() == KeyCode::Key_I) {
|
||||||
perform_operation(Calculator::Operation::Inverse);
|
perform_operation(Calculator::Operation::Inverse);
|
||||||
mimic_pressed_button(m_inverse_button);
|
mimic_pressed_button(m_inverse_button);
|
||||||
} else {
|
} else if (event.code_point() == '+') {
|
||||||
Calculator::Operation operation;
|
perform_operation(Calculator::Operation::Add);
|
||||||
|
|
||||||
switch (event.code_point()) {
|
|
||||||
case '+':
|
|
||||||
operation = Calculator::Operation::Add;
|
|
||||||
mimic_pressed_button(m_add_button);
|
mimic_pressed_button(m_add_button);
|
||||||
break;
|
} else if (event.code_point() == '-') {
|
||||||
case '-':
|
perform_operation(Calculator::Operation::Subtract);
|
||||||
operation = Calculator::Operation::Subtract;
|
|
||||||
mimic_pressed_button(m_subtract_button);
|
mimic_pressed_button(m_subtract_button);
|
||||||
break;
|
} else if (event.code_point() == '*') {
|
||||||
case '*':
|
perform_operation(Calculator::Operation::Multiply);
|
||||||
operation = Calculator::Operation::Multiply;
|
|
||||||
mimic_pressed_button(m_multiply_button);
|
mimic_pressed_button(m_multiply_button);
|
||||||
break;
|
} else if (event.code_point() == '/') {
|
||||||
case '/':
|
perform_operation(Calculator::Operation::Divide);
|
||||||
operation = Calculator::Operation::Divide;
|
|
||||||
mimic_pressed_button(m_divide_button);
|
mimic_pressed_button(m_divide_button);
|
||||||
break;
|
} else if (event.code_point() == '%') {
|
||||||
case '%':
|
perform_operation(Calculator::Operation::Percent);
|
||||||
operation = Calculator::Operation::Percent;
|
|
||||||
mimic_pressed_button(m_percent_button);
|
mimic_pressed_button(m_percent_button);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_keypad.set_value(m_calculator.begin_operation(operation, m_keypad.value()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update_display();
|
update_display();
|
||||||
|
|
|
@ -135,6 +135,11 @@ DeprecatedString Keypad::to_deprecated_string() const
|
||||||
return builder.to_deprecated_string();
|
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)
|
void Keypad::set_rounding_length(unsigned rounding_threshold)
|
||||||
{
|
{
|
||||||
m_displayed_fraction_length = rounding_threshold;
|
m_displayed_fraction_length = rounding_threshold;
|
||||||
|
|
|
@ -35,6 +35,8 @@ public:
|
||||||
|
|
||||||
DeprecatedString to_deprecated_string() const;
|
DeprecatedString to_deprecated_string() const;
|
||||||
|
|
||||||
|
bool in_typing_state() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Internal representation of the current decimal value.
|
// Internal representation of the current decimal value.
|
||||||
// Those variables are only used when the user is entering a value.
|
// Those variables are only used when the user is entering a value.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue