559 lines
14 KiB
C
559 lines
14 KiB
C
/****************************Module*Header***********************************\
|
|
* Module Name: SCIDISP.C
|
|
*
|
|
* Module Descripton:
|
|
*
|
|
* Warnings:
|
|
*
|
|
* Created:
|
|
*
|
|
* Author:
|
|
\****************************************************************************/
|
|
|
|
#include "scicalc.h"
|
|
#include "unifunc.h"
|
|
#include "input.h"
|
|
|
|
#include <tchar.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
extern HNUMOBJ ghnoNum;
|
|
extern eNUMOBJ_FMT nFE;
|
|
extern TCHAR szDec[5];
|
|
extern TCHAR gszSep[5];
|
|
extern UINT gnDecGrouping;
|
|
extern LPTSTR gpszNum;
|
|
extern BOOL gbRecord;
|
|
extern BOOL gbUseSep;
|
|
extern CALCINPUTOBJ gcio;
|
|
|
|
|
|
/****************************************************************************\
|
|
* void DisplayNum(void)
|
|
*
|
|
* Convert ghnoNum to a string in the current radix.
|
|
*
|
|
* Updates the following globals:
|
|
* ghnoNum, gpszNum
|
|
\****************************************************************************/
|
|
//
|
|
// State of calc last time DisplayNum was called
|
|
//
|
|
typedef struct {
|
|
HNUMOBJ hnoNum;
|
|
LONG nPrecision;
|
|
LONG nRadix;
|
|
INT nFE;
|
|
INT nCalc;
|
|
INT nHexMode;
|
|
BOOL fIntMath;
|
|
BOOL bRecord;
|
|
BOOL bUseSep;
|
|
} LASTDISP;
|
|
|
|
LASTDISP gldPrevious = { NULL, -1, -1, -1, -1, -1, FALSE, FALSE, FALSE };
|
|
|
|
#define InvalidLastDisp( pglp ) ((pglp)->hnoNum == NULL )
|
|
|
|
|
|
void GroupDigits(TCHAR sep,
|
|
UINT nGrouping,
|
|
BOOL bIsNumNegative,
|
|
PTSTR szDisplay,
|
|
PTSTR szSepDisplay);
|
|
|
|
|
|
void DisplayNum(void)
|
|
{
|
|
SetWaitCursor( TRUE );
|
|
|
|
//
|
|
// Only change the display if
|
|
// we are in record mode -OR-
|
|
// this is the first time DisplayNum has been called, -OR-
|
|
// something important has changed since the last time DisplayNum was
|
|
// called.
|
|
//
|
|
if ( gbRecord || InvalidLastDisp( &gldPrevious ) ||
|
|
!NumObjIsEq( gldPrevious.hnoNum, ghnoNum ) ||
|
|
gldPrevious.nPrecision != nPrecision ||
|
|
gldPrevious.nRadix != nRadix ||
|
|
gldPrevious.nFE != (int)nFE ||
|
|
gldPrevious.nCalc != nCalc ||
|
|
gldPrevious.bUseSep != gbUseSep ||
|
|
gldPrevious.nHexMode != nHexMode ||
|
|
gldPrevious.fIntMath != F_INTMATH() ||
|
|
gldPrevious.bRecord != gbRecord )
|
|
{
|
|
// Assign is an expensive operation, only do when really needed
|
|
if ( ghnoNum )
|
|
NumObjAssign( &gldPrevious.hnoNum, ghnoNum );
|
|
|
|
gldPrevious.nPrecision = nPrecision;
|
|
gldPrevious.nRadix = nRadix;
|
|
gldPrevious.nFE = (int)nFE;
|
|
gldPrevious.nCalc = nCalc;
|
|
gldPrevious.nHexMode = nHexMode;
|
|
|
|
gldPrevious.fIntMath = F_INTMATH();
|
|
gldPrevious.bRecord = gbRecord;
|
|
gldPrevious.bUseSep = gbUseSep;
|
|
|
|
if (gbRecord)
|
|
{
|
|
// Display the string and return.
|
|
|
|
CIO_vConvertToString(&gpszNum, &gcio, nRadix);
|
|
}
|
|
else if (!F_INTMATH())
|
|
{
|
|
// Decimal conversion
|
|
|
|
NumObjGetSzValue( &gpszNum, ghnoNum, nRadix, nFE );
|
|
}
|
|
else
|
|
{
|
|
// Non-decimal conversion
|
|
int i;
|
|
|
|
// Truncate to an integer. Do not round here.
|
|
intrat( &ghnoNum );
|
|
|
|
// Check the range.
|
|
if ( NumObjIsLess( ghnoNum, HNO_ZERO ) )
|
|
{
|
|
// if negative make positive by doing a twos complement
|
|
NumObjNegate( &ghnoNum );
|
|
subrat( &ghnoNum, HNO_ONE );
|
|
NumObjNot( &ghnoNum );
|
|
}
|
|
|
|
andrat( &ghnoNum, g_ahnoChopNumbers[nHexMode] );
|
|
|
|
NumObjGetSzValue( &gpszNum, ghnoNum, nRadix, FMT_FLOAT );
|
|
|
|
// Clobber trailing decimal point
|
|
i = lstrlen( gpszNum ) - 1;
|
|
if ( i >= 0 && gpszNum[i] == szDec[0] )
|
|
gpszNum[i] = TEXT('\0');
|
|
}
|
|
|
|
// Display the string and return.
|
|
|
|
if (!gbUseSep)
|
|
{
|
|
TCHAR szTrailSpace[256];
|
|
|
|
lstrcpyn(szTrailSpace,gpszNum, 254);
|
|
lstrcat(szTrailSpace,TEXT(" "));
|
|
SetDisplayText(g_hwndDlg, szTrailSpace);
|
|
}
|
|
else
|
|
{
|
|
TCHAR szSepNum[256];
|
|
|
|
switch(nRadix)
|
|
{
|
|
|
|
case 10:
|
|
GroupDigits(gszSep[0],
|
|
gnDecGrouping,
|
|
(TEXT('-') == *gpszNum),
|
|
gpszNum,
|
|
szSepNum);
|
|
break;
|
|
|
|
case 8:
|
|
GroupDigits(TEXT(' '), 0x03, FALSE, gpszNum, szSepNum);
|
|
break;
|
|
|
|
case 2:
|
|
case 16:
|
|
GroupDigits(TEXT(' '), 0x04, FALSE, gpszNum, szSepNum);
|
|
break;
|
|
|
|
default:
|
|
lstrcpy(szSepNum,gpszNum);
|
|
}
|
|
|
|
lstrcat(szSepNum,TEXT(" "));
|
|
SetDisplayText(g_hwndDlg, szSepNum);
|
|
}
|
|
}
|
|
|
|
SetWaitCursor( FALSE );
|
|
|
|
return;
|
|
}
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* WatchDogThread
|
|
*
|
|
* Thread to look out for functions that take too long. If it finds one, it
|
|
* prompts the user if he wants to abort the function, and asks RATPAK to
|
|
* abort if he does.
|
|
*
|
|
* History
|
|
* 26-Nov-1996 JonPa Wrote it.
|
|
*
|
|
\****************************************************************************/
|
|
BOOL gfExiting = FALSE;
|
|
HANDLE ghCalcStart = NULL;
|
|
HANDLE ghCalcDone = NULL;
|
|
HANDLE ghDogThread = NULL;
|
|
|
|
INT_PTR TimeOutMessageBox( void );
|
|
|
|
DWORD WINAPI WatchDogThread( LPVOID pvParam ) {
|
|
DWORD cmsWait;
|
|
INT_PTR iRet;
|
|
|
|
while( !gfExiting ) {
|
|
WaitForSingleObject( ghCalcStart, INFINITE );
|
|
if (gfExiting)
|
|
break;
|
|
|
|
cmsWait = CMS_CALC_TIMEOUT;
|
|
|
|
while( WaitForSingleObject( ghCalcDone, cmsWait ) == WAIT_TIMEOUT ) {
|
|
|
|
// Put up the msg box
|
|
MessageBeep( MB_ICONEXCLAMATION );
|
|
iRet = TimeOutMessageBox();
|
|
|
|
// if user wants to cancel, then stop
|
|
if (gfExiting || iRet == IDYES || iRet == IDCANCEL) {
|
|
NumObjAbortOperation(TRUE);
|
|
break;
|
|
} else {
|
|
cmsWait *= 2;
|
|
if (cmsWait > CMS_MAX_TIMEOUT) {
|
|
cmsWait = CMS_MAX_TIMEOUT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 42;
|
|
}
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* TimeCalc
|
|
*
|
|
* Function to keep track of how long Calc is taking to do a calculation.
|
|
* If calc takes too long (about 10 sec's), then a popup is put up asking the
|
|
* user if he wants to abort the operation.
|
|
*
|
|
* Usage:
|
|
* TimeCalc( TRUE );
|
|
* do a lengthy operation
|
|
* TimeCalc( FALSE );
|
|
*
|
|
* History
|
|
* 26-Nov-1996 JonPa Wrote it.
|
|
*
|
|
\****************************************************************************/
|
|
HWND ghwndTimeOutDlg = NULL;
|
|
|
|
void TimeCalc( BOOL fStart ) {
|
|
if (ghCalcStart == NULL) {
|
|
ghCalcStart = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
}
|
|
|
|
if (ghCalcDone == NULL) {
|
|
ghCalcDone = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
}
|
|
|
|
if (ghDogThread == NULL) {
|
|
DWORD tid;
|
|
ghDogThread = CreateThread( NULL, 0, WatchDogThread, NULL, 0, &tid );
|
|
}
|
|
|
|
if (fStart) {
|
|
NumObjAbortOperation(FALSE);
|
|
ResetEvent( ghCalcDone );
|
|
SetEvent( ghCalcStart );
|
|
} else {
|
|
|
|
SetEvent( ghCalcDone );
|
|
|
|
if( ghwndTimeOutDlg != NULL ) {
|
|
SendMessage( ghwndTimeOutDlg, WM_COMMAND, IDRETRY, 0L );
|
|
}
|
|
|
|
if( NumObjWasAborted() ) {
|
|
DisplayError(SCERR_ABORTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* KillTimeCalc
|
|
*
|
|
* Should be called only at the end of the program, just before exiting, to
|
|
* kill the background timer thread and free its resources.
|
|
*
|
|
* History
|
|
* 26-Nov-1996 JonPa Wrote it.
|
|
*
|
|
\****************************************************************************/
|
|
void KillTimeCalc( void ) {
|
|
gfExiting = TRUE;
|
|
SetEvent( ghCalcStart );
|
|
SetEvent( ghCalcDone );
|
|
|
|
WaitForSingleObject( ghDogThread, CMS_MAX_TIMEOUT );
|
|
|
|
CloseHandle( ghCalcStart );
|
|
CloseHandle( ghCalcDone );
|
|
CloseHandle( ghDogThread );
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* TimeOutMessageBox
|
|
*
|
|
* Puts up a dialog that looks like a message box. If the operation returns
|
|
* before the user has responded to the dialog, the dialog gets taken away.
|
|
*
|
|
* History
|
|
* 04-Dec-1996 JonPa Wrote it.
|
|
*
|
|
\****************************************************************************/
|
|
INT_PTR
|
|
CALLBACK TimeOutDlgProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
|
|
{
|
|
RECT rc;
|
|
int y;
|
|
|
|
switch( uMsg ) {
|
|
case WM_INITDIALOG:
|
|
ghwndTimeOutDlg = hwndDlg;
|
|
|
|
//
|
|
// Move ourselves to be over the main calc window
|
|
//
|
|
|
|
// Find the display window so we don't cover it up.
|
|
GetWindowRect(GetDlgItem(g_hwndDlg, IDC_DISPLAY), &rc );
|
|
y = rc.bottom;
|
|
|
|
// Get the main calc window pos
|
|
GetWindowRect( g_hwndDlg, &rc );
|
|
|
|
SetWindowPos( hwndDlg, 0, rc.left + 15, y + 40, 0, 0,
|
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
EndDialog( hwndDlg, LOWORD(wParam) );
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
INT_PTR TimeOutMessageBox( void ) {
|
|
return (int)DialogBox( hInst, MAKEINTRESOURCE(IDD_TIMEOUT), NULL, TimeOutDlgProc );
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* DigitGroupingStringToGroupingNum
|
|
*
|
|
* Description:
|
|
* This will take the digit grouping string found in the regional applet and
|
|
* represent this string as a hex value. The grouping numbers are represented
|
|
* as 4 bit numbers logically shifted and or'd to together so:
|
|
*
|
|
* Grouping_string GroupingNum
|
|
* 0;0 0x000 - no grouping
|
|
* 3;0 0x003 - group every 3 digits
|
|
* 3;2;0 0x023 - group 1st 3 and then every 2 digits
|
|
* 4;0 0x004 - group every 4 digits
|
|
* 5;3;2;0 0x235 - group 5, then 3, then every 2
|
|
*
|
|
* Returns: the grouping number
|
|
*
|
|
* History
|
|
* 10-Sept-1999 KPeery - Wrote it to fix grouping on Hindi
|
|
*
|
|
\****************************************************************************/
|
|
UINT
|
|
DigitGroupingStringToGroupingNum(PTSTR szGrouping)
|
|
{
|
|
PTSTR p,q;
|
|
UINT n, nGrouping, shift;
|
|
|
|
if (NULL == szGrouping)
|
|
return 0;
|
|
|
|
nGrouping=0;
|
|
shift=0;
|
|
for(p=szGrouping; *p != TEXT('\0'); /* nothing */)
|
|
{
|
|
n=_tcstoul(p,&q,10);
|
|
|
|
if ((n > 0) && (n < 16))
|
|
{
|
|
n<<=shift;
|
|
shift+=4;
|
|
|
|
nGrouping|=n;
|
|
}
|
|
|
|
if (q)
|
|
p=q+1;
|
|
else
|
|
p++;
|
|
}
|
|
|
|
return nGrouping;
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* GroupDigits
|
|
*
|
|
* Description:
|
|
* This routine will take a grouping number and the display string and
|
|
* add the separator according to the pattern indicated by the separator.
|
|
*
|
|
* GroupingNum
|
|
* 0x000 - no grouping
|
|
* 0x003 - group every 3 digits
|
|
* 0x023 - group 1st 3 and then every 2 digits
|
|
* 0x004 - group every 4 digits
|
|
* 0x235 - group 5, then 3, then every 2
|
|
*
|
|
* History
|
|
* 08-Sept-1998 KPeery - Wrote orignal add num separator routine
|
|
* 10-Sept-1999 KPeery - Re-wrote it to do digit grouping in general
|
|
*
|
|
\***************************************************************************/
|
|
void
|
|
GroupDigits(TCHAR sep,
|
|
UINT nGrouping,
|
|
BOOL bIsNumNegative,
|
|
PTSTR szDisplay,
|
|
PTSTR szSepDisplay)
|
|
{
|
|
PTSTR src,dest, dec;
|
|
size_t len;
|
|
int nDigits, nOrgDigits, count;
|
|
UINT nOrgGrouping, nCurrGrouping;
|
|
|
|
if ((sep == TEXT('\0')) || (nGrouping == 0))
|
|
{
|
|
lstrcpy(szSepDisplay,szDisplay);
|
|
return;
|
|
}
|
|
|
|
// find decimal point
|
|
|
|
for(dec=szDisplay; (*dec != szDec[0]) && (*dec != TEXT('\0')); dec++)
|
|
; // do nothing
|
|
|
|
// at this point dec should point to '\0' or '.' we will add the left
|
|
// side of the number to the final string
|
|
|
|
// length of left half of number
|
|
len=(dec-szDisplay);
|
|
|
|
// num of digits
|
|
nDigits=len-(bIsNumNegative ? 1 : 0);
|
|
|
|
|
|
nOrgDigits=nDigits;
|
|
nOrgGrouping=nGrouping;
|
|
|
|
//
|
|
// ok, we must now find the adjusted len, to do that we loop
|
|
// through the grouping while keeping track of where we are in the
|
|
// number. When the nGrouping reaches 0 then we simply repeat the
|
|
// last grouping for the rest of the digits.
|
|
//
|
|
nCurrGrouping=nGrouping % 0x10;
|
|
|
|
for ( ; nDigits > 0; )
|
|
{
|
|
if ((UINT)nDigits > nCurrGrouping)
|
|
{
|
|
nDigits-=nCurrGrouping;
|
|
len++; // add one for comma
|
|
|
|
nGrouping>>=4;
|
|
|
|
if (nGrouping > 0)
|
|
nCurrGrouping=nGrouping % 0x10;
|
|
}
|
|
else
|
|
nDigits-=nCurrGrouping;
|
|
}
|
|
|
|
//
|
|
// restore the saved nDigits and grouping pattern
|
|
//
|
|
nDigits=nOrgDigits;
|
|
nGrouping=nOrgGrouping;
|
|
nCurrGrouping=nGrouping % 0x10;
|
|
|
|
//
|
|
// ok, now we know the length copy the digits, at the same time
|
|
// repeat the grouping pattern and place the seperator appropiatly,
|
|
// repeating the last grouping until we are done
|
|
//
|
|
|
|
src=dec-1;
|
|
dest=szSepDisplay+len-1;
|
|
|
|
count=0;
|
|
for( ; nDigits > 0; )
|
|
{
|
|
*dest=*src;
|
|
nDigits--;
|
|
count++;
|
|
|
|
if (((count % nCurrGrouping) == 0) && (nDigits > 0))
|
|
{
|
|
dest--;
|
|
*dest=sep;
|
|
|
|
count=0; // account for comma
|
|
|
|
nGrouping>>=4;
|
|
|
|
if (nGrouping > 0)
|
|
nCurrGrouping=nGrouping % 0x10;
|
|
}
|
|
|
|
dest--;
|
|
src--;
|
|
}
|
|
|
|
// now copy the minus sign if it is there
|
|
if (bIsNumNegative)
|
|
*szSepDisplay=*szDisplay;
|
|
|
|
//
|
|
// ok, now add the right (fractional) part of the number to the final
|
|
// string.
|
|
//
|
|
dest=szSepDisplay+len;
|
|
|
|
lstrcpy(dest,dec);
|
|
}
|
|
|
|
|