/* * CALC Provides a calculator that opens on the active screen when * you press a specific key sequence. Otherwise, the program * waits quitely in the background. * * Copyright 1989 by Davide P. Cervone. * You may use this code, provided this copyright notice is kept intact. */ #define INTUITION_PREFERENCES_H /* don't need 'em */ #include #include #include #include #include #include "cHandler.h" #include "cKeys.h" /* * ASCII codes for some special keys */ #define CTRLC 3 #define BACKSPACE 8 #define ENTER 13 /* * Delay() time for keypress to invert corresponding gadget */ #define KEYPAUSE 4L /* * TINYVALUE is the round-off value for the display. * BIGVALUE is the largest value that can be dispayed or entered. */ #define NODECIMAL -1 #define TINYVALUE 0.000000000001 #define BIGVALUE 999999999999.0 /* * MAXDISPLAY is the number of characters that are in the display. * MAXSTACK is the size of the pending operation stack (includes * parenthesies). */ #define MAXDISPLAY 13 #define MAXSTACK 20 /* * Standard character width for Topaz 8-pt font */ #define CHARW 8 static struct IntuiText *DisplayIText = &KeyText[KEY_DISP]; static short DisplayPos; /* The position in the DisplayBuffer */ static double DisplayValue; /* The value on the display */ static double DecimalPower; /* The power of ten for next digit */ static int DecimalPlace = NODECIMAL; /* Decimal place for next digit */ static int NoDigit = TRUE; /* TRUE if digits not yet entered */ static int ErrorStatus; /* TRUE if an error occured */ /* * The codes for each of the pending operations that can appear on the stack */ #define OP_NONE 0 #define OP_PLUS 1 #define OP_MINUS 2 #define OP_TIMES 3 #define OP_DIVIDE 4 #define OP_LPAREN 5 #define OP_RPAREN 6 #define OP_EQUAL 7 #define OP_PAREN 8 /* * The precedence values for each of the stack operations. */ static UBYTE Precedence[] = {0,4,4,5,5,6,2,1,3}; static UBYTE Op[MAXSTACK]; /* The pending operation stack */ static double Val[MAXSTACK]; /* The pending value stack */ static short StackTop; /* pointer to top of stack */ /* * ClearDisplay() * * Clears the display buffer and sets all the flags to indicate that we * are now entering a number from the keypad. */ static void ClearDisplay() { NoDigit = FALSE; DecimalPlace = NODECIMAL; DecimalPower = 0.0; DisplayValue = 0.0; DisplayPos = 1; DisplayBuffer[DisplayPos] = '0'; DisplayBuffer[DisplayPos+1] = 0; DisplayIText->IText = &DisplayBuffer[1]; DisplayIText->LeftEdge = DISPLAYIW - CHARW; ErrorStatus = FALSE; } /* * RefreshDisplay() * * Refresh the DisplayGadget */ static void RefreshDisplay() { RefreshGList(&CalcGadget[KEY_DISP],CalcWindow,NULL,1L); } /* * DisplayError() * * Put the error message in the display buffer, and set the display * gadget's left edge so that the message is centered. Refresh the * display. Set the error status flag, and clear the stack. */ static void DisplayError(s) char *s; { ClearDisplay(); NoDigit = TRUE; strcpy(DisplayIText->IText,s); DisplayIText->LeftEdge = (DISPLAYIW-strlen(DisplayIText->IText)*CHARW+1) / 2; RefreshDisplay(); ErrorStatus = TRUE; StackTop = 0; } /* * AddToDisplay() * * Check for buffer overflow. If none, then add character into the display * buffer and adjust the display position (but don't add non-significant * zero digits). Refresh the display on the screen and cancel any errors. */ static int AddToDisplay(c) char c; { if (DisplayPos == MAXDISPLAY || DisplayValue > BIGVALUE || DisplayValue < -BIGVALUE) { DisplayError("Overflow"); } else { if (DisplayValue != 0.0 || DisplayBuffer[DisplayPos] != '0' || DecimalPlace != NODECIMAL) DisplayIText->LeftEdge -= CHARW; DisplayBuffer[DisplayPos++] = c; DisplayBuffer[DisplayPos] = 0; if (DisplayValue == 0.0 && c == '0' && DecimalPlace == NODECIMAL) DisplayPos--; RefreshDisplay(); ErrorStatus = FALSE; } return(ErrorStatus == FALSE); } /* * SetDisplay() * * Sets the display to the given value. * Check for overflow. If none, then * Set the display value and the current display position. * If the value is near zero, set the display to zero, * Otherwise * If the value is negative, add a minus sign and make the value positive. * divide the value by the power of ten that makes its integer part only * one digit and add a small offset so that we round up. * If the number is less than 1.0, then add '0.' and as many zeros as * needed to get to the first non-zero decimal digit. * Mark the last non-zero decimal place. * As long as the value is still non-zero, * Find the first digit of the number (TINYVALUE is for round-off errors) * Multiply by ten to get next digit ready. * Add a decimal place if it is time. * Check if the digit is non-zero (or a non-trivial zero) * Decrement the digits still before the decimal place. * If the display is filled, cancel the loop. * NULL-terminate the display string. * Calculate the display offset, and refresh the display. * Set the flags. */ static void SetDisplay(v) double v; { char c; short digits,LastNonZero; extern double pow(),log10(); if (v > BIGVALUE || v < -BIGVALUE) { DisplayError("Overflow"); } else { DisplayValue = v; DisplayPos = 1; DisplayIText->IText = &DisplayBuffer[1]; if (v <= TINYVALUE && v >= -TINYVALUE) { DisplayBuffer[DisplayPos++] = '0'; DisplayBuffer[DisplayPos++] = '.'; LastNonZero = DisplayPos; } else { if (v < 0.0) { DisplayBuffer[DisplayPos++] = '-'; v = -v; } digits = log10(v); v = v / pow(10.0,(double)digits) + (5.0*TINYVALUE); if (digits < 0) { DisplayBuffer[DisplayPos++] = '0'; DisplayBuffer[DisplayPos++] = '.'; LastNonZero = DisplayPos; while (++digits) DisplayBuffer[DisplayPos++] = '0'; digits = -1; } else { LastNonZero = DisplayPos; } while (v > TINYVALUE) { c = v + TINYVALUE; v = (v-c) * 10.0; DisplayBuffer[DisplayPos++] = c + '0'; if (digits == 0 && DisplayPos <= MAXDISPLAY) DisplayBuffer[DisplayPos++] = '.'; if (c || digits >= 0) LastNonZero = DisplayPos; digits--; if (DisplayPos > MAXDISPLAY) v = 0.0; } } DisplayBuffer[LastNonZero] = 0; DisplayIText->LeftEdge = DISPLAYIW - (LastNonZero-1)*CHARW; RefreshDisplay(); ErrorStatus = FALSE; NoDigit = TRUE; DecimalPlace = NODECIMAL; } } /* * ClearEntry() * * Clear the display. Set the NoDigit flag to TRUE so that the zero we are * now displaying will not be part of the displayed number. */ static void ClearEntry() { ClearDisplay(); NoDigit = TRUE; RefreshDisplay(); } /* * DoFunction() * * Do the operation that's on the stack using the value on the top of the * stack as the first operand and the display value as the second one. * (Check for division by zero errors) */ static void DoFunction() { switch(Op[StackTop]) { case OP_PLUS: DisplayValue = Val[StackTop] + DisplayValue; break; case OP_MINUS: DisplayValue = Val[StackTop] - DisplayValue; break; case OP_TIMES: DisplayValue = Val[StackTop] * DisplayValue; break; case OP_DIVIDE: if (DisplayValue != 0.0) DisplayValue = Val[StackTop] / DisplayValue; else DisplayError("Zero Division"); break; } } /* * DoOperation() * * Perform any pending operations of higher (or equal precedence) up to * an open parenthesis (the bottom of the stack is OP_NONE). * Convert a left-paren to OP_PAREN (the left paren has highest precedence * so it will not cause other operations to be performed, but when on * the stack, we want it to have the lowest precedence, so it will not be * poped until a right paren or an equal. * If the operation is not an equal or right paren (precedence 1 and 2) then * Push the operation on the stack (we need to get the next operand) * Push the current display value onto the stack. * (If there is a stack overflow, beep the screen). */ static void DoOperation(theOp) int theOp; { while (Precedence[theOp] <= Precedence[Op[StackTop]] && theOp != OP_NONE) { if (Op[StackTop] == OP_PAREN && theOp != OP_EQUAL) theOp = OP_NONE; else DoFunction(); if (StackTop) StackTop--; } if (theOp == OP_LPAREN) theOp = OP_PAREN; if (Precedence[theOp] > 2) { if (StackTop < MAXSTACK-1) { Op[++StackTop] = theOp; Val[StackTop] = DisplayValue; } else { DisplayBeep(CalcScreen); } } } /* * DoGadget() * * The gadget ID indicates which function has been pressed. * For a dot, if we don't already have a dot, then * If no other digits have been entered, clear the display value. * If the current value is zero, add a zero to the display. * Set the decimal place counters * Add a dot to the display. * For one of the function keys, parens, or equal, * Set NoDigit to indicate a calculated result, and clear the decimal flag. * Do the operation indicated by the gadget ID. * If an error did not occur, display the result. * For the sign change key, * If the number is a result, show it's negative, * Otherwise, if the displayalue is not zero, then * If the displayed value is already negative, * Change the IText pointer to be past the negative, * Fix the IText left edge, * Otherwise * Change the IText pointer to include the negative, * Fix the IText left edge, * Refresh the display. * For the Root key, * If the current value is negative, display an error. * otherwise display the square root. * For the Percent key, * If the number has been entered (not caluclated) * divide by 100 * If the pending operation is + or -, then change display to * the given percent of the pending operand. * Display the result. * For the Clear key, * Clear the stack of all pending operations, * and display the a zero. * For the Clear Entry key, clear the entry. * For a numeric key (the GadgetID tells which number was pressed), * If this is the first digit, * Clear the display area, * Set the display to the number that was pressed, and * Add the correct digit to the display. * Otherwise * If the value contains a decimal place, * increase the decimal power of the current digit position, * and increase the decimal place counter for the display. * Set the display value to the current value plus the number pressed * (shifted to the proper decimal place), * Add the digit to the display. * Otherwise (no decimal place yet) * multiply the current value by 10 and add the number pressed * Add the digit to the display. */ void DoGadget(theGadget) struct Gadget *theGadget; { if (theGadget) { switch(theGadget->GadgetID) { case KEY_DOT: if (DecimalPlace == NODECIMAL) { if (NoDigit || DisplayValue == 0.0) { ClearDisplay(); DisplayPos++; } DecimalPlace = 0; DecimalPower = 1.0; AddToDisplay('.'); } break; case KEY_PLUS: case KEY_MINUS: case KEY_TIMES: case KEY_DIVIDE: case KEY_LPAREN: case KEY_RPAREN: case KEY_EQUAL: NoDigit = TRUE; DecimalPlace = NODECIMAL; DoOperation(theGadget->GadgetID - KEY_PLUS + 1); if (ErrorStatus == FALSE) SetDisplay(DisplayValue); break; case KEY_SIGN: if (NoDigit) { SetDisplay(-DisplayValue); } else if (DisplayValue > TINYVALUE || DisplayValue < -TINYVALUE) { if (DisplayIText->IText[0] == '-') { DisplayIText->IText++; DisplayIText->LeftEdge += CHARW; } else { DisplayIText->IText--; DisplayIText->LeftEdge -= CHARW; } DisplayValue = -DisplayValue; RefreshDisplay(); } break; case KEY_SQRT: if (DisplayValue < 0.0) DisplayError("Imaginary"); else SetDisplay(sqrt(DisplayValue)); break; case KEY_PERCENT: if (NoDigit == FALSE) { DisplayValue /= 100.0; if (Op[StackTop] == OP_PLUS || Op[StackTop] == OP_MINUS) DisplayValue *= Val[StackTop]; } SetDisplay(DisplayValue); break; case KEY_CLEAR: StackTop = 0; ClearEntry(); break; case KEY_DISP: if (NoDigit == FALSE) ClearEntry(); break; default: if (NoDigit) { ClearDisplay(); if (AddToDisplay(theGadget->GadgetID+'0')) DisplayValue = (double)theGadget->GadgetID; } else { if (DecimalPlace != NODECIMAL) { DecimalPlace++; DecimalPower *= 10.0; if (AddToDisplay(theGadget->GadgetID+'0')) DisplayValue += theGadget->GadgetID / DecimalPower; } else { if (AddToDisplay(theGadget->GadgetID+'0')) DisplayValue = DisplayValue*10.0 + theGadget->GadgetID; } } break; } } } /* * DoKey() * * Check the key code for special characters: * CTRL-C means close the calculator (return FALSE). * ENTER key is converted to the equal sign (ENTER also equals RETURN). * Look through the gadget list for a gadget that has this character * in it's UserData field. This is the gadget associated with this key code. * If one is found, then * complement the gadget that was "pressed" * do the gadget's function * delay for the key-press delay period (so that we can see the gadget * while it is complemented) * and un-complement the gadget. */ int DoKey(Code) int Code; { struct Gadget *theGadget = &CalcGadget[0]; int x,y,w,h; if (Code == CTRLC) return(FALSE); if (Code == BACKSPACE && NoDigit == FALSE) ClearEntry(); if (Code == ENTER) Code = '='; while (theGadget && Code != (int)theGadget->UserData) theGadget = theGadget->NextGadget; if (theGadget) { SetDrMd(rp,COMPLEMENT); x = theGadget->LeftEdge; y = theGadget->TopEdge; w = x + theGadget->Width - 1; h = y + theGadget->Height - 1; RectFill(rp,x,y,w,h); DoGadget(theGadget); Delay(KEYPAUSE); RectFill(rp,x,y,w,h); } return(TRUE); }