Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "CalculatorWidget.h"
28#include <AK/Assertions.h>
29#include <LibGUI/Button.h>
30#include <LibGUI/Label.h>
31#include <LibGUI/TextBox.h>
32
33CalculatorWidget::CalculatorWidget()
34{
35 set_fill_with_background_color(true);
36
37 m_entry = add<GUI::TextBox>();
38 m_entry->set_relative_rect(5, 5, 244, 26);
39 m_entry->set_text_alignment(Gfx::TextAlignment::CenterRight);
40
41 m_label = add<GUI::Label>();
42 m_label->set_relative_rect(12, 42, 27, 27);
43 m_label->set_foreground_color(Color::NamedColor::Red);
44 m_label->set_frame_shadow(Gfx::FrameShadow::Sunken);
45 m_label->set_frame_shape(Gfx::FrameShape::Container);
46 m_label->set_frame_thickness(2);
47
48 update_display();
49
50 for (int i = 0; i < 10; i++) {
51 m_digit_button[i] = add<GUI::Button>();
52 auto& button = *m_digit_button[i];
53 int p = i ? i + 2 : 0;
54 int x = 55 + (p % 3) * 39;
55 int y = 177 - (p / 3) * 33;
56 button.move_to(x, y);
57 button.set_foreground_color(Color::NamedColor::Blue);
58 add_button(button, i);
59 }
60
61 m_mem_add_button = add<GUI::Button>();
62 m_mem_add_button->move_to(9, 177);
63 m_mem_add_button->set_foreground_color(Color::NamedColor::Red);
64 m_mem_add_button->set_text("M+");
65 add_button(*m_mem_add_button, Calculator::Operation::MemAdd);
66
67 m_mem_save_button = add<GUI::Button>();
68 m_mem_save_button->move_to(9, 144);
69 m_mem_save_button->set_foreground_color(Color::NamedColor::Red);
70 m_mem_save_button->set_text("MS");
71 add_button(*m_mem_save_button, Calculator::Operation::MemSave);
72
73 m_mem_recall_button = add<GUI::Button>();
74 m_mem_recall_button->move_to(9, 111);
75 m_mem_recall_button->set_foreground_color(Color::NamedColor::Red);
76 m_mem_recall_button->set_text("MR");
77 add_button(*m_mem_recall_button, Calculator::Operation::MemRecall);
78
79 m_mem_clear_button = add<GUI::Button>();
80 m_mem_clear_button->move_to(9, 78);
81 m_mem_clear_button->set_foreground_color(Color::NamedColor::Red);
82 m_mem_clear_button->set_text("MC");
83 add_button(*m_mem_clear_button, Calculator::Operation::MemClear);
84
85 m_clear_button = add<GUI::Button>();
86 m_clear_button->set_foreground_color(Color::NamedColor::Red);
87 m_clear_button->set_text("C");
88 m_clear_button->on_click = [this] {
89 m_keypad.set_value(0.0);
90 m_calculator.clear_operation();
91 update_display();
92 };
93 add_button(*m_clear_button);
94 m_clear_button->set_relative_rect(187, 40, 60, 28);
95
96 m_clear_error_button = add<GUI::Button>();
97 m_clear_error_button->set_foreground_color(Color::NamedColor::Red);
98 m_clear_error_button->set_text("CE");
99 m_clear_error_button->on_click = [this] {
100 m_calculator.clear_error();
101 update_display();
102 };
103 add_button(*m_clear_error_button);
104 m_clear_error_button->set_relative_rect(124, 40, 59, 28);
105
106 m_backspace_button = add<GUI::Button>();
107 m_backspace_button->set_foreground_color(Color::NamedColor::Red);
108 m_backspace_button->set_text("Backspace");
109 m_backspace_button->on_click = [this] {
110 m_keypad.type_backspace();
111 update_display();
112 };
113 add_button(*m_backspace_button);
114 m_backspace_button->set_relative_rect(55, 40, 65, 28);
115
116 m_decimal_point_button = add<GUI::Button>();
117 m_decimal_point_button->move_to(133, 177);
118 m_decimal_point_button->set_foreground_color(Color::NamedColor::Blue);
119 m_decimal_point_button->set_text(".");
120 m_decimal_point_button->on_click = [this] {
121 m_keypad.type_decimal_point();
122 update_display();
123 };
124 add_button(*m_decimal_point_button);
125
126 m_sign_button = add<GUI::Button>();
127 m_sign_button->move_to(94, 177);
128 m_sign_button->set_foreground_color(Color::NamedColor::Blue);
129 m_sign_button->set_text("+/-");
130 add_button(*m_sign_button, Calculator::Operation::ToggleSign);
131
132 m_add_button = add<GUI::Button>();
133 m_add_button->move_to(172, 177);
134 m_add_button->set_foreground_color(Color::NamedColor::Red);
135 m_add_button->set_text("+");
136 add_button(*m_add_button, Calculator::Operation::Add);
137
138 m_subtract_button = add<GUI::Button>();
139 m_subtract_button->move_to(172, 144);
140 m_subtract_button->set_foreground_color(Color::NamedColor::Red);
141 m_subtract_button->set_text("-");
142 add_button(*m_subtract_button, Calculator::Operation::Subtract);
143
144 m_multiply_button = add<GUI::Button>();
145 m_multiply_button->move_to(172, 111);
146 m_multiply_button->set_foreground_color(Color::NamedColor::Red);
147 m_multiply_button->set_text("*");
148 add_button(*m_multiply_button, Calculator::Operation::Multiply);
149
150 m_divide_button = add<GUI::Button>();
151 m_divide_button->move_to(172, 78);
152 m_divide_button->set_foreground_color(Color::NamedColor::Red);
153 m_divide_button->set_text("/");
154 add_button(*m_divide_button, Calculator::Operation::Divide);
155
156 m_sqrt_button = add<GUI::Button>();
157 m_sqrt_button->move_to(211, 78);
158 m_sqrt_button->set_foreground_color(Color::NamedColor::Blue);
159 m_sqrt_button->set_text("sqrt");
160 add_button(*m_sqrt_button, Calculator::Operation::Sqrt);
161
162 m_inverse_button = add<GUI::Button>();
163 m_inverse_button->move_to(211, 144);
164 m_inverse_button->set_foreground_color(Color::NamedColor::Blue);
165 m_inverse_button->set_text("1/x");
166 add_button(*m_inverse_button, Calculator::Operation::Inverse);
167
168 m_percent_button = add<GUI::Button>();
169 m_percent_button->move_to(211, 111);
170 m_percent_button->set_foreground_color(Color::NamedColor::Blue);
171 m_percent_button->set_text("%");
172 add_button(*m_percent_button, Calculator::Operation::Percent);
173
174 m_equals_button = add<GUI::Button>();
175 m_equals_button->move_to(211, 177);
176 m_equals_button->set_foreground_color(Color::NamedColor::Red);
177 m_equals_button->set_text("=");
178 m_equals_button->on_click = [this] {
179 double argument = m_keypad.value();
180 double res = m_calculator.finish_operation(argument);
181 m_keypad.set_value(res);
182 update_display();
183 };
184 add_button(*m_equals_button);
185}
186
187CalculatorWidget::~CalculatorWidget()
188{
189}
190
191void CalculatorWidget::add_button(GUI::Button& button, Calculator::Operation operation)
192{
193 add_button(button);
194 button.on_click = [this, operation] {
195 double argument = m_keypad.value();
196 double res = m_calculator.begin_operation(operation, argument);
197 m_keypad.set_value(res);
198 update_display();
199 };
200}
201
202void CalculatorWidget::add_button(GUI::Button& button, int digit)
203{
204 add_button(button);
205 button.set_text(String::number(digit));
206 button.on_click = [this, digit] {
207 m_keypad.type_digit(digit);
208 update_display();
209 };
210}
211
212void CalculatorWidget::add_button(GUI::Button& button)
213{
214 button.resize(35, 28);
215}
216
217void CalculatorWidget::update_display()
218{
219 m_entry->set_text(m_keypad.to_string());
220 if (m_calculator.has_error())
221 m_label->set_text("E");
222 else
223 m_label->set_text("");
224}
225
226void CalculatorWidget::keydown_event(GUI::KeyEvent& event)
227{
228 //Clear button selection when we are typing
229 m_equals_button->set_focus(true);
230 m_equals_button->set_focus(false);
231
232 if (event.key() == KeyCode::Key_Return) {
233 m_keypad.set_value(m_calculator.finish_operation(m_keypad.value()));
234
235 } else if (event.key() >= KeyCode::Key_0 && event.key() <= KeyCode::Key_9) {
236 m_keypad.type_digit(atoi(event.text().characters()));
237
238 } else if (event.key() == KeyCode::Key_Period) {
239 m_keypad.type_decimal_point();
240
241 } else if (event.key() == KeyCode::Key_Escape) {
242 m_keypad.set_value(0.0);
243 m_calculator.clear_operation();
244
245 } else if (event.key() == KeyCode::Key_Backspace) {
246 m_keypad.type_backspace();
247
248 } else {
249 Calculator::Operation operation;
250
251 switch (event.key()) {
252 case KeyCode::Key_Plus:
253 operation = Calculator::Operation::Add;
254 break;
255 case KeyCode::Key_Minus:
256 operation = Calculator::Operation::Subtract;
257 break;
258 case KeyCode::Key_Asterisk:
259 operation = Calculator::Operation::Multiply;
260 break;
261 case KeyCode::Key_Slash:
262 operation = Calculator::Operation::Divide;
263 break;
264 case KeyCode::Key_Percent:
265 operation = Calculator::Operation::Percent;
266 break;
267 default:
268 return;
269 }
270
271 m_keypad.set_value(m_calculator.begin_operation(operation, m_keypad.value()));
272 }
273
274 update_display();
275}