windows-nt/Source/XPSP1/NT/shell/osshell/accesory/calc/scicomm.c
2020-09-26 16:20:57 +08:00

737 lines
22 KiB
C

/****************************Module*Header***********************************\
* Module Name: SCICOMM.C
*
* Module Descripton:
*
* Warnings:
*
* Created:
*
* Author:
\****************************************************************************/
#include "scicalc.h"
#include "calchelp.h"
#include "unifunc.h"
#include "input.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. */
lstrcpy(szJunk, TEXT("(="));
lstrcat(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;
}