/****************************Module*Header***********************************\ * Module Name: SCICOMM.C * * Module Descripton: * * Warnings: * * Created: * * Author: \****************************************************************************/ #include "scicalc.h" #include "calchelp.h" #include "unifunc.h" #include "input.h" #include "strsafe.h" extern HWND hStatBox; extern HNUMOBJ ghnoNum, ghnoLastNum, ghnoMem; extern HNUMOBJ ghnoParNum[25], ghnoPrecNum[25]; extern eNUMOBJ_FMT nFE; extern INT nTempCom, nParNum, nPrecNum, gcIntDigits, nOpCode, nOp[25], nPrecOp[25]; extern BOOL bError; extern TCHAR szBlank[6]; extern TCHAR *rgpsz[CSTRINGS]; int nLastCom; // Last command entered. CALCINPUTOBJ gcio; // Global calc input object for decimal strings BOOL gbRecord; // Global mode: recording or displaying /* Puts up the wait cursor if the calc will take a long time */ HCURSOR ghcurOld = NULL; BOOL SetWaitCursor( BOOL fOn ) { if (fOn && ghcurOld == NULL) { ghcurOld = SetCursor( LoadCursor(NULL, IDC_WAIT) ); } else if (!fOn && ghcurOld != NULL) { SetCursor( ghcurOld ); ghcurOld = NULL; } return (fOn && ghcurOld != NULL); } /* Process all keyclicks whether by mouse or accelerator. */ VOID NEAR RealProcessCommands(WPARAM wParam); VOID NEAR ProcessCommands(WPARAM wParam) { if (wParam != IDM_ABOUT) { TimeCalc(TRUE); } try { RealProcessCommands( wParam ); } catch( ... ) { // note: it should be impossible for a throw to reach this level, this is put here as an // emergency backup only. Throws are normally caught at the boundry between calc and ratpak. ASSERT( 0 ); MessageBox( g_hwndDlg, TEXT("An unknown error has occured."), TEXT("Error"), MB_OK ); } if (wParam != IDM_ABOUT) { TimeCalc(FALSE); } } VOID NEAR RealProcessCommands(WPARAM wParam) { static BOOL bNoPrevEqu=TRUE, /* Flag for previous equals. */ bChangeOp=FALSE; /* Flag for changing operation. */ INT nx, ni; TCHAR szJunk[50], szTemp[50]; static BYTE rgbPrec[24]={ 0,0, IDC_OR,0, IDC_XOR,0, IDC_AND,1, IDC_ADD,2, IDC_SUB,2, RSHF,3, IDC_LSHF,3, IDC_MOD,3, IDC_DIV,3, IDC_MUL,3, IDC_PWR,4}; // Make sure we're only getting commands we understand. ASSERT( xwParam(IDC_FIRSTCONTROL, IDC_LASTCONTROL) || // Is it a button? xwParam(IDM_FIRSTMENU, IDM_LASTMENU) ); // or a menu command? // Save the last command. Some commands are not saved in this manor, these // commands are: // Inv, Hyp, Deg, Rad, Grad, Stat, FE, MClear, Back, and Exp. The excluded // commands are not // really mathematical operations, rather they are GUI mode settings. if ( !xwParam(IDC_INV, IDC_HYP) && !xwParam(IDM_HEX, IDM_BIN) && !xwParam(IDM_QWORD, IDM_BYTE) && !xwParam(IDM_DEG, IDM_GRAD) && wParam!=IDC_STAT && wParam!=IDC_FE && wParam!=IDC_MCLEAR && wParam!=IDC_BACK && wParam!=IDC_EXP) { nLastCom=nTempCom; nTempCom=(INT)wParam; } // If error and not a clear key or help key, BEEP. if (bError && (wParam !=IDC_CLEAR) && (wParam !=IDC_CENTR) && (wParam != IDM_HELPTOPICS)) { MessageBeep(0); return; } // Toggle Record/Display mode if appropriate. if (gbRecord) { if (xwParam(IDC_AND, IDC_MPLUS) || xwParam(IDC_AVE, IDC_CLOSEP) || xwParam(IDC_INV, IDC_HYP) || xwParam(IDM_HEX, IDM_BIN) || xwParam(IDM_QWORD, IDM_BYTE) || xwParam(IDM_DEG, IDM_GRAD) || wParam == IDM_PASTE) { gbRecord = FALSE; SetWaitCursor(TRUE); CIO_vConvertToNumObj(&ghnoNum, &gcio); DisplayNum(); // Causes 3.000 to shrink to 3. on first op. SetWaitCursor(FALSE); } } else { if ( xwParam(IDC_0, IDC_F) || wParam == IDC_PNT) { gbRecord = TRUE; CIO_vClear(&gcio); } } // Interpret digit keys. if (xwParam(IDC_0, IDC_F)) { int iValue = (int)(wParam-IDC_0); // this is redundant, illegal keys are disabled if (iValue >= nRadix) { //ASSERT( 0 ); MessageBeep(0); return; } if (!CIO_bAddDigit(&gcio, iValue)) { MessageBeep(0); return; } DisplayNum(); return; } // STATISTICAL FUNCTIONS: if (xwParam(IDC_AVE,IDC_DATA)) { /* Do statistics functions on data in fpStatNum array. */ if (hStatBox) { DisplayNum(); // Make sure gpszNum has the correct string try { StatFunctions (wParam); } catch ( ... ) { ASSERT( 0 ); // the only thing stat box should be able to throw is out of memory // which in previous versions of calc caused a program crash } if (!bError) DisplayNum (); } else /* Beep if the stat box is not active. */ MessageBeep(0); /* Reset the inverse flag since some functions use it. */ SetBox (IDC_INV, bInv=FALSE); return; } // BINARY OPERATORS: if (xwParam(IDC_AND,IDC_PWR)) { if (bInv && wParam==IDC_LSHF) { SetBox (IDC_INV, bInv=FALSE); wParam=RSHF; } /* Change the operation if last input was operation. */ if (nLastCom >=IDC_AND && nLastCom <=IDC_PWR) { nOpCode=(INT)wParam; return; } /* bChangeOp is true if there was an operation done and the */ /* current ghnoNum is the result of that operation. This is so */ /* entering 3+4+5= gives 7 after the first + and 12 after the */ /* the =. The rest of this stuff attempts to do precedence in*/ /* Scientific mode. */ if (bChangeOp) { DoPrecedenceCheckAgain: nx=0; while (wParam!=rgbPrec[nx*2] && nx <12) nx++; ni=0; while (nOpCode!=rgbPrec[ni*2] && ni <12) ni++; if (nx==12) nx=0; if (ni==12) ni=0; if (rgbPrec[nx*2+1] > rgbPrec[ni*2+1] && nCalc==0) { if (nPrecNum <25) { NumObjAssign( &ghnoPrecNum[nPrecNum], ghnoLastNum ); nPrecOp[nPrecNum]=nOpCode; } else { nPrecNum=24; MessageBeep(0); } nPrecNum++; } else { /* do the last operation and then if the precedence array is not * empty or the top is not the '(' demarcator then pop the top * of the array and recheck precedence against the new operator */ SetWaitCursor(TRUE); DoOperation(nOpCode, &ghnoNum, ghnoLastNum); SetWaitCursor(FALSE); if ((nPrecNum !=0) && (nPrecOp[nPrecNum-1])) { nPrecNum--; nOpCode=nPrecOp[nPrecNum] ; if (NumObjOK( ghnoPrecNum[nPrecNum] )) NumObjAssign(&ghnoLastNum , ghnoPrecNum[nPrecNum]); else NumObjAssign(&ghnoLastNum, HNO_ZERO); goto DoPrecedenceCheckAgain ; } if (!bError) DisplayNum (); } } NumObjAssign(&ghnoLastNum, ghnoNum); NumObjAssign(&ghnoNum, HNO_ZERO); nOpCode=(INT)wParam; bNoPrevEqu=bChangeOp=TRUE; return; } // UNARY OPERATORS: if (xwParam(IDC_CHOP,IDC_PERCENT)) { /* Functions are unary operations. */ /* If the last thing done was an operator, ghnoNum was cleared. */ /* In that case we better use the number before the operator */ /* was entered, otherwise, things like 5+ 1/x give Divide By */ /* zero. This way 5+=gives 10 like most calculators do. */ if (nLastCom >= IDC_AND && nLastCom <= IDC_PWR) NumObjAssign( &ghnoNum, ghnoLastNum ); SetWaitCursor(TRUE); SciCalcFunctions ( &ghnoNum, (DWORD)wParam); SetWaitCursor(FALSE); if (bError) return; /* Display the result, reset flags, and reset indicators. */ DisplayNum (); /* reset the bInv and bHyp flags and indicators if they are set and have been used */ if (bInv && (wParam == IDC_CHOP || wParam == IDC_SIN || wParam == IDC_COS || wParam == IDC_TAN || wParam == IDC_SQR || wParam == IDC_CUB || wParam == IDC_LOG || wParam == IDC_LN || wParam == IDC_DMS)) { bInv=FALSE; SetBox (IDC_INV, FALSE); } if (bHyp && (wParam == IDC_SIN || wParam == IDC_COS || wParam == IDC_TAN)) { bHyp = FALSE; SetBox (IDC_HYP, FALSE); } bNoPrevEqu=TRUE; return; } // BASE CHANGES: if (xwParam(IDM_HEX, IDM_BIN)) { // Change radix and update display. if (nCalc==1) { wParam=IDM_DEC; } SetRadix((DWORD)wParam); return; } SetWaitCursor(TRUE); /* Now branch off to do other commands and functions. */ switch(wParam) { case IDM_COPY: case IDM_PASTE: case IDM_ABOUT: case IDM_SC: case IDM_SSC: case IDM_USE_SEPARATOR: case IDM_HELPTOPICS: // Jump to menu command handler in scimenu.c. MenuFunctions((DWORD)wParam); DisplayNum(); break; case IDC_CLEAR: /* Total clear. */ NumObjAssign( &ghnoLastNum, HNO_ZERO ); nPrecNum=nTempCom=nLastCom=nOpCode=nParNum=bChangeOp=FALSE; nFE = FMT_FLOAT; // back to the default number format bNoPrevEqu=TRUE; /* clear the paranthesis status box indicator, this will not be cleared for CENTR */ SetDlgItemText(g_hwndDlg, IDC_PARTEXT, szBlank); /* fall through */ case IDC_CENTR: /* Clear only temporary values. */ NumObjAssign( &ghnoNum, HNO_ZERO ); if (!nCalc) { // Clear the INV, HYP indicators & leave (=xx indicator active SetBox (IDC_INV, bInv=FALSE); SetBox (IDC_HYP, bHyp=FALSE); } bError=FALSE; CIO_vClear(&gcio); gbRecord = TRUE; DisplayNum (); break; case IDC_STAT: /* Shift focus to Statistix Box if it's active. */ if (hStatBox) SetFocus(hStatBox); else SetStat (TRUE); break; case IDC_BACK: // Divide number by the current radix and truncate. // Only allow backspace if we're recording. if (gbRecord) { if (!CIO_bBackspace(&gcio)) MessageBeep(0); DisplayNum(); } else MessageBeep(0); break; /* EQU enables the user to press it multiple times after and */ /* operation to enable repeats of the last operation. I don't */ /* know if I can explain what the [censored] I did here... */ case IDC_EQU: do { // NOTE: the number pointed to by hnoHold won't get freed until process termination. static HNUMOBJ hnoHold; /* Last thing keyed in was an operator. Lets do the op on*/ /* a duplicate of the last entry. */ if ((nLastCom >= IDC_AND) && (nLastCom <= IDC_PWR)) NumObjAssign( &ghnoNum, ghnoLastNum ); if (nOpCode) /* Is there a valid operation around? */ { /* If this is the first EQU in a string, set hnoHold=ghnoNum */ /* Otherwise let ghnoNum=hnoTemp. This keeps ghnoNum constant */ /* through all EQUs in a row. */ if (bNoPrevEqu) NumObjAssign(&hnoHold, ghnoNum); else NumObjAssign(&ghnoNum, hnoHold); /* Do the current or last operation. */ DoOperation (nOpCode, &ghnoNum, ghnoLastNum); NumObjAssign(&ghnoLastNum, ghnoNum ); /* Check for errors. If this wasn't done, DisplayNum */ /* would immediately overwrite any error message. */ if (!bError) DisplayNum (); /* No longer the first EQU. */ bNoPrevEqu=FALSE; } else if (!bError) DisplayNum(); if (nPrecNum==0 || nCalc==1) break; nOpCode=nPrecOp[--nPrecNum]; if (NumObjOK( ghnoPrecNum[nPrecNum] )) NumObjAssign(&ghnoLastNum , ghnoPrecNum[nPrecNum]); else NumObjAssign(&ghnoLastNum, HNO_ZERO); bNoPrevEqu=TRUE; } while (nPrecNum >= 0); bChangeOp=FALSE; break; case IDC_OPENP: case IDC_CLOSEP: nx=0; if (wParam==IDC_OPENP) nx=1; // -IF- the Paren holding array is full and we try to add a paren // -OR- the paren holding array is empty and we try to remove a // paren // -OR- the the precidence holding array is full if ((nParNum >= 25 && nx) || (!nParNum && !nx) || ( (nPrecNum >= 25 && nPrecOp[nPrecNum-1]!=0) ) ) { MessageBeep(0); break; } if (nx) { /* Open level of parentheses, save number and operation. */ NumObjAssign( &ghnoParNum[nParNum], ghnoLastNum); nOp[nParNum++]=nOpCode; /* save a special marker on the precedence array */ nPrecOp[nPrecNum++]=0 ; NumObjAssign( &ghnoLastNum, HNO_ZERO ); nTempCom=0; nOpCode=IDC_ADD; } else { /* Get the operation and number and return result. */ DoOperation (nOpCode, &ghnoNum, ghnoLastNum); /* now process the precedence stack till we get to an opcode which is zero. */ while (nOpCode = nPrecOp[--nPrecNum]) { if (NumObjOK( ghnoPrecNum[nPrecNum] )) NumObjAssign(&ghnoLastNum , ghnoPrecNum[nPrecNum]); else NumObjAssign(&ghnoLastNum, HNO_ZERO); DoOperation (nOpCode, &ghnoNum, ghnoLastNum); } /* now get back the operation and opcode at the begining of this paranthesis pair */ nParNum -= 1; NumObjAssign( &ghnoLastNum, ghnoParNum[nParNum] ); nOpCode=nOp[nParNum]; /* if nOpCode is a valid operator then set bChangeOp to be true else set it false */ if (nOpCode) bChangeOp=TRUE; else bChangeOp=FALSE ; } /* Set the "(=xx" indicator. */ StringCchCopy(szJunk, ARRAYSIZE(szJunk), TEXT("(=")); StringCchCat(szJunk, ARRAYSIZE(szJunk), UToDecT(nParNum, szTemp)); SetDlgItemText(g_hwndDlg, IDC_PARTEXT, (nParNum) ? (szJunk) : (szBlank)); if (bError) break; if (nx) { /* Build a display string of nParNum "("'s. */ for (nx=0; nx < nParNum; nx++) szJunk[nx]=TEXT('('); szJunk[nx]=0; /* Null-terminate. */ SetDisplayText(g_hwndDlg, szJunk); bChangeOp=FALSE; } else DisplayNum (); break; case IDM_QWORD: case IDM_DWORD: case IDM_WORD: case IDM_BYTE: case IDM_DEG: case IDM_RAD: case IDM_GRAD: if (!F_INTMATH()) { // in decimal mode, these buttons simply set a flag which is // passed to the ratpak to handle angle conversions if (xwParam(IDM_DEG, IDM_GRAD)) { nDecMode = (ANGLE_TYPE)(wParam - IDM_DEG); CheckMenuRadioItem(GetSubMenu(GetMenu(g_hwndDlg), 1), IDM_DEG, IDM_GRAD, IDM_DEG+nDecMode, MF_BYCOMMAND); CheckRadioButton(g_hwndDlg, IDC_DEG, IDC_GRAD, IDC_DEG+nDecMode); } } else { if (xwParam(IDM_DEG, IDM_GRAD)) { // if in hex mode, but we got a decimal key press this // likely is the accelorator. map this to the correct key wParam=IDM_DWORD+(wParam-IDM_DEG); } if ( gbRecord ) { CIO_vConvertToNumObj(&ghnoNum, &gcio); gbRecord = FALSE; } // Compat. mode BaseX: Qword, Dword, Word, Byte nHexMode = (int)(wParam - IDM_QWORD); switch (nHexMode) { case 0: dwWordBitWidth = 64; break; case 1: dwWordBitWidth = 32; break; case 2: dwWordBitWidth = 16; break; case 3: dwWordBitWidth = 8; break; default: ASSERT( 0 ); // Invalid Word Size break; } // different wordsize means the new wordsize determines // the precision BaseOrPrecisionChanged(); CheckMenuRadioItem(GetSubMenu(GetMenu(g_hwndDlg), 1), IDM_QWORD, IDM_BYTE, IDM_QWORD+nHexMode, MF_BYCOMMAND); CheckRadioButton(g_hwndDlg, IDC_QWORD, IDC_BYTE, IDC_QWORD+nHexMode); } // REARCHITECT: the call to display number is what actually does the // chop. it would make more sense to do the chop here when the // wordsize changes. the chop must be done when a different // wordsize is selected AND when the base is changed to non-decimal DisplayNum(); break; case IDC_SIGN: // Change the sign. if (gbRecord) CIO_vToggleSign(&gcio); else { NumObjNegate( &ghnoNum ); } DisplayNum(); break; case IDC_RECALL: /* Recall immediate memory value. */ NumObjAssign( &ghnoNum, ghnoMem ); DisplayNum (); break; case IDC_MPLUS: /* MPLUS adds ghnoNum to immediate memory and kills the "mem" */ /* indicator if the result is zero. */ addrat( &ghnoMem, ghnoNum); SetDlgItemText(g_hwndDlg,IDC_MEMTEXT, !NumObjIsZero(ghnoMem) ? (TEXT(" M")):(szBlank)); break; case IDC_STORE: case IDC_MCLEAR: if (wParam==IDC_STORE) { NumObjAssign( &ghnoMem, ghnoNum ); } else { NumObjAssign( &ghnoMem, HNO_ZERO ); } SetDlgItemText(g_hwndDlg,IDC_MEMTEXT, !NumObjIsZero(ghnoMem) ? (TEXT(" M")):(szBlank)); break; case IDC_PI: if (!F_INTMATH()) { /* Return PI if bInv==FALSE, or 2PI if bInv==TRUE. */ if (bInv) NumObjAssign( &ghnoNum, HNO_2PI ); else NumObjAssign( &ghnoNum, HNO_PI ); DisplayNum(); SetBox(IDC_INV, bInv=FALSE); } else MessageBeep(0); break; case IDC_FE: // Toggle exponential notation display. nFE = NUMOBJ_FMT(!(int)nFE); DisplayNum(); break; case IDC_EXP: if (gbRecord && !F_INTMATH()) if (CIO_bExponent(&gcio)) { DisplayNum(); break; } MessageBeep(0); break; case IDC_PNT: if (gbRecord && !F_INTMATH()) { if (CIO_bAddDecimalPt(&gcio)) { DisplayNum(); break; } } MessageBeep(0); break; case IDC_INV: SetBox((int)wParam, bInv=!bInv); break; case IDC_HYP: SetBox((int)wParam, bHyp=!bHyp); break; } SetWaitCursor(FALSE); } // change the display area from a static text to an editbox, which has the focus can make // Magnifer (Accessibility tool) work BOOL SetDisplayText(HWND hDlg, LPCTSTR szText) { HWND hDispEdit = GetDlgItem(hDlg, IDC_DISPLAY); int nLen = lstrlen(szText); SetWindowText(hDispEdit, szText); SetFocus(hDispEdit); // make sure the number just typed is shown at the center of Magnifier SendMessage(hDispEdit, EM_SETSEL, nLen, nLen); return TRUE; }