-- file: IntIdleLoop.mesa
-- edited by Brotz, December 1, 1980 11:57 AM
-- edited by Levin, 13-Nov-80 16:28:30

DIRECTORY
DMSTimeDefs,
dsD: FROM "DisplayDefs",
Editor,
inD: FROM "InteractorDefs",
Inline,
intCommon: FROM "IntCommon",
ovD: FROM "OverviewDefs",
ProcessDefs,
tsD: FROM "TOCSelectionDefs",
vmD: FROM "VirtualMgrDefs";

IntIdleLoop: MONITOR
IMPORTS DMSTimeDefs, dsD, Editor, inD, intC: intCommon, Inline, ProcessDefs,
tsD, vmD
EXPORTS inD = PUBLIC

BEGIN
OPEN inD;

-- Handles the IdleLoop and maintains the blinking caret.

-- Global variables --

caretX: ScreenXCoord;
caretY: ScreenYCoord;
caretState: CaretState ← notVisible;
caretLastUpdated: CARDINAL;

nap: CONDITION;

IdleLoop: PROCEDURE =
-- Called by cursor tracking routines and any other routine that has time on its hands.
-- Checks clock and calls procedures that must be activated at regular intervals, such as
-- the time update for the top window and periodic mail checks.
BEGIN
DoTimeUpdate[];
-- Pause[];
ProcessDefs.Yield[];
UpdateDMandCMThumbLines[];
MaintainBlinkingCaret[];
END; -- of IdleLoop --


Pause: ENTRY PROCEDURE = INLINE
-- causes the main Laurel process to give up control so that Pup and friends are happy.
BEGIN
WAIT nap;
END; -- of IdleLoop --


DoTimeUpdate: PROCEDURE = INLINE
-- Updates the date-time-of-day string, and posts it in the time of day house.
BEGIN
pt: DMSTimeDefs.PackedTime = GetPackedTime[];
elapsedSeconds: CARDINAL;

IF intC.timeMayBeBogus THEN {intC.timeMayBeBogus ← FALSE; SetTime[]; RETURN};

IF pt.lowbits = intC.secondsLastChecked THEN RETURN;

intC.secondsLastChecked ← pt.lowbits;
elapsedSeconds ← intC.secondsLastChecked - intC.lastMinuteSeconds;
IF elapsedSeconds < 60 THEN RETURN;

intC.lastMinuteSeconds ← intC.lastMinuteSeconds + 60 * (elapsedSeconds / 60);
DMSTimeDefs.MapPackedTimeToTimeZoneString[pt, intC.timeHouse.text];
TextHouseRefresher[intC.timeHouse];
END; -- of DoTimeUpdate --


-- GetPackedTime is copied from TimeConvert to minimize swapping

GetPackedTime: PRIVATE PROCEDURE RETURNS [DMSTimeDefs.PackedTime] =
BEGIN
ht: DMSTimeDefs.HardwareTime ← DMSTimeDefs.currentTime↑;
RETURN[[num[highbits: ht.highbits, lowbits: ht.lowbits]]];
END; -- of GetPackedTime --

-- Thumb line updating procedures


UpdateThumbLineRecord: TYPE = RECORD
[region: {toc, displayed, composed},
tlnp: ThumbLineNbrPtr,
exists: BOOLEAN,
length,
start,
end,
selection: CARDINAL];


UpdateTOCThumbLine: PROCEDURE =
-- Checks the TOC thumb line for a change in state and updates accordingly.
BEGIN
tnp: TOCTextNbrPtr ← intC.tocTextNbr;
updateThumbLineRecord: UpdateThumbLineRecord;
updateThumbLineRecord ← UpdateThumbLineRecord
[region: toc,
tlnp: intC.tocThumbLineNbr,
exists: intC.haveMailFile,
length: IF intC.haveMailFile THEN vmD.GetFirstFreeTOCIndex[] - 1 ELSE 0,
start: tnp.lines.linePair.index - 1,
end: tnp.firstLineOffScreen.linePair.index - 1,
selection: IF tsD.TOCSelectionEmpty[] THEN 0 ELSE tsD.FirstSelectedEntry[] - 1];
UpdateThumbLine[@updateThumbLineRecord];
END; -- of UpdateTOCThumbLine --


UpdateDMandCMThumbLines: PROCEDURE =
-- Checks the DM and CM thumb lines for a change in state and updates accordingly.
BEGIN
updateThumbLineRecord: UpdateThumbLineRecord ←
[region: displayed,
tlnp: intC.dmThumbLineNbr,
exists: ,
length: ,
start: ,
end: ,
selection: intC.target.start];

SetFromMNP: PROCEDURE[mnp: MessageTextNbrPtr] =
BEGIN
updateThumbLineRecord.start ← mnp.lines.firstCharIndex;
updateThumbLineRecord.end ← mnp.firstLineOffScreen.firstCharIndex;
updateThumbLineRecord.length ←
IF (updateThumbLineRecord.exists ← mnp.haveMessage)
THEN vmD.GetMessageSize[mnp.message] ELSE 0;
END; -- of SetFrom MNP --

SetFromMNP[intC.dmTextNbr];
UpdateThumbLine[@updateThumbLineRecord];
updateThumbLineRecord.region ← composed;
updateThumbLineRecord.tlnp ← intC.cmThumbLineNbr;
SetFromMNP[intC.cmTextNbr];
UpdateThumbLine[@updateThumbLineRecord];
END; -- of UpdateDMandCMThumbLines --


UpdateThumbLine: PROCEDURE [utlr: POINTER TO UpdateThumbLineRecord] =
-- Checks a thumb line for a change in state and updates accordingly.
BEGIN OPEN utlr;
quotient, remainder: CARDINAL;
startX, endX, selectionX: ScreenXCoord;
tlnp: ThumbLineNbrPtr ← utlr.tlnp;
xRange: CARDINAL ← tlnp.rightX - tlnp.leftX;

IF exists THEN BEGIN
IF ~tlnp.exists OR tlnp.length # length OR tlnp.start # start OR
tlnp.end # end OR (region # displayed AND tlnp.selection # selection) THEN
BEGIN -- state changed and thumbable information exists
tlnp.exists ← exists;
tlnp.length ← length;
tlnp.start ← start;
tlnp.end ← end;
tlnp.selection ← selection;
IF length = 0 THEN
BEGIN
dsD.ClearRectangle[tlnp.leftX, tlnp.rightX, tlnp.topY + 1, tlnp.topY + 11];
dsD.BlackenRectangle[tlnp.leftX, tlnp.rightX, tlnp.topY + 5, tlnp.topY + 7];
tlnp.startX ← tlnp.leftX;
tlnp.endX ← tlnp.leftX;
tlnp.selectionX ← tlnp.leftX;
END
ELSE BEGIN
[quotient, remainder]
← Inline.LongDivMod[Inline.LongMult[start, xRange], length];
startX ← quotient + tlnp.leftX + (IF remainder > 0 THEN 1 ELSE 0);
[quotient, remainder]
← Inline.LongDivMod[Inline.LongMult[end, xRange], length];
endX ← quotient + tlnp.leftX + (IF remainder > 0 THEN 1 ELSE 0);
IF region # displayed THEN
BEGIN
[quotient, remainder]
← Inline.LongDivMod[Inline.LongMult[selection, xRange], length];
selectionX ← quotient + tlnp.leftX + (IF remainder > 0 THEN 1 ELSE 0);
END;
IF startX + 8 > endX THEN BEGIN
IF startX + 8 > tlnp.rightX THEN startX ← endX - 8
ELSE endX ← startX + 8;
END;
IF tlnp.startX # startX OR tlnp.endX # endX
OR (region # displayed AND tlnp.selectionX # selectionX) THEN
BEGIN OPEN dsD;
IF region # displayed THEN
PaintPicture[tlnp.selectionX, tlnp.topY+1, selectionThumbMark, erase];
ClearRectangle[tlnp.leftX, startX, tlnp.topY + 4, tlnp.topY + 8];
BlackenRectangle[tlnp.leftX, startX, tlnp.topY + 5, tlnp.topY + 7];
ReplaceRectangle[startX, endX, tlnp.topY + 4, tlnp.topY + 8, dottedLine];
ClearRectangle[endX, tlnp.rightX, tlnp.topY + 4, tlnp.topY + 8];
BlackenRectangle[endX, tlnp.rightX, tlnp.topY + 5, tlnp.topY + 7];
IF region # displayed THEN
PaintPicture[selectionX, tlnp.topY + 1, selectionThumbMark, replace];
tlnp.startX ← startX;
tlnp.endX ← endX;
tlnp.selectionX ← selectionX;
END;
END;
END;
END
ELSE IF tlnp.exists THEN
BEGIN
tlnp.exists ← FALSE;
tlnp.startX ← tlnp.endX ← 0;
dsD.ClearRectangle[tlnp.leftX, tlnp.rightX, tlnp.topY + 1, tlnp.topY + 11];
dsD.BlackenRectangle[tlnp.leftX, tlnp.rightX, tlnp.topY + 5, tlnp.topY + 7];
END;
END; -- of UpdateThumbLine --


StartBlinkingCaret: PROCEDURE [x: ScreenXCoord, y: ScreenYCoord] =
-- Notes time of call. Then places upper left corner of box containing caret at x,y.
BEGIN
StopBlinkingCaret[];
caretX ← x;
caretY ← y;
dsD.PaintPicture[caretX, caretY, caret, dsD.invert];
caretState ← black;
caretLastUpdated ← realTimeClock↑;
END; -- of StartBlinkingCaret --


MaintainBlinkingCaret: PROCEDURE =
-- Inverts caret at remembered x,y at appropriate time.
BEGIN
IF caretState = notVisible THEN RETURN;
-- check if .3 seconds have elapsed since last caret inversion --
IF LOOPHOLE[realTimeClock↑ - caretLastUpdated, CARDINAL] >= 9 THEN
BEGIN -- one third second has elapsed since last update --
dsD.PaintPicture[caretX, caretY, caret, dsD.invert];
caretState ← IF caretState = black THEN white ELSE black;
caretLastUpdated ← realTimeClock↑;
END;
END; -- of MaintainBlinkingCaret --


StopBlinkingCaret: PROCEDURE =
-- Restores caret picture to original state.
BEGIN
IF caretState = black THEN dsD.PaintPicture[caretX, caretY, caret, dsD.invert];
caretState ← notVisible;
END; -- of StopBlinkingCaret --


SetCaretBlinking: PROCEDURE
[charIndex: ovD.CharIndex, mnp: MessageTextNbrPtr] =
BEGIN OPEN Editor;
line: LinePtr;
line ← MapCharIndexToLine[charIndex, mnp];
IF line # NIL THEN
BEGIN
caretX: ScreenXCoord
← MapCharIndexInLineToLeftX[charIndex, line, mnp.message] - 3;
caretY: ScreenYCoord ← line.y + 9;
StartBlinkingCaret[caretX, caretY];
END;
END; -- of SetCaretBlinking --

-- Main body --

ProcessDefs.InitializeCondition[@nap, 1];


END. -- of IntIdleLoop --