From e6ac1abda1c729b65525865276abbaeddecf087c Mon Sep 17 00:00:00 2001 From: Markus Gans Date: Mon, 22 Jun 2015 00:28:06 +0200 Subject: [PATCH] Add a simple calculator with trigonometric functions --- test/calculator.cpp | 948 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 948 insertions(+) create mode 100644 test/calculator.cpp diff --git a/test/calculator.cpp b/test/calculator.cpp new file mode 100644 index 00000000..3011fe5b --- /dev/null +++ b/test/calculator.cpp @@ -0,0 +1,948 @@ +// calculator.cpp + +//---------------------------------------------------------------------- +// A simple calculator with trigonometric functions +//---------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "fapp.h" +#include "fdialog.h" +#include "fmessagebox.h" + +const long double PI = 3.141592653589793238L; + +//---------------------------------------------------------------------- +// class Button +//---------------------------------------------------------------------- + +#pragma pack(push) +#pragma pack(1) + +class Button : public FButton +{ +private: + bool checked; + public: + explicit Button (FWidget* parent=0); // constructor + void setChecked(bool); + void onKeyPress (FKeyEvent*); +}; +#pragma pack(pop) + +//---------------------------------------------------------------------- +Button::Button (FWidget* parent) : FButton(parent) +{ + checked = false; +} + +//---------------------------------------------------------------------- +void Button::setChecked(bool on) +{ + if ( checked != on ) + { + checked = on; + + if ( checked ) + { + setBackgroundColor(fc::Cyan); + setFocusForegroundColor(fc::White); + setFocusBackgroundColor(fc::Cyan); + } + else + { + setBackgroundColor(wc.button_active_bg); + setFocusForegroundColor(wc.button_active_focus_fg); + setFocusBackgroundColor(wc.button_active_focus_bg); + } + redraw(); + } +} + +//---------------------------------------------------------------------- +void Button::onKeyPress (FKeyEvent* event) +{ + int key = event->key(); + + // catch the enter key + if ( key == fc::Fkey_return || key == fc::Fkey_return ) + return; + + FButton::onKeyPress(event); +} + + +//---------------------------------------------------------------------- +// class Calc +//---------------------------------------------------------------------- + +#pragma pack(push) +#pragma pack(1) + +class Calc : public FDialog +{ + private: + bool error; + bool arcus_mode; + bool hyperbolic_mode; + + enum button + { + Sine, + Cosine, + Tangent, + Reciprocal, + On, + Natural_logarithm, + Powers_of_e, + Power, + Square_root, + Divide, + Common_logarithm, + Powers_of_ten, + Parenthese_l, + Parenthese_r, + Multiply, + Hyperbolic, + Seven, + Eight, + Nine, + Subtract, + Arcus, + Four, + Five, + Six, + Add, + Pi, + One, + Two, + Three, + Percent, + Zero, + Decimal_point, + Change_sign, + Equals, + NUM_OF_BUTTONS + }; + + long double a, b; + uInt max_char; + int last_key; + char infix_operator; + char last_infix_operator; + FString input; + int button_no[Calc::NUM_OF_BUTTONS]; + + struct stack_data + { + long double term; + char infix_operator; + }; + + std::stack bracket_stack; + std::map calculator_buttons; + + private: + void drawDispay(); + virtual void draw(); + bool isDataEntryKey(int); + bool isOperatorKey(int); + void setDisplay (long double); + void setInfixOperator(char); + void clearInfixOperator(); + void calcInfixOperator(); + + public: + explicit Calc (FWidget* parent=0); // constructor + ~Calc(); // destructor + void onKeyPress (FKeyEvent*); + void onAccel (FAccelEvent*); + void onClose (FCloseEvent*); + void cb_buttonClicked (FWidget*, void*); + + protected: + void adjustSize(); +}; +#pragma pack(pop) + +//---------------------------------------------------------------------- +Calc::Calc (FWidget* parent) : FDialog(parent) +{ + error = false; + arcus_mode = false; + hyperbolic_mode = false; + input = ""; + clearInfixOperator(); + last_infix_operator = '\0'; + max_char = 32; + last_key = -1; + a = b = 0.0L; + + const wchar_t* button_text[Calc::NUM_OF_BUTTONS] = + { + L"&Sin", + L"&Cos", + L"&Tan", + L"1/&x", + L"&On", + L"L&n", + L"&e\x02e3", + L"&y\x02e3", + L"Sq&r", + L"&\xf7", + L"&Lg", + L"10&\x02e3", + L"&(", + L"&)", + L"&\xd7", + L"&Hyp", + L"&7", + L"&8", + L"&9", + L"&-", + L"&Arc", + L"&4", + L"&5", + L"&6", + L"&+", + L"&\x03c0", + L"&1", + L"&2", + L"&3", + L"&%", + L"&0", + L"&.", + L"&±", + L"&=" + }; + + setlocale(LC_NUMERIC, "C"); + + setText ("calculator"); + setGeometry (19, 6, 37, 18); + addAccelerator('q'); // press 'q' to quit + setShadow(); + + for (int key=0; key < Calc::NUM_OF_BUTTONS; key++) + { + Button* btn = new Button(this); + button_no[key] = key; + + if ( key == Equals ) + btn->setGeometry(30, 15, 5, 3); + else + { + int x, y, n; + (key <= Three) ? n=0 : n=1; + x = (key+n)%5*7 + 2; + y = (key+n)/5*2 + 3; + btn->setGeometry(x, y, 5, 1); + } + btn->setFlat(); + btn->setNoUnderline(); + btn->unsetClickAnimation(); + btn->setText(button_text[key]); + btn->setDoubleFlatLine(fc::top); + if ( key <= Three ) + btn->setDoubleFlatLine(fc::bottom); + + btn->addCallback + ( + "clicked", + this, + reinterpret_cast(&Calc::cb_buttonClicked), + &button_no[key] + ); + + calculator_buttons[button(key)] = btn; + + } + + calculator_buttons[On]->addAccelerator(fc::Fkey_dc); // del key + calculator_buttons[On]->setFocus(); + calculator_buttons[Pi]->addAccelerator('p'); + calculator_buttons[Power]->addAccelerator('^'); + calculator_buttons[Divide]->addAccelerator('/'); + calculator_buttons[Powers_of_ten]->addAccelerator('d'); + calculator_buttons[Multiply]->addAccelerator('*'); + calculator_buttons[Decimal_point]->addAccelerator(','); + calculator_buttons[Change_sign]->addAccelerator('#'); + calculator_buttons[Equals]->addAccelerator(fc::Fkey_return); + calculator_buttons[Equals]->addAccelerator(fc::Fkey_enter); + +} + +//---------------------------------------------------------------------- +Calc::~Calc() +{ +} + +//---------------------------------------------------------------------- +void Calc::drawDispay() +{ + FString display = input; + setUpdateVTerm(false); + + if ( display.isNull() || display.isEmpty() ) + display = L'0'; + + if ( display.right(3) == L"-0." ) + display = L'0'; + + if ( display.back() == L'.' && display.getLength() > 1 ) + display = display.left(display.getLength() - 1); + + if ( ! display.isEmpty() && display.getLength() < max_char ) + display.insert(FString(max_char - display.getLength(), L' '), 0); + + if ( display.getLength() > max_char ) + display = display.left(max_char); + + if ( infix_operator ) + display[1] = infix_operator; + + if ( error ) + display = " Error "; + + setColor(fc::Black, fc::LightGray); + gotoxy (xpos+xmin+1, ypos+ymin+1); + print(display); + print(L' '); + + setColor(fc::Black, fc::White); + if ( isNewFont() ) + { + FString bottom_line(33, wchar_t(fc::NF_border_line_bottom)); + gotoxy (xpos+xmin+1, ypos+ymin); + print(bottom_line); + gotoxy (xpos+xmin+0, ypos+ymin+1); + print (wchar_t(fc::NF_rev_border_line_right)); + gotoxy (xpos+xmin+34, ypos+ymin+1); + print (wchar_t(fc::NF_border_line_left)); + FString top_bottom_line_5(5, wchar_t(fc::NF_border_line_up_and_down)); + FString top_line_2(2, wchar_t(fc::NF_border_line_upper)); + gotoxy (xpos+xmin+1, ypos+ymin+2); + print ( top_bottom_line_5 + top_line_2 + + top_bottom_line_5 + top_line_2 + + top_bottom_line_5 + top_line_2 + + top_bottom_line_5 + top_line_2 + + top_bottom_line_5); + } + else + { + FString separator = FString(wchar_t(fc::BoxDrawingsVerticalAndRight)) + + FString(35, wchar_t(fc::BoxDrawingsHorizontal)) + + FString(wchar_t(fc::BoxDrawingsVerticalAndLeft)); + gotoxy (xpos+xmin-1, ypos+ymin+2); + print(separator); + } + setUpdateVTerm(true); +} + +//---------------------------------------------------------------------- +void Calc::draw() +{ + FDialog::draw(); + drawDispay(); +} + +//---------------------------------------------------------------------- +bool Calc::isDataEntryKey (int key) +{ + // test if key is in {'.', '0'..'9'} + int data_entry_keys[] = + { + Decimal_point, + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine + }; + + int* iter = std::find (data_entry_keys, data_entry_keys+11, key); + + if ( iter != data_entry_keys+11 ) + return true; + else + return false; +} + +//---------------------------------------------------------------------- +bool Calc::isOperatorKey(int key) +{ + // test if key is in {'*', '/', '+', '-', '^', '='} + int operators[] = + { + Multiply, + Divide, + Add, + Subtract, + Power, + Equals + }; + + int* iter = std::find (operators, operators+6, key); + + if ( iter != operators+6 ) + return true; + else + return false; +} + +//---------------------------------------------------------------------- +void Calc::setDisplay (long double d) +{ + char buffer[32]; + snprintf (buffer, sizeof(buffer), "%31.11Lg", d); + input = buffer; +} + +//---------------------------------------------------------------------- +inline void Calc::setInfixOperator(char c) +{ + infix_operator = c; + last_infix_operator = infix_operator; +} + +//---------------------------------------------------------------------- +inline void Calc::clearInfixOperator() +{ + infix_operator = '\0'; +} + +//---------------------------------------------------------------------- +void Calc::calcInfixOperator() +{ + using namespace std; + + switch ( infix_operator ) + { + case '*': + if ( a != 0.0L ) + { + // ln(a * b) = ln(a) + ln(b) + if ( log(abs(a)) + log(abs(b)) <= log(LDBL_MAX) ) + a *= b; + else + error = true; + } + else + b = 0.0L; + break; + + case '/': + if ( b != 0.0L ) + a /= b; + else + error = true; + break; + + case '+': + if ( a != 0.0L ) + { + if ( log(abs(a)) + log(abs(1 + b/a)) <= log(LDBL_MAX) ) + a += b; + else + error = true; + } + else + a = b; + break; + + case '-': + if ( a != 0.0L ) + { + if ( log(abs(a)) + log(abs(1 - b/a)) <= log(LDBL_MAX) ) + a -= b; + else + error = true; + } + else + a = b * (-1.0L); + break; + + case '^': + a = pow(a, b); + if ( errno == EDOM || errno == ERANGE ) + error = true; + break; + } + clearInfixOperator(); +} + +//---------------------------------------------------------------------- +void Calc::onKeyPress (FKeyEvent* event) +{ + int len = int(input.getLength()); + int key = event->key(); + + switch ( key ) + { + case fc::Fkey_backspace: + if ( len > 0 ) + { + if ( len == 1 ) + input = ""; + else + input = input.left(input.getLength() - 1); + a = atof(input.c_str()); + drawDispay(); + updateTerminal(); + } + event->accept(); + break; + + case fc::Fkey_escape: + case fc::Fkey_escape_mintty: + FAccelEvent a_ev(Accelerator_Event, getFocusWidget()); + calculator_buttons[On]->onAccel(&a_ev); + event->accept(); + break; + } +} + +//---------------------------------------------------------------------- +void Calc::onAccel (FAccelEvent* ev) +{ + close(); + ev->accept(); +} + +//---------------------------------------------------------------------- +void Calc::onClose (FCloseEvent* event) +{ + int ret = FMessageBox::info ( this, "Quit", + "Do you really want\n" + "to quit the program ?", + FMessageBox::Yes, + FMessageBox::No ); + + (ret == FMessageBox::Yes) ? event->accept() : event->ignore(); +} + +//---------------------------------------------------------------------- +void Calc::cb_buttonClicked (FWidget*, void* data_ptr) +{ + int key; + long double* x; + + using namespace std; + + if ( infix_operator ) + x = &b; + else + x = &a; + + key = *(static_cast(data_ptr)); + + switch ( key ) + { + case Sine: // sin + if ( hyperbolic_mode ) + { + if ( arcus_mode ) + { + *x = log(*x + sqrt((*x) * (*x) + 1)); + if ( errno == EDOM || errno == ERANGE ) + error = true; + if ( *x == INFINITY ) + error = true; + } + else + *x = sinh(*x); + } + else + { + if ( arcus_mode ) + *x = asin(*x) * 180.0L/PI; + else + *x = sin(*x * PI/180.0L); + } + if ( errno == EDOM ) + error = true; + setDisplay(*x); + arcus_mode = false; + hyperbolic_mode = false; + calculator_buttons[Arcus]->setChecked(false); + calculator_buttons[Hyperbolic]->setChecked(false); + break; + + case Cosine: // cos + if ( hyperbolic_mode ) + { + if ( arcus_mode ) + { + *x = log(*x + sqrt((*x) * (*x) - 1)); + if ( errno == EDOM || errno == ERANGE ) + error = true; + if ( *x == INFINITY ) + error = true; + } + else + *x = cosh(*x); + } + else + { + if ( arcus_mode ) + *x = acos(*x) * 180.0L/PI; + else + *x = cos(*x * PI/180.0L); + } + if ( errno == EDOM ) + error = true; + setDisplay(*x); + arcus_mode = false; + hyperbolic_mode = false; + calculator_buttons[Arcus]->setChecked(false); + calculator_buttons[Hyperbolic]->setChecked(false); + break; + + case Tangent: // tan + if ( hyperbolic_mode ) + { + if ( arcus_mode ) + if ( *x < 1 ) + { + *x = 0.5L * log((1+(*x))/(1-(*x))); + if ( errno == EDOM || errno == ERANGE ) + error = true; + } + else + error = true; + else + *x = tanh(*x); + } + else + { + if ( arcus_mode ) + *x = atan(*x) * 180.0L/PI; + else + if ( fmod(*x,180.0L) != 0.0L && fmod(*x,90.0L) == 0.0L ) + error = true; + else if ( fmod(*x,180.0L) == 0.0L ) + *x = 0.0L; + else + *x = tan(*x * PI/180.0L); + } + if ( errno == EDOM ) + error = true; + setDisplay(*x); + arcus_mode = false; + hyperbolic_mode = false; + calculator_buttons[Arcus]->setChecked(false); + calculator_buttons[Hyperbolic]->setChecked(false); + break; + + case Reciprocal: // 1/x + if ( *x == 0.0L ) + error = true; + else + { + *x = 1/(*x); + setDisplay(*x); + } + break; + + case On: + error = false; + arcus_mode = false; + hyperbolic_mode = false; + calculator_buttons[Arcus]->setChecked(false); + calculator_buttons[Hyperbolic]->setChecked(false); + input = ""; + clearInfixOperator(); + last_infix_operator = '\0'; + a = b = 0.0L; + break; + + case Natural_logarithm: // ln + *x = log(*x); + if ( errno == EDOM || errno == ERANGE ) + error = true; + setDisplay(*x); + break; + + case Powers_of_e: // eˣ + *x = exp(*x); + if ( errno == ERANGE ) + error = true; + setDisplay(*x); + break; + + case Power: // yˣ + if ( ! isOperatorKey(last_key) ) + calcInfixOperator(); + setDisplay(*x); + setInfixOperator('^'); + break; + + case Square_root: // sqrt + *x = sqrt(*x); + if ( errno == EDOM || errno == ERANGE ) + error = true; + setDisplay(*x); + break; + + case Divide: // ÷ + if ( ! isOperatorKey(last_key) ) + calcInfixOperator(); + setDisplay(a); + setInfixOperator('/'); + break; + + case Common_logarithm: // lg + *x = log10(*x); + if ( errno == EDOM || errno == ERANGE ) + error = true; + setDisplay(*x); + break; + + case Powers_of_ten: // 10ˣ + *x = pow(10,*x); + if ( errno == EDOM || errno == ERANGE ) + error = true; + setDisplay(*x); + break; + + case Parenthese_l: // ( + { + stack_data d = { a, infix_operator }; + bracket_stack.push(d); + clearInfixOperator(); + input = ""; + a = b = 0.0L; + setDisplay(a); + } + break; + + case Parenthese_r: // ) + if ( ! bracket_stack.empty() ) + { + calcInfixOperator(); + setDisplay(a); + stack_data d = bracket_stack.top(); + bracket_stack.pop(); + b = d.term; + infix_operator = d.infix_operator; + last_infix_operator = infix_operator; + } + break; + + case Multiply: // * + if ( ! isOperatorKey(last_key) ) + calcInfixOperator(); + setDisplay(a); + setInfixOperator('*'); + break; + + case Hyperbolic: // hyp + hyperbolic_mode = ! hyperbolic_mode; + calculator_buttons[Hyperbolic]->setChecked(hyperbolic_mode); + setDisplay(*x); + break; + + case Seven: // 7 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '7'; + else + input = '7'; + } + break; + + case Eight: // 8 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '8'; + else + input = '8'; + } + break; + + case Nine: // 9 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '9'; + else + input = '9'; + } + break; + + case Subtract: // - + if ( ! isOperatorKey(last_key) ) + calcInfixOperator(); + setDisplay(a); + setInfixOperator('-'); + break; + + case Arcus: // arc + arcus_mode = ! arcus_mode; + calculator_buttons[Arcus]->setChecked(arcus_mode); + setDisplay(*x); + break; + + case Four: // 4 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '4'; + else + input = '4'; + } + break; + + case Five: // 5 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '5'; + else + input = '5'; + } + break; + + case Six: // 6 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '6'; + else + input = '6'; + } + break; + + case Add: // + + if ( ! isOperatorKey(last_key) ) + calcInfixOperator(); + setDisplay(a); + setInfixOperator('+'); + break; + + case Pi: // π + *x = PI; + setDisplay(*x); + break; + + case One: // 1 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '1'; + else + input = '1'; + } + break; + + case Two: // 2 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '2'; + else + input = '2'; + } + break; + + case Three: // 3 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '3'; + else + input = '3'; + } + break; + + case Percent: // % + infix_operator = last_infix_operator; + *x /= 100.0L; + setDisplay(*x); + break; + + case Zero: // 0 + if ( input.getLength() < max_char ) + { + if ( isDataEntryKey(last_key) ) + input += '0'; + else + input = '0'; + } + break; + + case Decimal_point: // . + if ( ! input.includes('.') ) + input += '.'; + break; + + case Change_sign: // ± + *x *= -1.0L; + setDisplay(*x); + break; + + case Equals: // = + infix_operator = last_infix_operator; + calcInfixOperator(); + setDisplay(a); + break; + } // end of switch + + if ( ! input.isEmpty() ) + { + if ( isDataEntryKey(key) ) + *x = atof(input.c_str()); + else + { + // remove trailing zeros + while ( ! input.includes(L'e') + && input.includes(L'.') + && input.back() == L'0' ) + input = input.left(input.getLength()-1); + } + } + + drawDispay(); + updateTerminal(); + + if ( infix_operator && ! isDataEntryKey(key) ) + input = ""; + last_key = key; +} + +//---------------------------------------------------------------------- +void Calc::adjustSize() +{ + int pw = parentWidget()->getWidth(); + int ph = parentWidget()->getHeight(); + setX (1 + (pw - getWidth()) / 2, false); + setY (1 + (ph - getHeight()) / 2, false); + FDialog::adjustSize(); +} + +//---------------------------------------------------------------------- +// main part +//---------------------------------------------------------------------- +int main (int argc, char* argv[]) +{ + // Create the application object + FApplication app(argc, argv); + + // Create a calculator object + Calc calculator(&app); + + app.setMainWidget(&calculator); + calculator.show(); + return app.exec(); +}