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

506 lines
16 KiB
C

/*
* Windows Calendar
* Copyright (c) 1985 by Microsoft Corporation, all rights reserved.
* Written by Mark L. Chamberlin, consultant to Microsoft.
*
*/
/*
*****
***** calalarm.c
*****
*/
#include "cal.h"
/**** FAlarm - return TRUE if the ln has an alarm set. */
BOOL APIENTRY FAlarm (INT ln)
{
BOOL fAlarm;
WORD otqr;
fAlarm = FALSE;
if ((otqr = vtld [ln].otqr) != OTQRNIL)
{
fAlarm = ((PQR )(PbTqrLock () + otqr)) -> fAlarm;
DrUnlockCur ();
}
return (fAlarm);
}
/**** AlarmToggle - Here on Alarm Set command.
Since the command is only enabled
when the focus in on an ln, we know that all we need to do is
toggle the alarm state for the ln that has the focus.
*/
VOID APIENTRY AlarmToggle ()
{
QR qrNew;
WORD otqr;
register PQR pqr;
TM tm;
RECT rect;
BOOL fAlarm;
BOOL fEmpty;
register INT ln;
DT dt;
INT itdd;
FT ftTemp;
DR *pdr;
if ((otqr = vtld [ln = vlnCur].otqr) == OTQRNIL)
{
/* There is no QR for this ln, so we know that it can't
have an alarm set. We create a QR for it with the
alarm flag set.
*/
qrNew.cb = CBQRHEAD + 1;
fAlarm = qrNew.fAlarm = TRUE;
qrNew.fSpecial = FALSE;
qrNew.tm = tm = vtld [ln].tm;
qrNew.qd [0] = '\0';
/* Since we know there
was no old QR, we know FSearchTqr will not find a
match - so we ignore it's return value.
We call it to set up the insertion point in votqrNext.
*/
FSearchTqr (tm);
/* If there's not enough room to insert the new QR,
then the alarm cannot get set, so we don't want to
alter cAlarm (or any of the
other stuff that gets altered if the QR is inserted).
Note that FinsertQr puts up the alert.
*/
if (!FInsertQr (votqrNext, &qrNew))
return;
vtld [ln].otqr = votqrNext;
/* Adjust up the otqrs in the tld beyond the current ln. */
AdjustOtqr (ln, CBQRHEAD + 1);
}
else
{
/* There is a QR for this ln. Toggle its alarm flag. */
pqr = (PQR )(PbTqrLock () + otqr);
fAlarm = pqr -> fAlarm = !pqr -> fAlarm;
fEmpty = !fAlarm && !pqr -> fSpecial && pqr -> cb == CBQRHEAD + 1;
DrUnlockCur ();
if (fEmpty)
{
/* We can get rid of this QR now since it has no flags
set and it has a null appointment description.
*/
DeleteQr (otqr);
vtld [ln].otqr = OTQRNIL;
/* Adjust down the otqrs in the tld beyond the current ln. */
AdjustOtqr (ln, -(int)(CBQRHEAD + 1));
}
}
/* Get rid of or display the alarm bell icon. */
rect.top = YcoFromLn (ln);
rect.bottom = rect.top + vcyLineToLine;
rect.right = (rect.left = vxcoBell) + vcxBell;
InvalidateRect (vhwnd2B, (LPRECT)&rect, TRUE);
UpdateWindow (vhwnd2B);
/* Set the dirty flags, and adjust the count of alarms for this date. */
(pdr = PdrLockCur ()) -> fDirty = vfDirty = TRUE;
dt = pdr -> dt;
DrUnlockCur ();
FSearchTdd (dt, &itdd);
(TddLock () + itdd) -> cAlarms += fAlarm ? 1 : -1;
TddUnlock ();
ftTemp.dt = dt;
ftTemp.tm = vtld [ln].tm;
if (fAlarm)
{
/* Setting an alarm. */
if (CompareFt (&ftTemp, &vftAlarmNext) == -1
&& CompareFt (&ftTemp, &vftCur) > -1)
{
/* The alarm being set is less than the next armed alarm
and it is greater than or equal to the current time.
Make it the next alarm, and see if it needs to go off
right now. (Waiting for it to go off "naturally" could
result in its being a minutue too late since the alarms
are only checked when the minute changes.)
*/
vftAlarmNext = ftTemp;
AlarmCheck ();
}
}
else
{
/* Cancelling an alarm. */
if (CompareFt (&ftTemp, &vftAlarmNext) == 0)
{
/* Cancelling the next armed alarm. Need to arm the one
after it. Since the one we are cancelling has not yet
gone off, it can't be time for the one after it to
go off either, so there is no need to call AlarmCheck -
just let it go off naturally.
*/
GetNextAlarm (&vftCur, &vftCur, TRUE, NULL);
}
}
}
/**** ProcessAlarms */
VOID APIENTRY FAR uProcessAlarms ()
{
static BOOL vfLocked = FALSE;
/* This routine is locked to prevent reentry. This is done to prevent
an alarm dialog from getting put up on top of another one. In
addition to being less confusing for the user, this avoids the nasty
problems of running out of resources (such as stack and heap).
The reason that reentry may occur is that we arm the next alarm
in the process of putting up the dialog, and while the dialog is
waiting for input, timer messages can come in and the next alarm
can be triggered. This is a desirable feature
since it means the user still hears the audible alarm and we flash
the window if the next alarm goes off before the OK button is pressed
for the current alarm dialog (a likely scenario if the user is
not at the machine but left it with the focus on Calendar).
As soon as the user pushes the OK button for the current dialog,
another dialog will be put up to show the new alarms. This is
done by looping here until there are no new alarms.
*/
/* Only enter if the routine is not locked. */
if (!vfLocked)
{
/* Lock this routine to prevent reentry. */
vfLocked = TRUE;
/* vftAlarmFirst.dt will get set to DTNIL when the next alarm
gets armed (during the ACKALARMS dialog). If that alarm
gets triggered while the dialog box is up, vdtAlarmFirst
will get set to that alarm time by AlarmCheck. We continue
putting up dialog boxes as long as alarms go off while
the previous one is up.
*/
while (vftAlarmFirst.dt != DTNIL)
{
/* Quit flashing. */
StartStopFlash (FALSE);
if (vfMustSyncAlarm)
{
/* The system clock was changed so we need to
resynchronize the alarms. Tell the user about it.
*/
AlertBox (vszAlarmSync, (CHAR *)NULL,
MB_SYSTEMMODAL | MB_OK | MB_ICONEXCLAMATION);
/* Now that the user has had his last chance to mess
with the clock (could have changed it again
during the application modal alert), reset the
flag. We will resync to the latest time that's
been read by CalTimer.
*/
vfMustSyncAlarm = FALSE;
/* Arm the first alarm >= the current time. */
GetNextAlarm (&vftCur, &vftCur, TRUE, NULL);
/* Say there are no unacknowledged alarms. */
vftAlarmFirst.dt = DTNIL;
/* See if the alarm must go off immediately. If so,
AlarmCheck will make vftAlarmFirst something other
than DTNIL, so this loop will continue.
*/
AlarmCheck();
}
else
{
/* Show the alarms that have been triggered, returning
here after the user presses the OK button.
*/
FDoDialog (IDD_ACKALARMS);
}
}
/* Unlock this routine. */
vfLocked = FALSE;
}
}
/**** FnAckAlarms */
INT_PTR CALLBACK FnAckAlarms (
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
FT ftTemp;
switch (message)
{
case WM_INITDIALOG:
/* Remember the window handle of the dialog for AlertBox. */
vhwndDialog = hwnd;
/* Fill the list box, and arm the next alarm. The first
alarm to put in the list box is the first unacknowledged
one (which is in vftAlarmFirst). The next alarm to arm
(the one following the last one to go into the list
box), is the first one > the current time + the early
ring period. Since GetNextAlarm works with >=, add
one more than the early ring period.
*/
ftTemp = vftCur;
AddMinsToFt (&ftTemp, vcMinEarlyRing + 1);
GetNextAlarm (&vftAlarmFirst, &ftTemp, TRUE, hwnd);
/* Say there are no unacknowledged alarms. Note that
there has not been an opportunity for the alarm just
armed by GetNextAlarm to be triggered, since the last
thing that routine does is arm the alarm so no error
dialogs could have occurred after arming, and consequently
we could not have yielded and processed a timer message.
*/
vftAlarmFirst.dt = DTNIL;
return (TRUE);
case WM_COMMAND:
if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK)
{
EndDialog (hwnd, TRUE);
return (TRUE);
}
/* Fall into default case if WM_COMMAND is not from IDOK. */
default:
/* Tell Windows we did not process the message. */
return (FALSE);
}
}
/**** GetNextAlarm */
VOID APIENTRY FAR GetNextAlarm (
FT *pftStart, /* Start looking at alarms >= this. */
FT *pftStop, /* Stop when find alarm >= this. If an alarm >=
this is found, arm it.
*/
BOOL fDisk, /* Ok to read from disk if this is TRUE. If FALSE,
give up if the next alarm is not in memory.
*/
HWND hwnd) /* If not null, use this handle to send the triggered
alarms to the list box in the alarm acknowledgement
dialog box.
*/
{
/* Need enough space for a time sz (we overwrite the terminating 0
with a space), a maximum length appointment description, and a
terminating 0.
*/
CHAR rgchAlarm [CCHTIMESZ + CCHQDMAX + 1];
INT itdd;
INT cch;
DD *pdd;
FT ftTemp;
FT ftStart;
FT ftStop;
INT cAlarms;
DL dl;
register WORD idr;
register PQR pqr;
WORD idrFree;
/* This could take some time if we hit the disk. */
HourGlassOn();
/* Make local copies of the FTs we were passed pointers to so we
don't overwrite them (suppose we are passed a pointer to vftAlarmNext
for example).
*/
ftStart = *pftStart;
ftStop = *pftStop;
/* Say there is no next alarm. */
vftAlarmNext.dt = DTNIL;
/* Find a free DR in case we need to read in from disk. */
idrFree = IdrFree ();
for (FSearchTdd (ftStart.dt, &itdd); itdd < vcddUsed; itdd++)
{
pdd = TddLock () + itdd;
ftTemp.dt = pdd -> dt;
cAlarms = pdd -> cAlarms;
dl = pdd -> dl;
idr = pdd -> idr;
TddUnlock ();
if (cAlarms == 0)
continue;
if (idr == IDRNIL)
{
/* The next alarm is not in memory. If we're not supposed
to hit the disk, we've done all we can do.
*/
if (!fDisk)
goto Exit0;
/* Note - since cAlarms was not zero, there must be some
data for this date somewhere. It isn't in memory, so
it must be on disk. Therefore, dl cannot be DLNIL, so
we don't need check to see if it is.
*/
ReadTempDr (idr = idrFree, dl);
}
pqr = (PQR)PbTqrFromPdr(PdrLock(idr));
for ( ; cAlarms; pqr = (PQR )((BYTE *)pqr + pqr -> cb))
{
if (pqr -> fAlarm)
{
cAlarms--;
/* Remember the time of the alarm. */
ftTemp.tm = pqr -> tm;
if (CompareFt (&ftTemp, &ftStart) != -1)
{
/* This ft is greater than or equal to ftStart. */
if (CompareFt (&ftTemp, &ftStop) != -1)
{
/* This ft is greater than or equal
to ftStop, so this is the next alarm.
*/
vftAlarmNext = ftTemp;
DrUnlock (idr);
goto Exit0;
}
/* This is a triggered alarm, so put it into the
list box of the alarm acknowledgement dialog box.
*/
if (hwnd != NULL)
{
cch = GetTimeSz(pqr -> tm, rgchAlarm);
rgchAlarm[cch] = ' ';
lstrcpy(&rgchAlarm[cch + 1], pqr -> qd);
if (SendDlgItemMessage (hwnd, IDCN_LISTBOX,
LB_ADDSTRING, 0, (LPARAM)rgchAlarm)
== LB_ERRSPACE)
{
/* ??? Not enough memory. Be sure to unlock
if bail out due to this error.
*/
}
}
}
}
}
DrUnlock (idr);
}
/* End of tdd reached - there is no next alarm. */
Exit0:
HourGlassOff ();
}
/**** IdrFree - find a free DR. */
WORD APIENTRY IdrFree ()
{
register WORD idr;
register DT dt;
/* Find a free DR to read the date into. There is guaranteed
to be at least one free one since there are 3 and we keep at
most 2 dates in memory at one time, so finding a free one
will terminate this loop with idr containing the index of the
free DR.
*/
idr = CDR;
do
{
idr--;
dt = PdrLock(idr)->dt;
DrUnlock (idr);
}
while (dt != DTNIL);
return (idr);
}
/**** ReadTempDr - read date into DR for temporary use. */
VOID APIENTRY ReadTempDr (
WORD idr,
DL dl)
{
register DR *pdr;
pdr = PdrLock (idr);
if (!FReadDrFromFile (TRUE, pdr, dl))
{
/* ??? Error trying to read date - what now? */
}
/* Make the DR still look free since we are only using it
temporarily.
*/
pdr -> dt = DTNIL;
DrUnlock (idr);
}
/**** StartStopFlash */
VOID APIENTRY StartStopFlash (BOOL fStart)
/* FALSE means stop flashing, TRUE means start flashing. */
{
if (fStart != vfFlashing)
FlashWindow (vhwnd0, vfFlashing = fStart);
}