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

599 lines
18 KiB
C

/*
* Windows Calendar
* Copyright (c) 1985 by Microsoft Corporation, all rights reserved.
* Written by Mark L. Chamberlin, consultant to Microsoft.
*
*/
/*
*****
***** caltqr.c
*****
*/
#include "cal.h"
/**** FSearchTqr */
BOOL APIENTRY FSearchTqr (TM tm)
{
BYTE *pbPrev;
BYTE *pbCur;
BYTE *pbNext;
BYTE *pbFirst;
BYTE *pbMax;
BOOL fFound;
DR *pdr;
pdr = PdrLockCur ();
votqrPrev = votqrCur = OTQRNIL;
votqrNext = pdr -> cbTqr;
pbMax = (pbCur = pbFirst = PbTqrFromPdr (pdr)) + pdr -> cbTqr;
fFound = FALSE;
while (pbCur < pbMax)
{
pbNext = pbCur + ((PQR )pbCur) -> cb;
if (((PQR )pbCur) -> tm == tm)
{
votqrCur = (WORD)(pbCur - pbFirst);
votqrNext = (WORD)(pbNext - pbFirst);
fFound = TRUE;
break;
}
else if (((PQR )pbCur) -> tm > tm)
{
votqrNext = (WORD)(pbCur - pbFirst);
break;
}
pbPrev = pbCur;
pbCur = pbNext;
}
if (pbCur != pbFirst)
votqrPrev = (WORD)(pbPrev - pbFirst);
DrUnlockCur ();
return (fFound);
}
/**** StoreQd - Move the appointment description from the edit control
into the tqr.
No need to check for room in DR here since we have been monitoring
key strokes and checking for room.
*/
VOID APIENTRY StoreQd ()
{
BYTE rgbQrBuf [CBQRHEAD + CCHQDMAX + 1];
CHAR szqdOld [CCHQDMAX + 1];
INT cchqdOld;
INT cchqdNew;
WORD otqr;
BYTE *pb;
BOOL fSame;
register INT ln;
/* Copy into register variable to save code. */
ln = vlnCur;
/* Fetch the text from the edit control. */
cchqdNew = (int)SendMessage (vhwnd3, WM_GETTEXT, CCHQDMAX + 1,
(LPARAM)(rgbQrBuf + CBQRHEAD));
*szqdOld = '\0';
cchqdOld = 0;
pb = PbTqrLock ();
if ((otqr = vtld [ln].otqr) != OTQRNIL)
{
cchqdOld = ((PQR )(pb + otqr)) -> cb - CBQRHEAD - 1;
lstrcpy (szqdOld, (CHAR *)(pb + otqr + CBQRHEAD));
}
fSame = cchqdOld == cchqdNew
&& lstrcmp ((LPSTR)szqdOld, (LPSTR)(rgbQrBuf + CBQRHEAD)) == 0;
DrUnlockCur ();
if (fSame)
{
/* This thing hasn't changed, so don't waste alot of time. */
return;
}
/* The QD has changed, so mark the DR and the file dirty. */
PdrLockCur () -> fDirty = vfDirty = TRUE;
DrUnlockCur ();
if (otqr == OTQRNIL)
{
/* There was no previous QR. Say there is no alarm set,
say it's not a special time, and copy in the appointment
time from the tld.
*/
((PQR )rgbQrBuf) -> fAlarm = ((PQR )rgbQrBuf) -> fSpecial = FALSE;
((PQR )rgbQrBuf) -> tm = vtld [ln].tm;
}
else
{
/* There was a previous QR. Copy it's header information
(we want the flags and the appointment time) into the
new QR we're building.
*/
BltByte (PbTqrLock () + otqr, rgbQrBuf, CBQRHEAD);
DrUnlockCur ();
/* Delete the old QR. */
DeleteQr (otqr);
/* In case we don't insert a new qr. */
vtld [ln].otqr = OTQRNIL;
/* Adjust down the otqrs in the tld beyond the current ln. */
AdjustOtqr (ln, -((PQR )rgbQrBuf) -> cb);
}
/* Set the length of the new QR. */
((PQR )rgbQrBuf) -> cb = (WORD)(CBQRHEAD + cchqdNew + 1);
if (cchqdNew != 0 || ((PQR )rgbQrBuf) -> fAlarm
|| ((PQR )rgbQrBuf) -> fSpecial)
{
if (otqr == OTQRNIL)
{
/* There was no previous QR, so search the tqr to find
out where to put the new one. Since we know there
was no old QR, we know FSearchTqr will not find a
match - so we ignore its return value.
*/
FSearchTqr (vtld [ln].tm);
otqr = votqrNext;
}
FInsertQr (otqr, (PQR )rgbQrBuf);
vtld [ln].otqr = otqr;
/* Adjust up the otqrs in the tld beyond the current ln. */
AdjustOtqr (ln, ((PQR )rgbQrBuf) -> cb);
}
}
/**** AdjustOtqr */
VOID APIENTRY AdjustOtqr (
INT ln, /* otqr starting with vtld [ln + 1] are adjusted. */
INT cb) /* The amount to adjust the otqr by (can be negative). */
{
while (++ln < vcln)
{
if (vtld [ln].otqr != OTQRNIL)
vtld [ln].otqr += (WORD)cb;
}
}
/**** DeleteQr - delete qr from tqr. */
VOID APIENTRY DeleteQr (WORD otqr)
{
BYTE *pbTqr;
BYTE *pbSrc;
BYTE *pbDst;
WORD cb;
DR *pdr;
pdr = PdrLockCur ();
pbDst = (pbTqr = PbTqrFromPdr (pdr)) + otqr;
pbSrc = pbDst + (cb = ((PQR )pbDst) -> cb);
BltByte (pbSrc, pbDst, (WORD)(pbTqr + pdr -> cbTqr - pbSrc));
pdr -> cbTqr -= cb;
DrUnlockCur ();
}
/**** FInsertQr - insert qr into tqr. */
BOOL APIENTRY FInsertQr (
WORD otqr,
PQR pqr)
{
BYTE *pbTqr;
register BYTE *pbSrc;
BYTE *pbDst;
WORD cb;
register DR *pdr;
BOOL fOk;
/* Make sure there's enough room. */
pdr = PdrLockCur ();
if ((WORD)(fOk = cb = pqr->cb) <= (WORD)(CBTQRMAX - pdr -> cbTqr))
{
/* Zero the unused bits. */
pqr -> reserved = 0;
pbSrc = (pbTqr = PbTqrFromPdr (pdr)) + otqr;
pbDst = pbSrc + cb;
BltByte (pbSrc, pbDst, (WORD)(pbTqr + pdr -> cbTqr - pbSrc));
BltByte ((BYTE *)(pqr), pbSrc, cb);
pdr -> cbTqr += cb;
}
else
{
/* Tell the user the date is full. */
AlertBox (vszDateIsFull, (CHAR *)NULL,
MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
}
DrUnlockCur ();
return (fOk);
}
/**** PbTqrLock - Lock the current DR, returning a pointer to the first
byte of the embedded Tqr.
*/
BYTE * APIENTRY PbTqrLock ()
{
register DR *pdr;
pdr = PdrLockCur ();
return (PbTqrFromPdr (pdr));
}
/**** PdrLockCur - Lock the current DR, returning a pointer to it. */
DR * APIENTRY PdrLockCur ()
{
return (PdrLock (vidrCur));
}
/**** DrUnlockCur - Unlock the current DR. */
VOID APIENTRY DrUnlockCur ()
{
DrUnlock (vidrCur);
}
/**** PdrLock - lock and return a pointer to the specified DR. */
DR * APIENTRY PdrLock (UINT idr)
{
return ((DR *)LocalLock (vrghlmDr [idr]));
}
/**** DrUnlock - unlock the specified DR. */
VOID APIENTRY DrUnlock (UINT idr)
{
LocalUnlock (vrghlmDr [idr]);
}
/**** PbTqrFromPdr - Given a pdr, return a pointer to the first byte
of the embedded tqr.
*/
BYTE * APIENTRY PbTqrFromPdr (DR*pdr)
{
return ((BYTE *)pdr + CBDRHEAD + pdr -> cbNotes);
}
/**** StoreNotes - store the notes into the DR.
No need to check for room in DR here since we have been monitoring
key strokes and checking for room.
*/
VOID APIENTRY StoreNotes ()
{
BYTE rgbEcNotes [CBNOTESMAX];
CHAR *szNew;
CHAR *szOld;
register INT cbNew;
INT cbMore;
register DR *pdr;
BOOL fFormatted;
/* Set up a pointer to the old notes, using the null string if there
were no old notes. Also assume the new notes are empty.
*/
szNew = szOld = "";
pdr = PdrLockCur ();
if (pdr -> cbNotes != 0)
szOld = (CHAR *)((BYTE *)pdr + CBDRHEAD);
/* Format the new text. This inserts <CR,CR,LF> where soft line breaks
are. (Hard line breaks are represented by <CR,LF>.)
*/
fFormatted = (BOOL)SendMessage (vhwnd2C, EM_FMTLINES, TRUE, 0L);
/* Find out how long the text is (not including the string
terminator).
*/
cbNew = (int)SendMessage (vhwnd2C, WM_GETTEXTLENGTH, 0, 0L);
if (cbNew != 0)
{
/* String is not null - will need an extra byte to store the zero
terminator (which is why we ++cbNew here).
*/
SendMessage (vhwndFocus, WM_GETTEXT, ++cbNew,
(LPARAM)(szNew = rgbEcNotes));
}
/* If the notes have not changed don't mark the DR dirty or store the
new (unchanged) text. (For speed, don't strcmp if length not same.)
*/
if ((cbMore = cbNew - pdr -> cbNotes) != 0 || lstrcmp ((LPSTR)szOld, (LPSTR)szNew) != 0)
{
/* Adjust the hole for the notes if necessary by moving the tqr
up or down. Note that the same length case works OK too.
*/
BltByte (PbTqrFromPdr (pdr), PbTqrFromPdr (pdr) + cbMore,
pdr -> cbTqr);
/* Copy in the new string. Note - can't copy to szOld since
it is not in the DR if the old string was null.
*/
BltByte ((BYTE *)szNew, (BYTE *)pdr + CBDRHEAD, cbNew);
/* Set the new length. */
pdr -> cbNotes = (WORD)cbNew;
/* Mark the DR and the file dirty. */
pdr -> fDirty = vfDirty = TRUE;
}
DrUnlockCur ();
/* Remove soft line breaks. */
if (fFormatted)
SendMessage (vhwnd2C, EM_FMTLINES, FALSE, 0L);
}
/**** SetNotesEc - Set the text of the notes edit control. */
VOID APIENTRY SetNotesEc ()
{
register CHAR *szNotes;
register DR *pdr;
szNotes = "";
if ((pdr = PdrLockCur ()) -> cbNotes != 0)
szNotes = (CHAR *)((BYTE *)pdr + CBDRHEAD);
SetEcText(vhwnd2C, szNotes);
DrUnlockCur ();
/* Length of text in edit ctl will probably be different than cbNotes
since formatting info has been removed. Set selection to end of text
based on new length. */
}
/**** EcNotification - handle notifications from edit controls.
Note - all of the vcbEcTextMax code assumes that the data in the
DR is up-to-date. This is the case as long as my assumption that
you can't get an EN_SETFOCUS for the same edit control without
having seen an EN_KILLFOCUS in the intervening time.
(The KILLFOCUS will bring the DR up-to-date because that's when
we store the data.)
*/
INT vcbEcTextMax = 32767; /* This is the maximum number of bytes we can
allow the text of the edit control to grow to.
This does NOT include the zero terminator,
only the space used by the text itself.
We reserve space for the terminator outside
of this count.
Keep it set very large when not otherwise
set up so an EN_CHANGE (which can come in
from a WM_SETTEXT for example) won't
erroneously trigger a DR full situation.
*/
VOID APIENTRY EcNotification (
WORD idec, /* ID of the edit control. */
WORD en) /* The notification code. */
{
register DR *pdr;
WORD otqr;
register BOOL fQd;
if ((fQd = idec == IDECQD) || idec == IDECNOTES)
{
switch (en)
{
case EN_SETFOCUS:
if (fQd)
{
vhwndFocus = vhwnd3;
/* Assuming there is no QR for this appointment time,
we will need room to insert one (CBQRHEAD + 1 (for
the string terminator)). Subtract this from the
available space for the tqr to grow to determine
the maximum
size we can allow for the edit control text.
(The available space is given by CBTQRMAX -
cbTqr.)
*/
pdr = PdrLockCur ();
vcbEcTextMax = CBTQRMAX - CBQRHEAD - 1 - pdr -> cbTqr;
if ((otqr = vtld [vlnCur].otqr) != OTQRNIL)
{
/* There is already a QR for this appointment
time, so the space it's occupying will also
be available.
*/
vcbEcTextMax +=
((PQR )(PbTqrFromPdr (pdr) + otqr)) -> cb;
}
DrUnlockCur ();
/* Don't allow vcbEcTextMax to be negative (which
it could be right now if there's no QR for this
appointment and the amount of free space for
the tqr is less than CBQRHEAD + 1). By setting it
to zero we will prevent any characters from
being typed, and the pruning process will work
correctly (which it wouldn't for a negative
value).
*/
if (vcbEcTextMax < 0)
vcbEcTextMax = 0;
}
else
{
vhwndFocus = vhwnd2C;
/* The notes edit control is not allowed to grow
beyond CBNOTESTESTMAX bytes.
*/
vcbEcTextMax = CBNOTESTEXTMAX;
}
break;
case EN_KILLFOCUS:
/* Put big value in while not in use. */
vcbEcTextMax = 32767;
if (fQd)
{
/* Leaving appointment edit control - store away
the text if it has changed.
*/
StoreQd ();
}
else
{
/* Leaving notes edit control - store away the
text if it has changed.
*/
StoreNotes ();
}
break;
case EN_ERRSPACE:
AlertBox (vszOutOfMemory, (CHAR *) NULL,
MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
break;
case EN_MAXTEXT:
AlertBox (vszTextTruncated, (CHAR *) NULL,
MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
break;
case EN_CHANGE:
/* If this change is not for the edit control that has
the focus, ignore it. (This happens for example
when we have set the focus to a QD and then set the
text of the notes - as when switching to a new date.)
If we didn't ignore it we would be comparing the
wrong lengths against each other.
Also check out of here if we haven't exceeded the
maximum text length yet.
*/
if (vhwndFocus == (fQd ? vhwnd3 : vhwnd2C)
&& ((INT)SendMessage (vhwndFocus, WM_GETTEXTLENGTH,0,0L) > vcbEcTextMax))
PruneEcText ();
/* Fall into the return. */
}
}
}
/**** PruneEcText - truncate edit control text since it won't fit in the DR.
This is a separate routine so that the large buffer on the stack
would not be allocated in EcNotification. This is because EcNotification
gets called recursively (because WM_SETTEXT causes an EN_CHANGE) and
it also calls StoreNotes which also uses a large buffer on the stack.
*/
VOID APIENTRY PruneEcText ()
{
BYTE rgbEcTextBuf [CBNOTESMAX];
register BYTE *pbCur;
INT cbEcText;
INT cbTemp;
/* That last change made the text too big to fit in
the DR. Truncate the text, and tell the user about it.
*/
SendMessage (vhwndFocus, WM_GETTEXT, CBNOTESMAX, (LPARAM)rgbEcTextBuf);
/* Truncate what doesn't fit, being careful not
to truncate in the middle of a multi-byte
character (Kanji).
Note that this loop is guaranteed to execute at
least once (since 0 has to be <= vcbEcTectMax), so
cbEcText will definitely get set.
*/
for (pbCur = rgbEcTextBuf;
(cbTemp = (int)(pbCur - rgbEcTextBuf)) <= vcbEcTextMax;
pbCur = (BYTE *)AnsiNext ((LPSTR)pbCur))
cbEcText = cbTemp;
/* cbEcText now contains the count of bytes in the
the characters that would fit (not including the
string terminator). Put in the terminator.
*/
rgbEcTextBuf [cbEcText] = '\0';
/* Put the shortened text back into the edit control.
This will put the caret
at the end of the text and redisplay. (Putting
the caret at the end is good since it draws the
user's attention to where the chopping occurred.)
Note that this WM_SETTEXT will cause an EN_CHANGE, but the
text will no longer be too long so we will not end up
recursing into PruneEcText (which we want to avoid due
to the large buffer on the stack).
*/
SetEcText(vhwndFocus, rgbEcTextBuf);
/* Tell the user the bad news. */
AlertBox (vszDateIsFull, vszTextTruncated,
MB_APPLMODAL | MB_OK | MB_ICONEXCLAMATION);
}