Calculator.cpp

/******************************************************************************** * Calculator app with 4 basic operations + square root, and realistic appearance * Created by Dimitry Rotstein * Copyright (C) 2014 by Miranor *******************************************************************************/ #include <elgrint.cpp> #include <math.h> // For 'sqrt' // Constants const double errorResult = 1E100; // Result of undefined operation const MChar chMul = 0x00D7; // Unicode multiplication sign const MChar chDiv = 0x00F7; // Unicode division sign const MChar chSqrt = 0x221A; // Unicode square root sign const MChar chPM = 0x00B1; // Unicode plus/minus sign // Markings for each button (left-to-right and top-to-bottom) MString marks = (MString)"X"+chSqrt+chPM+"C789"+chDiv+"456"+chMul+"123-0.=+"; // Calculator button (custom appearance) // struct CalcButton : public MButton { CalcButton(MWindow& parent, MRect rect) : MButton(parent,rect,0,MChar(int(rect.x))) {} ON_FocusChanged // Propagate focus to parent (avoid focused buttons) { if (isFocused()) getParent().setFocus(); } ON_Paint // Draw button with proper mark inside { // Background (same color as parent to blend into it) setFillSettings(clBlack+200); fillRect(MaxRect); // Prepare MChar ch = getText()[0]; // Symbol to draw on the button bool pushed = isPushed(); // Affects button color (next 2 lines) MColor bc = (ch == 'X' ? clBlack : ch == 'C' ? clRed-100 : clGray-30); bc = pushed ? bc-60 : isHot() ? bc+20 : bc; setFillSettings(bc-60, bc+60, 135); setDrawTransform( MTransform(ZeroPoint,getSize()/100) ); // Draw button with rounded corners fillPoly(MPoint(50,0), MPoint(95,0), MPoint(100,5), MPoint(100,95), MPoint(95,100), MPoint(5,100), MPoint(0,95), MPoint(0,5), MPoint(5,0), MPoint(50,0)); // Draw mark on the button setDrawTextSettings(MFont("Courier New",90,taBold), clWhite); // Auto-scaled drawText(ch, MRect(pushed,pushed,100,100)); // Shift mark on push } }; // Main frame of the calcualtor (contains display and buttons) // struct CalcFrame : public MMainFrame { //// Constructor //// CalcFrame() : m_state(1) { // Create icon MImage icon(MSize(16,16),clBlack+200); MPoint p; for (p.y = 0; p.y <= 5; ++p.y) // Display for (p.x = 0; p.x <= 15; ++p.x) icon.setPixel(p, MRect(1,1,14,4).includes2D(p) ? MColor(150,150,120) : clBlack); for (int i = 0; i < 9; ++i) // Buttons for (p.y = 7+3*(i/3); p.y <= 8+3*(i/3); ++p.y) for (p.x = 1+5*(i%3); p.x <= 4+5*(i%3); ++p.x) icon.setPixel(p, i==0 ? clBlack: i==2 ? clRed-100: clGray); // Configure frame setIcon(icon); setCaption("Calc"); bar().close(); maxButton().close(); minButton().close(); xButton().close(); // Calculator has its own x-button setHighQuality(true); // Inherited by all the buttons below cancelPropagation(mcResized,psChildren); // For efficiency // Create calculator buttons for (MNum i = 0; i < marks.cnt(); ++i) m_buttons.append( MPtr<CalcButton>(*this,MRect(marks[i],0,0,0)) ); setSize(MSize(260,260)); // Auto-adjust the buttons (see OnResized) } //// Message handlers //// ON_KeysEntered { switch (digKeys()) { case kbEsc : clear(); break; case kbEnter : processInput('='); break; //TEMP case kbCtrl+kbP : MApp::screen().getImage(getRect()).saveAs("screenshot.png"); break; default : MMainFrame::OnKeysEntered(); break; } } ON_StringEntered // Process digits/operators (0123456789.+-*/=) { MMainFrame::OnStringEntered(); processInput( digStringRef()[0] ); } ON_Resized // Adjust transform and buttons to new size { // Schedule full repaint (among other things) MMainFrame::OnResized(); // Adjust scaling transformation m_tr = MTransform(ZeroPoint, getSize()/260); // Scale position and size of each button for (MNum i = 0; i < m_buttons.cnt(); ++i) m_buttons.get(i)->setRect(m_tr( MRect(35+49*(i%4), 95+30*(i/4), 43, 25) )); } ON_Paint // Draw background and display { // Config setDrawSettings(clBlack+100,2); // Before transform to prevent scaling setDrawTransform(m_tr); // Scale the following drawings // Background setFillSettings(clWhite); fillRect(MaxRect); // Upper body (black) setFillSettings(clBlack); fillPoly(MPoint(130, 0), MPoint(230, 0), MPoint(260,30), MPoint(260,85), MPoint(260,85), MPoint( 0,85), MPoint( 0,85), MPoint( 0,30), MPoint( 30, 0), MPoint(130, 0)); // Lower body (gray) setFillSettings(clBlack+200); fillPoly(MPoint(130, 85), MPoint(260, 85), MPoint(260, 85), MPoint(260,230), MPoint(230,260), MPoint( 30,260), MPoint( 0,230), MPoint( 0, 85), MPoint( 0, 85), MPoint(130, 85)); // Display's glass MRect r(40,25,180,45); setFillSettings(clBlack+50); fillRect(MRect(r.x-5,r.y-5,r.w+10,r.h+10)); drawPoly(MPoint(r.x-5,r.y+r.h+5), MPoint(r.x+r.w+5,r.y+r.h+5)); // The display itself setFillSettings(MColor(150,150,120)); fillRect(r); // Text on the display (with shadow) // Prepare text MString text = (m_state == 0 ? "-E-" : m_state == 1 || !m_b.cnt() ? m_a : m_b); if (!text.cnt()) text = "0"; // Config text drawing setDrawTextSettings(MFont("Arial", r.h*0.8, taBold), SameColor); setDrawTransform(MTransform()); // Prevent shadow offset scaling // Text's shadow setDrawTextSettings(SameFont, MColor(100,100,80)); drawText(text, m_tr(r)+MPoint(3,5), tjRight); // The text itself setDrawTextSettings(SameFont, clBlack); drawText(text, m_tr(r)+MPoint(0,2), tjRight); // Light reflection off the display's glass (to make it look more realistic) setDrawTransform(m_tr); setFillSettings(MColor(255,255,255,128), MColor(255,255,255,0), 270); fillPoly(MPoint(180, 0), MPoint(230, 0), MPoint(260,30), MPoint(260,85), MPoint(260,85), MPoint(140,85)); } ON_Notice // Process release notices from buttons (notice ID = 0) { MMainFrame::OnNotice(); if (digNID() == 0) processInput( digOrigin().as<MButton>().getText()[0] ); } //// Auxiliary functions //// void processInput(MChar ch) { // Special cases if (ch == 'X') { close(); return; } // Quit if (ch == 'C') { clear(); return; } // Clear if (m_state == 0) return; // Ignore other input in error state // Prepare MString& str = (m_state == 1 ? m_a : m_b); // Current operand if (ch == chMul ) ch = '*'; // Convert Unicode to ASCII if (ch == chDiv ) ch = '/'; if (ch == chSqrt) ch = 'S'; // This way Shift+S works too if (ch == chPM ) ch = 'P'; // This way Shift+P works too // Calculate switch (ch) { // Calculate square root or negative of the current operand case 'S': case 'P': { double x = 0; str >> x; // Convert to number x = (ch == 'P' ? -x : x>=0.0 ? sqrt(x) : errorResult); if (x == errorResult) // Convert back to string m_state = 0; else str = (x == 0 ? "" : MString(x,16)); break; } // Add decimal point unless one already exists case '.': { if (str.findFirst(".") != str.cnt()) break; // Already has '.' if (!str.cnt()) str.append('0'); // Prevent leading '.' str.append(ch); break; } // Process operators: store (state=1) or calculate result (state=2) case '+': case '-': case '*': case '/': case '=': { if (m_state == 1) // Store the operator and switch to the second operand { if (ch != '=') // Ignore '=' until both operands are defined { m_op = ch; m_state = 2; } } else // Calculate result and make it the new first operand { // Convert operands to numbers double a = 0, b = 0; m_a >> a; // Conversion error can be ignored ('a' remains 0) if (m_b.cnt()) m_b >> b; else b = a; // Calculate result double res = (m_op == '+' ? a+b : m_op == '-' ? a-b : m_op == '*' ? a*b : m_op == '/' && b ? a/b : errorResult); // Reset data for next time m_a = MString(res,16); m_b = ZeroString; if (res == errorResult) m_state = 0; else if (ch == '=') m_state = 1; else m_op = ch; } break; } // Add another digit to the current operand default: if (ch.isDigit()) str.append(ch); } // Display the new text (after calculation) repaint(); } void clear() { m_a.reset(); m_b.reset(); m_op = 0; m_state = 1; repaint(); } // Data members private: MVector< MPtr<CalcButton> > m_buttons; // Button child windows MTransform m_tr; // Helps adjust graphics to any frame size MString m_a, m_b; // Operands (in string form) MChar m_op; // Binary operator (+,-,*,/) for operands int m_state; // 1|2: m_a|m_b is being defined, 0: error }; void MAppMain() { CalcFrame().runMessageLoop(); }

Miranor Home | About Miranor | About Elgrint | Create account | Login | Account settings | Contact Us | Privacy Policy | Site map

© Copyright 2014 by Miranor. All rights reserved. By using this site you agree to the Terms of Use.

Page last updated on August 10th, 2014.

Real Time Web Analytics