/* * 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); }