1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-24 11:25:07 +00:00
serenity/Userland/Applications/Calculator/CalculatorWidget.cpp
Lucas CHOLLET 53eb35caba Calculator: Change internal representation to support perfect division
The purpose of this patch is to support addition, subtraction,
multiplication and division without using conversion to double. To this
end, we use the BigFraction class of LibCrypto. With this solution, we
can store values without any losses and forward rounding as the last
step before displaying.
2022-09-15 14:08:21 +01:00

220 lines
8 KiB
C++

/*
* Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
* Copyright (c) 2021, Glenford Williams <gw_dev@outlook.com>
* Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CalculatorWidget.h"
#include <Applications/Calculator/CalculatorGML.h>
#include <LibCore/Event.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibGUI/Button.h>
#include <LibGUI/Label.h>
#include <LibGUI/TextBox.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Palette.h>
CalculatorWidget::CalculatorWidget()
{
load_from_gml(calculator_gml);
m_entry = *find_descendant_of_type_named<GUI::TextBox>("entry_textbox");
m_entry->set_relative_rect(5, 5, 244, 26);
m_entry->set_text_alignment(Gfx::TextAlignment::CenterRight);
m_label = *find_descendant_of_type_named<GUI::Label>("label");
m_label->set_frame_shadow(Gfx::FrameShadow::Sunken);
m_label->set_frame_shape(Gfx::FrameShape::Container);
m_label->set_frame_thickness(2);
for (int i = 0; i < 10; i++) {
m_digit_button[i] = *find_descendant_of_type_named<GUI::Button>(String::formatted("{}_button", i));
add_digit_button(*m_digit_button[i], i);
}
m_mem_add_button = *find_descendant_of_type_named<GUI::Button>("mem_add_button");
add_operation_button(*m_mem_add_button, Calculator::Operation::MemAdd);
m_mem_save_button = *find_descendant_of_type_named<GUI::Button>("mem_save_button");
add_operation_button(*m_mem_save_button, Calculator::Operation::MemSave);
m_mem_recall_button = *find_descendant_of_type_named<GUI::Button>("mem_recall_button");
add_operation_button(*m_mem_recall_button, Calculator::Operation::MemRecall);
m_mem_clear_button = *find_descendant_of_type_named<GUI::Button>("mem_clear_button");
add_operation_button(*m_mem_clear_button, Calculator::Operation::MemClear);
m_clear_button = *find_descendant_of_type_named<GUI::Button>("clear_button");
m_clear_button->on_click = [this](auto) {
m_keypad.set_to_0();
m_calculator.clear_operation();
update_display();
};
m_clear_error_button = *find_descendant_of_type_named<GUI::Button>("clear_error_button");
m_clear_error_button->on_click = [this](auto) {
m_keypad.set_to_0();
update_display();
};
m_backspace_button = *find_descendant_of_type_named<GUI::Button>("backspace_button");
m_backspace_button->on_click = [this](auto) {
m_keypad.type_backspace();
update_display();
};
m_decimal_point_button = *find_descendant_of_type_named<GUI::Button>("decimal_button");
m_decimal_point_button->on_click = [this](auto) {
m_keypad.type_decimal_point();
update_display();
};
m_sign_button = *find_descendant_of_type_named<GUI::Button>("sign_button");
add_operation_button(*m_sign_button, Calculator::Operation::ToggleSign);
m_add_button = *find_descendant_of_type_named<GUI::Button>("add_button");
add_operation_button(*m_add_button, Calculator::Operation::Add);
m_subtract_button = *find_descendant_of_type_named<GUI::Button>("subtract_button");
add_operation_button(*m_subtract_button, Calculator::Operation::Subtract);
m_multiply_button = *find_descendant_of_type_named<GUI::Button>("multiply_button");
add_operation_button(*m_multiply_button, Calculator::Operation::Multiply);
m_divide_button = *find_descendant_of_type_named<GUI::Button>("divide_button");
add_operation_button(*m_divide_button, Calculator::Operation::Divide);
m_sqrt_button = *find_descendant_of_type_named<GUI::Button>("sqrt_button");
add_operation_button(*m_sqrt_button, Calculator::Operation::Sqrt);
m_inverse_button = *find_descendant_of_type_named<GUI::Button>("inverse_button");
add_operation_button(*m_inverse_button, Calculator::Operation::Inverse);
m_percent_button = *find_descendant_of_type_named<GUI::Button>("mod_button");
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();
};
}
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));
update_display();
}
void CalculatorWidget::add_operation_button(GUI::Button& button, Calculator::Operation operation)
{
button.on_click = [this, operation](auto) {
perform_operation(operation);
};
}
void CalculatorWidget::add_digit_button(GUI::Button& button, int digit)
{
button.on_click = [this, digit](auto) {
m_keypad.type_digit(digit);
update_display();
};
}
String CalculatorWidget::get_entry()
{
return m_entry->text();
}
void CalculatorWidget::set_entry(Crypto::BigFraction value)
{
m_keypad.set_value(move(value));
update_display();
}
void CalculatorWidget::mimic_pressed_button(RefPtr<GUI::Button> button)
{
button->set_mimic_pressed(true);
}
void CalculatorWidget::update_display()
{
m_entry->set_text(m_keypad.to_string());
if (m_calculator.has_error())
m_label->set_text("E");
else
m_label->set_text("");
}
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()));
mimic_pressed_button(m_equals_button);
} else if (event.code_point() >= '0' && event.code_point() <= '9') {
u32 digit = event.code_point() - '0';
m_keypad.type_digit(digit);
mimic_pressed_button(m_digit_button[digit]);
} else if (event.code_point() == '.') {
m_keypad.type_decimal_point();
mimic_pressed_button(m_decimal_point_button);
} else if (event.key() == KeyCode::Key_Escape || event.key() == KeyCode::Key_Delete) {
m_keypad.set_to_0();
m_calculator.clear_operation();
mimic_pressed_button(m_clear_button);
} else if (event.key() == KeyCode::Key_Backspace) {
m_keypad.type_backspace();
mimic_pressed_button(m_backspace_button);
} else if (event.key() == KeyCode::Key_Backslash) {
perform_operation(Calculator::Operation::ToggleSign);
mimic_pressed_button(m_sign_button);
} else if (event.key() == KeyCode::Key_S) {
perform_operation(Calculator::Operation::Sqrt);
mimic_pressed_button(m_sqrt_button);
} else if (event.key() == KeyCode::Key_Percent) {
perform_operation(Calculator::Operation::Percent);
mimic_pressed_button(m_percent_button);
} 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()));
}
update_display();
}