Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

505 lines
16 KiB

  1. /*
  2. * Windows Calendar
  3. * Copyright (c) 1985 by Microsoft Corporation, all rights reserved.
  4. * Written by Mark L. Chamberlin, consultant to Microsoft.
  5. *
  6. */
  7. /*
  8. *****
  9. ***** calalarm.c
  10. *****
  11. */
  12. #include "cal.h"
  13. /**** FAlarm - return TRUE if the ln has an alarm set. */
  14. BOOL APIENTRY FAlarm (INT ln)
  15. {
  16. BOOL fAlarm;
  17. WORD otqr;
  18. fAlarm = FALSE;
  19. if ((otqr = vtld [ln].otqr) != OTQRNIL)
  20. {
  21. fAlarm = ((PQR )(PbTqrLock () + otqr)) -> fAlarm;
  22. DrUnlockCur ();
  23. }
  24. return (fAlarm);
  25. }
  26. /**** AlarmToggle - Here on Alarm Set command.
  27. Since the command is only enabled
  28. when the focus in on an ln, we know that all we need to do is
  29. toggle the alarm state for the ln that has the focus.
  30. */
  31. VOID APIENTRY AlarmToggle ()
  32. {
  33. QR qrNew;
  34. WORD otqr;
  35. register PQR pqr;
  36. TM tm;
  37. RECT rect;
  38. BOOL fAlarm;
  39. BOOL fEmpty;
  40. register INT ln;
  41. DT dt;
  42. INT itdd;
  43. FT ftTemp;
  44. DR *pdr;
  45. if ((otqr = vtld [ln = vlnCur].otqr) == OTQRNIL)
  46. {
  47. /* There is no QR for this ln, so we know that it can't
  48. have an alarm set. We create a QR for it with the
  49. alarm flag set.
  50. */
  51. qrNew.cb = CBQRHEAD + 1;
  52. fAlarm = qrNew.fAlarm = TRUE;
  53. qrNew.fSpecial = FALSE;
  54. qrNew.tm = tm = vtld [ln].tm;
  55. qrNew.qd [0] = '\0';
  56. /* Since we know there
  57. was no old QR, we know FSearchTqr will not find a
  58. match - so we ignore it's return value.
  59. We call it to set up the insertion point in votqrNext.
  60. */
  61. FSearchTqr (tm);
  62. /* If there's not enough room to insert the new QR,
  63. then the alarm cannot get set, so we don't want to
  64. alter cAlarm (or any of the
  65. other stuff that gets altered if the QR is inserted).
  66. Note that FinsertQr puts up the alert.
  67. */
  68. if (!FInsertQr (votqrNext, &qrNew))
  69. return;
  70. vtld [ln].otqr = votqrNext;
  71. /* Adjust up the otqrs in the tld beyond the current ln. */
  72. AdjustOtqr (ln, CBQRHEAD + 1);
  73. }
  74. else
  75. {
  76. /* There is a QR for this ln. Toggle its alarm flag. */
  77. pqr = (PQR )(PbTqrLock () + otqr);
  78. fAlarm = pqr -> fAlarm = !pqr -> fAlarm;
  79. fEmpty = !fAlarm && !pqr -> fSpecial && pqr -> cb == CBQRHEAD + 1;
  80. DrUnlockCur ();
  81. if (fEmpty)
  82. {
  83. /* We can get rid of this QR now since it has no flags
  84. set and it has a null appointment description.
  85. */
  86. DeleteQr (otqr);
  87. vtld [ln].otqr = OTQRNIL;
  88. /* Adjust down the otqrs in the tld beyond the current ln. */
  89. AdjustOtqr (ln, -(int)(CBQRHEAD + 1));
  90. }
  91. }
  92. /* Get rid of or display the alarm bell icon. */
  93. rect.top = YcoFromLn (ln);
  94. rect.bottom = rect.top + vcyLineToLine;
  95. rect.right = (rect.left = vxcoBell) + vcxBell;
  96. InvalidateRect (vhwnd2B, (LPRECT)&rect, TRUE);
  97. UpdateWindow (vhwnd2B);
  98. /* Set the dirty flags, and adjust the count of alarms for this date. */
  99. (pdr = PdrLockCur ()) -> fDirty = vfDirty = TRUE;
  100. dt = pdr -> dt;
  101. DrUnlockCur ();
  102. FSearchTdd (dt, &itdd);
  103. (TddLock () + itdd) -> cAlarms += fAlarm ? 1 : -1;
  104. TddUnlock ();
  105. ftTemp.dt = dt;
  106. ftTemp.tm = vtld [ln].tm;
  107. if (fAlarm)
  108. {
  109. /* Setting an alarm. */
  110. if (CompareFt (&ftTemp, &vftAlarmNext) == -1
  111. && CompareFt (&ftTemp, &vftCur) > -1)
  112. {
  113. /* The alarm being set is less than the next armed alarm
  114. and it is greater than or equal to the current time.
  115. Make it the next alarm, and see if it needs to go off
  116. right now. (Waiting for it to go off "naturally" could
  117. result in its being a minutue too late since the alarms
  118. are only checked when the minute changes.)
  119. */
  120. vftAlarmNext = ftTemp;
  121. AlarmCheck ();
  122. }
  123. }
  124. else
  125. {
  126. /* Cancelling an alarm. */
  127. if (CompareFt (&ftTemp, &vftAlarmNext) == 0)
  128. {
  129. /* Cancelling the next armed alarm. Need to arm the one
  130. after it. Since the one we are cancelling has not yet
  131. gone off, it can't be time for the one after it to
  132. go off either, so there is no need to call AlarmCheck -
  133. just let it go off naturally.
  134. */
  135. GetNextAlarm (&vftCur, &vftCur, TRUE, NULL);
  136. }
  137. }
  138. }
  139. /**** ProcessAlarms */
  140. VOID APIENTRY FAR uProcessAlarms ()
  141. {
  142. static BOOL vfLocked = FALSE;
  143. /* This routine is locked to prevent reentry. This is done to prevent
  144. an alarm dialog from getting put up on top of another one. In
  145. addition to being less confusing for the user, this avoids the nasty
  146. problems of running out of resources (such as stack and heap).
  147. The reason that reentry may occur is that we arm the next alarm
  148. in the process of putting up the dialog, and while the dialog is
  149. waiting for input, timer messages can come in and the next alarm
  150. can be triggered. This is a desirable feature
  151. since it means the user still hears the audible alarm and we flash
  152. the window if the next alarm goes off before the OK button is pressed
  153. for the current alarm dialog (a likely scenario if the user is
  154. not at the machine but left it with the focus on Calendar).
  155. As soon as the user pushes the OK button for the current dialog,
  156. another dialog will be put up to show the new alarms. This is
  157. done by looping here until there are no new alarms.
  158. */
  159. /* Only enter if the routine is not locked. */
  160. if (!vfLocked)
  161. {
  162. /* Lock this routine to prevent reentry. */
  163. vfLocked = TRUE;
  164. /* vftAlarmFirst.dt will get set to DTNIL when the next alarm
  165. gets armed (during the ACKALARMS dialog). If that alarm
  166. gets triggered while the dialog box is up, vdtAlarmFirst
  167. will get set to that alarm time by AlarmCheck. We continue
  168. putting up dialog boxes as long as alarms go off while
  169. the previous one is up.
  170. */
  171. while (vftAlarmFirst.dt != DTNIL)
  172. {
  173. /* Quit flashing. */
  174. StartStopFlash (FALSE);
  175. if (vfMustSyncAlarm)
  176. {
  177. /* The system clock was changed so we need to
  178. resynchronize the alarms. Tell the user about it.
  179. */
  180. AlertBox (vszAlarmSync, (CHAR *)NULL,
  181. MB_SYSTEMMODAL | MB_OK | MB_ICONEXCLAMATION);
  182. /* Now that the user has had his last chance to mess
  183. with the clock (could have changed it again
  184. during the application modal alert), reset the
  185. flag. We will resync to the latest time that's
  186. been read by CalTimer.
  187. */
  188. vfMustSyncAlarm = FALSE;
  189. /* Arm the first alarm >= the current time. */
  190. GetNextAlarm (&vftCur, &vftCur, TRUE, NULL);
  191. /* Say there are no unacknowledged alarms. */
  192. vftAlarmFirst.dt = DTNIL;
  193. /* See if the alarm must go off immediately. If so,
  194. AlarmCheck will make vftAlarmFirst something other
  195. than DTNIL, so this loop will continue.
  196. */
  197. AlarmCheck();
  198. }
  199. else
  200. {
  201. /* Show the alarms that have been triggered, returning
  202. here after the user presses the OK button.
  203. */
  204. FDoDialog (IDD_ACKALARMS);
  205. }
  206. }
  207. /* Unlock this routine. */
  208. vfLocked = FALSE;
  209. }
  210. }
  211. /**** FnAckAlarms */
  212. INT_PTR CALLBACK FnAckAlarms (
  213. HWND hwnd,
  214. UINT message,
  215. WPARAM wParam,
  216. LPARAM lParam)
  217. {
  218. FT ftTemp;
  219. switch (message)
  220. {
  221. case WM_INITDIALOG:
  222. /* Remember the window handle of the dialog for AlertBox. */
  223. vhwndDialog = hwnd;
  224. /* Fill the list box, and arm the next alarm. The first
  225. alarm to put in the list box is the first unacknowledged
  226. one (which is in vftAlarmFirst). The next alarm to arm
  227. (the one following the last one to go into the list
  228. box), is the first one > the current time + the early
  229. ring period. Since GetNextAlarm works with >=, add
  230. one more than the early ring period.
  231. */
  232. ftTemp = vftCur;
  233. AddMinsToFt (&ftTemp, vcMinEarlyRing + 1);
  234. GetNextAlarm (&vftAlarmFirst, &ftTemp, TRUE, hwnd);
  235. /* Say there are no unacknowledged alarms. Note that
  236. there has not been an opportunity for the alarm just
  237. armed by GetNextAlarm to be triggered, since the last
  238. thing that routine does is arm the alarm so no error
  239. dialogs could have occurred after arming, and consequently
  240. we could not have yielded and processed a timer message.
  241. */
  242. vftAlarmFirst.dt = DTNIL;
  243. return (TRUE);
  244. case WM_COMMAND:
  245. if (GET_WM_COMMAND_ID(wParam, lParam) == IDOK)
  246. {
  247. EndDialog (hwnd, TRUE);
  248. return (TRUE);
  249. }
  250. /* Fall into default case if WM_COMMAND is not from IDOK. */
  251. default:
  252. /* Tell Windows we did not process the message. */
  253. return (FALSE);
  254. }
  255. }
  256. /**** GetNextAlarm */
  257. VOID APIENTRY FAR GetNextAlarm (
  258. FT *pftStart, /* Start looking at alarms >= this. */
  259. FT *pftStop, /* Stop when find alarm >= this. If an alarm >=
  260. this is found, arm it.
  261. */
  262. BOOL fDisk, /* Ok to read from disk if this is TRUE. If FALSE,
  263. give up if the next alarm is not in memory.
  264. */
  265. HWND hwnd) /* If not null, use this handle to send the triggered
  266. alarms to the list box in the alarm acknowledgement
  267. dialog box.
  268. */
  269. {
  270. /* Need enough space for a time sz (we overwrite the terminating 0
  271. with a space), a maximum length appointment description, and a
  272. terminating 0.
  273. */
  274. CHAR rgchAlarm [CCHTIMESZ + CCHQDMAX + 1];
  275. INT itdd;
  276. INT cch;
  277. DD *pdd;
  278. FT ftTemp;
  279. FT ftStart;
  280. FT ftStop;
  281. INT cAlarms;
  282. DL dl;
  283. register WORD idr;
  284. register PQR pqr;
  285. WORD idrFree;
  286. /* This could take some time if we hit the disk. */
  287. HourGlassOn();
  288. /* Make local copies of the FTs we were passed pointers to so we
  289. don't overwrite them (suppose we are passed a pointer to vftAlarmNext
  290. for example).
  291. */
  292. ftStart = *pftStart;
  293. ftStop = *pftStop;
  294. /* Say there is no next alarm. */
  295. vftAlarmNext.dt = DTNIL;
  296. /* Find a free DR in case we need to read in from disk. */
  297. idrFree = IdrFree ();
  298. for (FSearchTdd (ftStart.dt, &itdd); itdd < vcddUsed; itdd++)
  299. {
  300. pdd = TddLock () + itdd;
  301. ftTemp.dt = pdd -> dt;
  302. cAlarms = pdd -> cAlarms;
  303. dl = pdd -> dl;
  304. idr = pdd -> idr;
  305. TddUnlock ();
  306. if (cAlarms == 0)
  307. continue;
  308. if (idr == IDRNIL)
  309. {
  310. /* The next alarm is not in memory. If we're not supposed
  311. to hit the disk, we've done all we can do.
  312. */
  313. if (!fDisk)
  314. goto Exit0;
  315. /* Note - since cAlarms was not zero, there must be some
  316. data for this date somewhere. It isn't in memory, so
  317. it must be on disk. Therefore, dl cannot be DLNIL, so
  318. we don't need check to see if it is.
  319. */
  320. ReadTempDr (idr = idrFree, dl);
  321. }
  322. pqr = (PQR)PbTqrFromPdr(PdrLock(idr));
  323. for ( ; cAlarms; pqr = (PQR )((BYTE *)pqr + pqr -> cb))
  324. {
  325. if (pqr -> fAlarm)
  326. {
  327. cAlarms--;
  328. /* Remember the time of the alarm. */
  329. ftTemp.tm = pqr -> tm;
  330. if (CompareFt (&ftTemp, &ftStart) != -1)
  331. {
  332. /* This ft is greater than or equal to ftStart. */
  333. if (CompareFt (&ftTemp, &ftStop) != -1)
  334. {
  335. /* This ft is greater than or equal
  336. to ftStop, so this is the next alarm.
  337. */
  338. vftAlarmNext = ftTemp;
  339. DrUnlock (idr);
  340. goto Exit0;
  341. }
  342. /* This is a triggered alarm, so put it into the
  343. list box of the alarm acknowledgement dialog box.
  344. */
  345. if (hwnd != NULL)
  346. {
  347. cch = GetTimeSz(pqr -> tm, rgchAlarm);
  348. rgchAlarm[cch] = ' ';
  349. lstrcpy(&rgchAlarm[cch + 1], pqr -> qd);
  350. if (SendDlgItemMessage (hwnd, IDCN_LISTBOX,
  351. LB_ADDSTRING, 0, (LPARAM)rgchAlarm)
  352. == LB_ERRSPACE)
  353. {
  354. /* ??? Not enough memory. Be sure to unlock
  355. if bail out due to this error.
  356. */
  357. }
  358. }
  359. }
  360. }
  361. }
  362. DrUnlock (idr);
  363. }
  364. /* End of tdd reached - there is no next alarm. */
  365. Exit0:
  366. HourGlassOff ();
  367. }
  368. /**** IdrFree - find a free DR. */
  369. WORD APIENTRY IdrFree ()
  370. {
  371. register WORD idr;
  372. register DT dt;
  373. /* Find a free DR to read the date into. There is guaranteed
  374. to be at least one free one since there are 3 and we keep at
  375. most 2 dates in memory at one time, so finding a free one
  376. will terminate this loop with idr containing the index of the
  377. free DR.
  378. */
  379. idr = CDR;
  380. do
  381. {
  382. idr--;
  383. dt = PdrLock(idr)->dt;
  384. DrUnlock (idr);
  385. }
  386. while (dt != DTNIL);
  387. return (idr);
  388. }
  389. /**** ReadTempDr - read date into DR for temporary use. */
  390. VOID APIENTRY ReadTempDr (
  391. WORD idr,
  392. DL dl)
  393. {
  394. register DR *pdr;
  395. pdr = PdrLock (idr);
  396. if (!FReadDrFromFile (TRUE, pdr, dl))
  397. {
  398. /* ??? Error trying to read date - what now? */
  399. }
  400. /* Make the DR still look free since we are only using it
  401. temporarily.
  402. */
  403. pdr -> dt = DTNIL;
  404. DrUnlock (idr);
  405. }
  406. /**** StartStopFlash */
  407. VOID APIENTRY StartStopFlash (BOOL fStart)
  408. /* FALSE means stop flashing, TRUE means start flashing. */
  409. {
  410. if (fStart != vfFlashing)
  411. FlashWindow (vhwnd0, vfFlashing = fStart);
  412. }