source: trunk/src/helpers/timer.c@ 126

Last change on this file since 126 was 123, checked in by umoeller, 24 years ago

Lots of changes for icons and refresh.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 25.6 KB
Line 
1
2/*
3 *@@sourcefile timer.c:
4 * XTimers, which can be used to avoid excessive PM
5 * timer usage.
6 *
7 * The functions here allow you to share a single PM
8 * timer for many windows on the same thread. Basically,
9 * these start a "master PM timer", to whose WM_TIMER
10 * message your window procedure must respond by calling
11 * tmrTimerTick, which will "distribute" the timer tick
12 * to those XTimers which have elapsed.
13 *
14 * So to use XTimers, do the following:
15 *
16 * 1. Create a timer set with tmrCreateSet. Specify
17 * an owner window and the timer ID of the master
18 * PM timer.
19 *
20 * 2. You can then start and stop XTimers for windows
21 * on the same thread by calling tmrStartXTimer and
22 * tmrStopXTimer, respectively.
23 *
24 * 3. In the window proc of the owner window, respond
25 * to WM_TIMER for the master PM timer by calling
26 * tmrTimerTick. This will call the window procs
27 * of those windows with WM_TIMER messages directly
28 * whose XTimers have elapsed.
29 *
30 * The main advantage of the XTimers is that these
31 * are not a limited resource (as opposed to PM timers).
32 * I don't know how many PM timers exist on the system,
33 * but PMREF says that the no. of remaining timers can
34 * be queried with WinQuerySysValue(SV_CTIMERS).
35 *
36 * There are a few limitations with the XTimers though:
37 *
38 * -- If you start a timer with a timeout < 100 ms,
39 * the first WM_TIMER might not appear before
40 * 100 ms have elapsed. This may or be not the
41 * case, depending on whether other timers are
42 * running.
43 *
44 * -- tmrTimerTick (which you must call when the
45 * "master" WM_TIMER comes in) does not post WM_TIMER
46 * messages to the windows specified in the subtimers,
47 * but calls the window procedure of the target window
48 * directly. This makes sure that timers work even if
49 * some thread is hogging the SIQ.
50 *
51 * This however requires that all XTimers must run on
52 * the same thread as the owner window of the master
53 * timer which was specified with tmrCreateSet.
54 *
55 * -- Queue timers (with HWND == NULLHANDLE) are not
56 * supported.
57 *
58 * -- When a window is destroyed, its timers are not
59 * automatically cleaned up. tmrTimerTick does
60 * detect invalid windows and removes them from the
61 * timers list before posting, but to be on the safe
62 * side, always call tmrStopAllTimers when WM_DESTROY
63 * comes into a window which has used timers.
64 *
65 * Function prefixes:
66 * -- tmr* timer functions
67 *
68 *@@header "helpers\timer.h"
69 *@@added V0.9.7 (2000-12-04) [umoeller]
70 */
71
72/*
73 * Copyright (C) 2000-2001 Ulrich M”ller.
74 * This file is part of the "XWorkplace helpers" source package.
75 * This is free software; you can redistribute it and/or modify
76 * it under the terms of the GNU General Public License as published
77 * by the Free Software Foundation, in version 2 as it comes in the
78 * "COPYING" file of the XWorkplace main distribution.
79 * This program is distributed in the hope that it will be useful,
80 * but WITHOUT ANY WARRANTY; without even the implied warranty of
81 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
82 * GNU General Public License for more details.
83 */
84
85// OS2 includes
86
87#define OS2EMX_PLAIN_CHAR
88 // this is needed for "os2emx.h"; if this is defined,
89 // emx will define PSZ as _signed_ char, otherwise
90 // as unsigned char
91
92#define INCL_DOSEXCEPTIONS
93#define INCL_DOSPROCESS
94#define INCL_DOSSEMAPHORES
95#define INCL_DOSMISC
96#define INCL_DOSERRORS
97
98#define INCL_WINWINDOWMGR
99#define INCL_WINMESSAGEMGR
100#define INCL_WINTIMER
101#include <os2.h>
102
103#include <stdio.h>
104#include <setjmp.h>
105
106#include "setup.h" // code generation and debugging options
107
108#include "helpers\datetime.h"
109#include "helpers\except.h"
110#include "helpers\linklist.h"
111#include "helpers\math.h"
112#include "helpers\standards.h"
113#include "helpers\threads.h"
114#include "helpers\timer.h"
115
116/*
117 *@@category: Helpers\PM helpers\Timer replacements
118 * see timer.c.
119 */
120
121/* ******************************************************************
122 *
123 * Private declarations
124 *
125 ********************************************************************/
126
127/*
128 *@@ XTIMER:
129 * one of these represents an XTimer.
130 * These are stored in a linked list in
131 * an _XTIMERSET.
132 */
133
134typedef struct _XTIMER
135{
136 USHORT usTimerID; // timer ID, as passed to tmrStartXTimer
137 HWND hwndTarget; // target window, as passed to tmrStartXTimer
138 ULONG ulTimeout; // timer's timeout (in ms)
139 ULONG ulNextFire; // next time scalar (from dtGetULongTime) to fire at
140} XTIMER, *PXTIMER;
141
142/* ******************************************************************
143 *
144 * Global variables
145 *
146 ********************************************************************/
147
148HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
149
150/* ******************************************************************
151 *
152 * Private functions
153 *
154 ********************************************************************/
155
156/*
157 *@@ LockTimers:
158 *
159 *@@added V0.9.12 (2001-05-12) [umoeller]
160 */
161
162BOOL LockTimers(VOID)
163{
164 if (!G_hmtxTimers)
165 return (!DosCreateMutexSem(NULL,
166 &G_hmtxTimers,
167 0,
168 TRUE)); // request!
169 else
170 return (!WinRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT));
171}
172
173/*
174 *@@ UnlockTimers:
175 *
176 *@@added V0.9.12 (2001-05-12) [umoeller]
177 */
178
179VOID UnlockTimers(VOID)
180{
181 DosReleaseMutexSem(G_hmtxTimers);
182}
183
184/*
185 *@@ FindTimer:
186 * returns the XTIMER structure from the global
187 * linked list which matches the given window
188 * _and_ timer ID.
189 *
190 * Internal function. Caller must hold the mutex.
191 */
192
193PXTIMER FindTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
194 HWND hwnd, // in: timer target window
195 USHORT usTimerID) // in: timer ID
196{
197 if (pSet && pSet->pvllXTimers)
198 {
199 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
200 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
201 while (pNode)
202 {
203 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
204 if ( (pTimer->usTimerID == usTimerID)
205 && (pTimer->hwndTarget == hwnd)
206 )
207 {
208 return (pTimer);
209 }
210
211 pNode = pNode->pNext;
212 }
213 }
214
215 return (NULL);
216}
217
218/*
219 *@@ RemoveTimer:
220 * removes the specified XTIMER structure from
221 * the global linked list of running timers.
222 *
223 * Internal function. Caller must hold the mutex.
224 */
225
226VOID RemoveTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
227 PXTIMER pTimer) // in: timer to remove.
228{
229 if (pSet && pSet->pvllXTimers)
230 {
231 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
232 lstRemoveItem(pllXTimers,
233 pTimer); // auto-free!
234 }
235}
236
237/*
238 *@@ AdjustPMTimer:
239 * goes thru all XTimers in the sets and starts
240 * or stops the PM timer with a decent frequency.
241 *
242 * Internal function. Caller must hold the mutex.
243 *
244 *@@added V0.9.9 (2001-03-07) [umoeller]
245 *@@changed V0.9.14 (2001-07-07) [umoeller]: added GCD optimizations
246 */
247
248VOID AdjustPMTimer(PXTIMERSET pSet)
249{
250 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
251 ULONG cTimers = lstCountItems(pllXTimers);
252
253 #ifdef DEBUG_XTIMERS
254 _Pmpf((__FUNCTION__ ": entering"));
255 #endif
256
257 if (!cTimers)
258 {
259 // no XTimers running:
260 if (pSet->idPMTimerRunning)
261 {
262 // but PM timer running:
263 // stop it
264 WinStopTimer(pSet->hab,
265 pSet->hwndOwner,
266 pSet->idPMTimer);
267 pSet->idPMTimerRunning = 0;
268 }
269
270 pSet->ulPMTimeout = 0;
271 }
272 else
273 {
274 // we have timers:
275
276 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
277
278 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
279 PXTIMER pTimer1 = (PXTIMER)pNode->pItemData;
280
281 if (cTimers == 1)
282 {
283 // only one timer:
284 // that's easy
285 #ifdef DEBUG_XTIMERS
286 _Pmpf((" got 1 timer"));
287 #endif
288
289 pSet->ulPMTimeout = pTimer1->ulTimeout;
290 }
291 else if (cTimers == 2)
292 {
293 // exactly two timers:
294 // find the greatest common denominator
295 PXTIMER pTimer2 = (PXTIMER)pNode->pNext->pItemData;
296 #ifdef DEBUG_XTIMERS
297 _Pmpf((" got 2 timers"));
298 #endif
299
300 pSet->ulPMTimeout = mathGCD(pTimer1->ulTimeout,
301 pTimer2->ulTimeout);
302 }
303 else
304 {
305 // more than two timers:
306 // run through all timers and find the greatest
307 // common denominator of all frequencies...
308 int *paInts = (int*)_alloca(sizeof(int) * cTimers),
309 i = 0;
310
311 #ifdef DEBUG_XTIMERS
312 _Pmpf((" got %d timers", cTimers));
313 #endif
314
315 // fill an array of integers with the
316 // timer frequencies
317 while (pNode)
318 {
319 pTimer1 = (PXTIMER)pNode->pItemData;
320
321 #ifdef DEBUG_XTIMERS
322 _Pmpf((" timeout %d is %d", i, pTimer1->ulTimeout));
323 #endif
324
325 paInts[i++] = pTimer1->ulTimeout;
326
327 pNode = pNode->pNext;
328 }
329
330 pSet->ulPMTimeout = mathGCDMulti(paInts,
331 cTimers);
332 }
333
334 #ifdef DEBUG_XTIMERS
335 _Pmpf(("--> GCD is %d", pSet->ulPMTimeout));
336 #endif
337
338 if ( (!pSet->idPMTimerRunning) // timer not running?
339 || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
340 )
341 {
342 // start or restart PM timer
343 pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
344 pSet->hwndOwner,
345 pSet->idPMTimer,
346 pSet->ulPMTimeout);
347 }
348 }
349}
350
351/* ******************************************************************
352 *
353 * Exported functions
354 *
355 ********************************************************************/
356
357/*
358 *@@ tmrCreateSet:
359 * creates a "timer set" for use with the XTimer functions.
360 * This is the first step if you want to use the XTimers.
361 *
362 * hwndOwner must specify the "owner window", the target
363 * window for the master PM timer. This window must respond
364 * to a WM_TIMER message with the specified usPMTimerID and
365 * invoke tmrTimerTick then.
366 *
367 * Note that the master timer is not started until an XTimer
368 * is started.
369 *
370 * Use tmrDestroySet to free all resources again.
371 *
372 *@@added V0.9.9 (2001-02-28) [umoeller]
373 */
374
375PXTIMERSET tmrCreateSet(HWND hwndOwner, // in: owner window
376 USHORT usPMTimerID)
377{
378 PXTIMERSET pSet = NEW(XTIMERSET);
379 if (pSet)
380 {
381 pSet->hab = WinQueryAnchorBlock(hwndOwner);
382 pSet->hwndOwner = hwndOwner;
383 pSet->idPMTimer = usPMTimerID;
384 pSet->idPMTimerRunning = 0;
385 pSet->ulPMTimeout = 0;
386
387 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
388 }
389
390 return (pSet);
391}
392
393/*
394 *@@ tmrDestroySet:
395 * destroys a timer set previously created using
396 * tmrCreateSet.
397 *
398 * Of course, this will stop all XTimers which
399 * might still be running with this set.
400 *
401 *@@added V0.9.9 (2001-02-28) [umoeller]
402 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
403 */
404
405VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
406{
407 if (LockTimers())
408 {
409 if (pSet)
410 {
411 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
412 if (pllXTimers)
413 {
414 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
415 while (pTimerNode)
416 {
417 PLISTNODE pNext = pTimerNode->pNext;
418 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
419 RemoveTimer(pSet, pTimer);
420 pTimerNode = pNext;
421 }
422
423 lstFree(&pllXTimers);
424 pSet->pvllXTimers = NULL;
425 }
426 }
427
428 if (pSet->idPMTimerRunning)
429 {
430 WinStopTimer(pSet->hab,
431 pSet->hwndOwner,
432 pSet->idPMTimer);
433 pSet->idPMTimerRunning = 0;
434 }
435
436 free(pSet);
437
438 UnlockTimers();
439 }
440}
441
442/*
443 *@@ tmrTimerTick:
444 * implements a PM timer tick.
445 *
446 * When your window procs receives WM_TIMER for the
447 * one PM timer which is supposed to trigger all the
448 * XTimers, it must call this function. This will
449 * evaluate all XTimers on the list and "fire" them
450 * by calling the window procs directly with the
451 * WM_TIMER message.
452 *
453 *@@added V0.9.9 (2001-02-28) [umoeller]
454 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
455 *@@changed V0.9.12 (2001-05-24) [umoeller]: fixed crash if this got called during tmrTimerTick
456 *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed mem overwrite which might have caused crashes if this got called during tmrTimerTick
457 *@@changed V0.9.14 (2001-08-03) [umoeller]: fixed "half frequency" regression caused by frequency optimizations
458 */
459
460VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
461{
462 if (LockTimers())
463 {
464 if (pSet)
465 {
466 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
467 if (pllXTimers)
468 {
469 // go thru all XTimers and see which one
470 // has elapsed; for all of these, post WM_TIMER
471 // to the target window proc
472 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
473
474 if (!pTimerNode)
475 {
476 // no timers left:
477 if (pSet->idPMTimerRunning)
478 {
479 // but PM timer running:
480 // stop it
481 WinStopTimer(pSet->hab,
482 pSet->hwndOwner,
483 pSet->idPMTimer);
484 pSet->idPMTimerRunning = 0;
485 }
486
487 pSet->ulPMTimeout = 0;
488 }
489 else
490 {
491 // we have timers:
492 BOOL fFoundInvalid = FALSE;
493
494 // get current time
495 ULONG ulTimeNow = 0;
496 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
497 &ulTimeNow, sizeof(ulTimeNow));
498
499 #ifdef DEBUG_XTIMERS
500 _Pmpf((__FUNCTION__ ": ulTimeNow = %d", ulTimeNow));
501 #endif
502
503 while (pTimerNode)
504 {
505 // get next node first because the
506 // list can get modified while processing
507 // V0.9.12 (2001-05-24) [umoeller]
508 PLISTNODE pNext = pTimerNode->pNext;
509
510 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
511
512 #ifdef DEBUG_XTIMERS
513 _Pmpf((" timer %d: ulNextFire = %d",
514 lstIndexFromItem(pllXTimers, pTimer),
515 pTimer->ulNextFire));
516 #endif
517
518 if ( (pTimer)
519 // && (pTimer->ulNextFire < ulTimeNow)
520 // V0.9.14 (2001-08-01) [umoeller]
521 // use <= because otherwise we'll get
522 // only half the frequency...
523 // we get here frequently where the
524 // two values are EXACTLY equal due
525 // to the above optimization (DosQuerySysInfo
526 // called once only for the entire loop)
527 && (pTimer->ulNextFire <= ulTimeNow)
528 )
529 {
530 // this timer has elapsed:
531 // fire!
532
533 #ifdef DEBUG_XTIMERS
534 _Pmpf((" --> fire!"));
535 #endif
536
537 if (WinIsWindow(pSet->hab,
538 pTimer->hwndTarget))
539 {
540 // window still valid:
541 // get the window's window proc
542 QMSG qmsg;
543 /* PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
544 QWP_PFNWP); */
545
546 // moved this up V0.9.14 (2001-08-01) [umoeller]
547 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
548
549 // call the window proc DIRECTLY
550 qmsg.hwnd = pTimer->hwndTarget;
551 qmsg.msg = WM_TIMER;
552 qmsg.mp1 = (MPARAM)pTimer->usTimerID;
553 qmsg.mp2 = (MPARAM)0;
554 qmsg.time = 0;
555 qmsg.ptl.x = 0;
556 qmsg.ptl.y = 0;
557 qmsg.reserved = 0;
558 WinDispatchMsg(pSet->hab,
559 &qmsg);
560 /* pfnwp(pTimer->hwndTarget,
561 WM_TIMER,
562 (MPARAM)pTimer->usTimerID,
563 0); */
564 // V0.9.12 (2001-05-24) [umoeller]
565 // if the winproc chooses to start or
566 // stop a timer, pNext still points
567 // to a valid node...
568 // -- if a timer is removed, that's OK
569 // -- if a timer is added, it is added to
570 // the list, so we'll see it in this loop
571
572 // V0.9.14 (2001-08-01) [umoeller]
573
574 // DO NOT REFERENCE pTimer AFTER THIS CODE;
575 // tmrTimerTick might have removed the timer,
576 // and since the list is auto-free, pTimer
577 // might have been freed!!
578 }
579 else
580 {
581 // window has been destroyed:
582 lstRemoveNode(pllXTimers,
583 pTimerNode);
584 // pNext is still valid
585
586 fFoundInvalid = TRUE;
587 }
588
589 } // end if (pTimer->ulNextFire < ulTimeNow)
590
591 // next timer
592 pTimerNode = pNext; // V0.9.12 (2001-05-24) [umoeller]
593 } // end while (pTimerNode)
594
595 // destroy invalid timers, if any
596 if (fFoundInvalid)
597 AdjustPMTimer(pSet);
598
599 } // end else if (!pTimerNode)
600 } // end if (pllXTimers)
601 } // end if (pSet)
602
603 UnlockTimers();
604 } // end if (LockTimers())
605}
606
607/*
608 *@@ tmrStartXTimer:
609 * starts an XTimer.
610 *
611 * Any window can request an XTimer using
612 * this function. This operates similar to
613 * WinStartTimer, except that the number of
614 * XTimers is not limited.
615 *
616 * Returns a new timer or resets an existing
617 * timer (if usTimerID is already used with
618 * hwnd). Use tmrStopXTimer to stop the timer.
619 *
620 * The timer is _not_ stopped automatically
621 * when the window is destroyed.
622 *
623 * Note: Unless you own the timer set that
624 * your timer runs on, it is strongly recommended
625 * that your timer frequency is set to a multiple
626 * of 125. The PM master timer behind the timer
627 * set will be set to the greatest common divisor
628 * of all frequencies, and if you set one timer
629 * to 2000 and the other one to 2001, you will
630 * cause quite a lot of overhead. This applies
631 * especially to timers started by XCenter widgets.
632 *
633 * For security, all timer frequencies will be
634 * rounded to multiples of 25 anyway. Still,
635 * running two timers at 1000 and 1025 will cause
636 * the master timer to be set to 25, which is
637 * overkill.
638 *
639 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
640 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
641 *@@changed V0.9.14 (2001-07-12) [umoeller]: now rounding freq's to multiples of 25
642 */
643
644USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
645 HWND hwnd, // in: target window for XTimer
646 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
647 ULONG ulTimeout) // in: XTimer's timeout
648{
649 USHORT usrc = 0;
650
651 // _Pmpf((__FUNCTION__ ": entering"));
652
653 if (LockTimers())
654 {
655 if (pSet)
656 {
657 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
658
659 if ((pllXTimers) && (hwnd) && (ulTimeout))
660 {
661 PXTIMER pTimer;
662
663 // fix the timeout... we allow only multiples of
664 // 25, and it must be at least 25 (otherwise our
665 // internal master timer calculations will fail)
666 // V0.9.14 (2001-07-07) [umoeller]
667 if (ulTimeout < 25)
668 ulTimeout = 25;
669 else
670 ulTimeout = (ulTimeout + 10) / 25 * 25;
671
672 // check if this timer exists already
673 if (pTimer = FindTimer(pSet,
674 hwnd,
675 usTimerID))
676 {
677 // exists already: reset only
678 ULONG ulTimeNow;
679 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
680 &ulTimeNow, sizeof(ulTimeNow));
681 pTimer->ulNextFire = ulTimeNow + ulTimeout;
682 usrc = usTimerID;
683 }
684 else
685 {
686 // new timer needed:
687 if (pTimer = NEW(XTIMER))
688 {
689 ULONG ulTimeNow;
690 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
691 &ulTimeNow, sizeof(ulTimeNow));
692 pTimer->usTimerID = usTimerID;
693 pTimer->hwndTarget = hwnd;
694 pTimer->ulTimeout = ulTimeout;
695 pTimer->ulNextFire = ulTimeNow + ulTimeout;
696
697 lstAppendItem(pllXTimers,
698 pTimer);
699 usrc = usTimerID;
700 }
701 }
702
703 if (usrc)
704 // timer created or reset:
705 AdjustPMTimer(pSet);
706
707 } // if ((hwnd) && (ulTimeout))
708 }
709
710 UnlockTimers();
711 }
712
713 return (usrc);
714}
715
716/*
717 *@@ tmrStopXTimer:
718 * similar to WinStopTimer, this stops the
719 * specified timer (which must have been
720 * started with the same hwnd and usTimerID
721 * using tmrStartXTimer).
722 *
723 * Returns TRUE if the timer was stopped.
724 *
725 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
726 */
727
728BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
729 HWND hwnd,
730 USHORT usTimerID)
731{
732 BOOL brc = FALSE;
733 if (LockTimers())
734 {
735 if (pSet && pSet->pvllXTimers)
736 {
737 // PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
738
739 PXTIMER pTimer = FindTimer(pSet,
740 hwnd,
741 usTimerID);
742 if (pTimer)
743 {
744 RemoveTimer(pSet, pTimer);
745 // recalculate
746 AdjustPMTimer(pSet);
747 brc = TRUE;
748 }
749 }
750
751 UnlockTimers();
752 }
753
754 return (brc);
755}
756
757
Note: See TracBrowser for help on using the repository browser.