-- file: IntTrackEdit.Mesa -- edited by Brotz, October 11, 1982 5:42 PM -- edited by McCreight, September 16, 1982 9:03 AM DIRECTORY Ascii USING [CR], dsD: FROM "DisplayDefs" USING [ChangeCursor, CharProperty, GetCharBreakProp, GetCharRightX, GetCursor, ScreenXCoord, ScreenYCoord], Editor USING [ControlKey, ControlTap, DeUnderline, DeUnderlineSelection, MapCharIndexInLineToLeftX, MapCharIndexToLine, MapCursorToCharIndex, MonitorTaps, ResetInsertionBuffer, ResetTaps, ShiftKey, Underline, UnderlineSelection, UnderlineType], exD: FROM "ExceptionDefs" USING [SysBug], inD: FROM "InteractorDefs" USING [AcceptKeyboardInput, ButtonType, CaretIsBlinking, CharIndex, cursorX, cursorY, IdleLoop, leftMargin, lineBarLeftX, LinePtr, MessageTextNbrPtr, MouseButton, realTimeClock, ScreenXCoord, ScreenYCoord, ScrollDownMessageNbr, ScrollUpMessageNbr, SelectionMode, SetCaretBlinking, StopBlinkingCaret, TextSelection, TextSelectionPtr, TrackerType], intCommon USING [bluePendingDelete, cmTextNbr, commandMode, continuousScrollDelay, continuousScrollTimeOut, controlRedEnabled, currentSelection, editorType, multiClickTimeOut, newTargetSelection, pendingDeleteSetByControl, secondarySelectionEnabled, source, target], ovD: FROM "OverviewDefs" USING [LineBreakValue], prD: FROM "ProtectionDefs" USING [FindUnprotectedSubrange], vmD: FROM "VirtualMgrDefs" USING [CharIndex, DisplayMessagePtr, GetMessageChar, GetMessageSize, MessageRange, UnlockTOC, VirtualMessagePtr, WaitForLock]; IntTrackEdit: PROGRAM IMPORTS dsD, Editor, exD, inD, intC: intCommon, prD, vmD EXPORTS inD = BEGIN OPEN inD, Editor; -- Editor Department of the Interactor Division --This module does tracking specifically for the Editor -- Selection convention: a range is represented by a nonempty half open -- interval; a point is represented by an empty half open interval; selection -- beyond the message end is represented by the point -- [messageLength .. messageLength). clickDown, clickUp: CARDINAL _ realTimeClock^ - intC.multiClickTimeOut; button: ButtonType; multiClick: BOOLEAN; modelessEditor: BOOLEAN = (intC.editorType = modeless); TextNbrTracker: PUBLIC PROC [mnp: MessageTextNbrPtr, trackerType: TrackerType] = -- Tracks cursor within a message text area. Monitors mouse buttons for -- selection. Calls on TextScrollBarTracker and Selector to track the cursor -- within their respective domains. BEGIN x: ScreenXCoord; y: ScreenYCoord; xOffset, yOffset: INTEGER; cursorState: {scroll, line, char} _ char; dsD.ChangeCursor[charArrow]; DO [ , xOffset, yOffset] _ dsD.GetCursor[]; x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; SELECT TRUE FROM y ~IN [mnp.topY .. mnp.bottomY) => RETURN; ~mnp.haveMessage => NULL; (x IN [0 .. inD.lineBarLeftX)) => BEGIN TextScrollBarTracker[mnp: mnp, trackerType: trackerType]; cursorState _ scroll; END; (x IN [inD.lineBarLeftX .. inD.leftMargin) AND cursorState # line) => {dsD.ChangeCursor[lineArrow]; cursorState _ line}; (x >= inD.leftMargin AND cursorState # char) => {dsD.ChangeCursor[charArrow]; cursorState _ char}; MouseButton[any, down] => {clickDown _ realTimeClock^; Selector[mnp, trackerType]}; ENDCASE; IdleLoop[]; AcceptKeyboardInput[]; ENDLOOP; END; -- of TextNbrTracker -- TextScrollBarTracker: PROCEDURE [mnp: MessageTextNbrPtr, trackerType: TrackerType] = -- Watch cursor and mouse buttons while in the ScrollBar subneighborhood. BEGIN state, newState: {neutral, up, down, continuousUp, continuousDown} _ neutral; x: ScreenXCoord; y: ScreenYCoord; xOffset, yOffset: INTEGER; startTime: CARDINAL; isCM: BOOLEAN = mnp.editable; dm: vmD.DisplayMessagePtr = IF isCM THEN NIL ELSE LOOPHOLE[mnp.message]; key: CARDINAL _ 0; holdLock: BOOLEAN _ FALSE; DoScrollUp: PROCEDURE [y: ScreenYCoord] = BEGIN dealWithCaret: BOOLEAN = (isCM AND CaretIsBlinking[] AND trackerType = normal); IF dealWithCaret THEN StopBlinkingCaret[]; ScrollUpMessageNbr[y, mnp]; IF dealWithCaret THEN SetCaretBlinking[intC.target.point, mnp]; END; -- of DoScrollUp -- DoScrollDown: PROCEDURE [y: ScreenYCoord] = BEGIN dealWithCaret: BOOLEAN = (isCM AND CaretIsBlinking[] AND trackerType = normal); IF dealWithCaret THEN StopBlinkingCaret[]; ScrollDownMessageNbr[y, mnp]; IF dealWithCaret THEN SetCaretBlinking[intC.target.point, mnp]; END; -- of DoScrollDown -- dsD.ChangeCursor[scroll]; [ , xOffset, yOffset] _ dsD.GetCursor[]; DO x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; IF x >= inD.lineBarLeftX OR y ~IN [mnp.topY .. mnp.bottomY) THEN {IF holdLock THEN vmD.UnlockTOC[dm.toc, key]; RETURN}; newState _ SELECT TRUE FROM MouseButton[left, down] => IF state = continuousUp THEN state ELSE up, MouseButton[right, down] => IF state = continuousDown THEN state ELSE down, ENDCASE => neutral; IF ~isCM AND state # neutral AND ~holdLock AND intC.source.key = 0 THEN {key _ vmD.WaitForLock[dm.toc]; holdLock _ TRUE; LOOP}; SELECT state FROM up => IF newState = neutral THEN DoScrollUp[y]; down => IF newState = neutral THEN DoScrollDown[y]; continuousUp => IF inD.realTimeClock^ - startTime >= intC.continuousScrollDelay THEN {startTime _ inD.realTimeClock^; DoScrollUp[mnp.topY]}; continuousDown => IF inD.realTimeClock^ - startTime >= intC.continuousScrollDelay THEN {startTime _ inD.realTimeClock^; DoScrollDown[mnp.topY]}; ENDCASE; SELECT TRUE FROM (state = newState AND (state = up OR state = down)) => IF intC.continuousScrollTimeOut # 0 AND inD.realTimeClock^ - startTime >= intC.continuousScrollTimeOut THEN BEGIN state _ IF state = up THEN continuousUp ELSE continuousDown; startTime _ inD.realTimeClock^ - intC.continuousScrollDelay; END; (newState # state) => BEGIN IF (state _ newState) # neutral THEN startTime _ inD.realTimeClock^; dsD.ChangeCursor [SELECT state FROM up => scrollUp, down => scrollDown, ENDCASE => scroll]; END; ENDCASE; IF holdLock THEN {vmD.UnlockTOC[dm.toc, key]; holdLock _ FALSE}; IdleLoop[]; AcceptKeyboardInput[]; ENDLOOP; END; -- of TextScrollBarTracker -- Selector: PROCEDURE [mnp: MessageTextNbrPtr, trackerType: TrackerType] = -- Track cursor as long as initial down mouse button remains down. Scrolling is -- not allowed while selection is still in progress; if cursor moves out of text -- yard then selection reverts back to state at entry to Selector. UnderlineType -- describes form of underline to be displayed on screen. Selection may be -- made in character mode or word mode; treatment of these modes is hidden -- behind MapXToRange routines. BEGIN x: ScreenXCoord; y: ScreenYCoord; xOffset, yOffset: INTEGER; selPtr: TextSelectionPtr; target: TextSelectionPtr = @intC.target; source: TextSelectionPtr = @intC.source; underline: UnderlineType; secondarySelectionEnabled: POINTER TO BOOLEAN _ @intC.secondarySelectionEnabled; commandMode: POINTER TO BOOLEAN _ @intC.commandMode; modalShiftedSelection: BOOLEAN _ FALSE; shiftDown: BOOLEAN = ShiftKey[down]; pendingDelete: BOOLEAN = ControlKey[down] AND modelessEditor AND mnp.editable; [ , xOffset, yOffset] _ dsD.GetCursor[]; x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; SELECT TRUE FROM y ~IN [mnp.topY .. mnp.bottomY) => RETURN; (mnp.editable AND ~shiftDown AND commandMode^) => {selPtr _ target; underline _ target}; ((modelessEditor AND shiftDown) OR (modalShiftedSelection _ (shiftDown AND ~commandMode^)) OR secondarySelectionEnabled^) => {selPtr _ source; underline _ source}; ENDCASE => RETURN; Editor.ResetTaps[]; SELECT TRUE FROM MouseButton[left, down] => BEGIN multiClick _ (button = left AND clickDown - clickUp < intC.multiClickTimeOut); button _ left; StartSelector[selPtr, mnp, underline, pendingDelete AND intC.controlRedEnabled, trackerType, IF x IN [inD.lineBarLeftX .. inD.leftMargin) THEN line ELSE char]; END; MouseButton[middle, down] => BEGIN multiClick _ (button = middle AND clickDown - clickUp < intC.multiClickTimeOut); button _ middle; StartSelector[selPtr, mnp, underline, pendingDelete, trackerType, IF x IN [inD.lineBarLeftX .. inD.leftMargin) THEN paragraph ELSE word]; END; MouseButton[right, down] => BEGIN IF (selPtr.end = selPtr.start AND underline = source) OR selPtr.mnp # mnp THEN RETURN; multiClick _ (button = right AND clickDown - clickUp < intC.multiClickTimeOut); button _ right; ExtendSelector[selPtr, mnp, underline, pendingDelete, trackerType]; END; ENDCASE; IF modalShiftedSelection AND selPtr.start # selPtr.end THEN BEGIN IF secondarySelectionEnabled^ THEN ResetInsertionBuffer[intC.cmTextNbr]; secondarySelectionEnabled^ _ FALSE; END; END; -- of Selector -- StartSelector: PROCEDURE [selPtr: TextSelectionPtr, mnp: MessageTextNbrPtr, underline: UnderlineType, pendingDelete: BOOLEAN, tracker: TrackerType, mode: SelectionMode] = -- Track cursor as long as initial down (left or middle) mouse button remains -- down. Scrolling is not allowed while selection is still in progress; if cursor -- moves out of text yard then selection reverts back to state at entry. -- UnderlineType describes form of underline to be displayed on screen. -- Selection may be made in character mode or word mode; treatment of these -- modes is hidden behind MapXToRange routines. BEGIN x: ScreenXCoord; y: ScreenYCoord; xOffset, yOffset: INTEGER; lastRange, thisRange: vmD.MessageRange; lastCaretIndex, thisCaretIndex, charIndex: CharIndex; selectMode: SelectionMode; modelessTargetSelection: BOOLEAN _ (modelessEditor AND underline = target); normalModelessTargetSelection: BOOLEAN _ (modelessTargetSelection AND tracker = normal); dm: vmD.DisplayMessagePtr = IF mnp.editable THEN NIL ELSE LOOPHOLE[mnp.message]; key: CARDINAL _ 0; StartGuaranteeUnprotected: PROC [rangePtr: POINTER TO vmD.MessageRange] = BEGIN IF underline = target THEN prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: rangePtr, fixStart: TRUE]; END; -- StartGuaranteeUnprotected -- [ , xOffset, yOffset] _ dsD.GetCursor[]; x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; IF y ~IN [mnp.topY .. mnp.bottomY) OR ((mode = line OR mode = paragraph) AND x ~IN [inD.lineBarLeftX .. inD.leftMargin)) OR ((mode = char OR mode = word) AND x < inD.leftMargin) THEN RETURN; charIndex _ MapCursorToCharIndex[x, y, mnp]; selectMode _ IF multiClick AND charIndex IN [selPtr.start .. selPtr.end) THEN (SELECT selPtr.mode FROM char => word, word => line, line => paragraph, ENDCASE => everything) ELSE mode; IF dm # NIL AND selPtr.key = 0 THEN key _ vmD.WaitForLock[dm.toc]; lastRange _ MapCharIndexToRange[charIndex, mnp.message, selectMode]; lastCaretIndex _ FindCaretIndex[lastRange.start, lastRange.end, x, y, mnp]; IF normalModelessTargetSelection THEN StopBlinkingCaret[]; DeUnderlineSelection[selPtr, underline]; StartGuaranteeUnprotected[@lastRange]; Underline[lastRange.start, lastRange.end, underline, pendingDelete, mnp]; IF normalModelessTargetSelection THEN SetCaretBlinking[lastCaretIndex, mnp]; DO -- track start selection point x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; IF y ~IN [mnp.topY .. mnp.bottomY) OR ((mode = line OR mode = paragraph) AND x ~IN [inD.lineBarLeftX .. inD.leftMargin)) OR ((mode = char OR mode = word) AND x < inD.leftMargin) THEN BEGIN IF normalModelessTargetSelection THEN StopBlinkingCaret[]; DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp]; UnderlineSelection[selPtr, underline]; IF normalModelessTargetSelection THEN SetCaretBlinking[selPtr.point, mnp]; IF key # 0 THEN vmD.UnlockTOC[dm.toc, key]; RETURN; END; IF ControlTap[] AND mnp.editable THEN BEGIN DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp]; pendingDelete _ ~pendingDelete; Underline[lastRange.start, lastRange.end, underline, pendingDelete, mnp]; END; x _ MAX[x, inD.leftMargin]; thisRange _ MapCharIndexToRange [MapCursorToCharIndex[x, y, mnp], mnp.message, selectMode]; StartGuaranteeUnprotected[@thisRange]; IF modelessTargetSelection THEN thisCaretIndex _ FindCaretIndex[thisRange.start, thisRange.end, x, y, mnp]; IF thisRange # lastRange OR (modelessTargetSelection AND thisCaretIndex # lastCaretIndex) THEN BEGIN IF normalModelessTargetSelection THEN StopBlinkingCaret[]; DeUnderline[lastRange.start, lastRange.end, underline, pendingDelete, mnp]; Underline[thisRange.start, thisRange.end, underline, pendingDelete, mnp]; lastRange _ thisRange; IF normalModelessTargetSelection THEN SetCaretBlinking[thisCaretIndex, mnp]; lastCaretIndex _ thisCaretIndex; END; IF MouseButton[button, up] THEN BEGIN clickUp _ realTimeClock^; IF selPtr.key # 0 THEN BEGIN IF mnp = selPtr.mnp THEN key _ selPtr.key ELSE BEGIN message: vmD.DisplayMessagePtr = LOOPHOLE[selPtr.mnp.message]; vmD.UnlockTOC[message.toc, selPtr.key]; END; END; selPtr^ _ TextSelection [mnp, lastRange.start, lastRange.end, lastCaretIndex, key, selectMode, pendingDelete]; IF underline = target THEN intC.newTargetSelection _ TRUE; intC.currentSelection _ IF underline = source THEN source ELSE target; intC.pendingDeleteSetByControl _ pendingDelete; RETURN; END; MonitorTaps[]; IdleLoop[]; ENDLOOP; -- track start selection point END; -- of StartSelector -- ExtendSelector: PROCEDURE [selPtr: TextSelectionPtr, mnp: MessageTextNbrPtr, underline: UnderlineType, pendingDelete: BOOLEAN, tracker: TrackerType] = -- Track cursor as long as right mouse button remains down. Scrolling is not -- allowed while selection is still in progress; if cursor moves out of text yard -- then selection reverts back to state at entry. UnderlineType describes form -- of underline to be displayed on screen. Selection may be made in character -- mode or word mode; treatment of these modes is hidden behind -- MapXToRange routines. BEGIN firstX, x: ScreenXCoord; y: ScreenYCoord; xOffset, yOffset: INTEGER; lastRange, thisRange, unprotRange: vmD.MessageRange; fixedEnd, floatingEnd, minEnd, maxEnd, thisCaretIndex, charIndex: CharIndex; mode: SelectionMode _ selPtr.mode; selectMode: SelectionMode; modelessTargetSelection: BOOLEAN _ (modelessEditor AND underline = target); normalModelessTargetSelection: BOOLEAN _ (modelessTargetSelection AND tracker = normal); controlKeyUsed: BOOLEAN _ pendingDelete; ExtendGuaranteeUnprotected: PROCEDURE [rangePtr: POINTER TO vmD.MessageRange, fixStart: BOOLEAN _ TRUE] = BEGIN IF underline = target THEN prD.FindUnprotectedSubrange[pfp: mnp.protectedFieldPtr, rangePtr: rangePtr, fixStart: fixStart]; END; -- ExtendGuaranteeUnprotected -- [ , xOffset, yOffset] _ dsD.GetCursor[]; firstX _ cursorX^ + xOffset; y _ cursorY^ + yOffset; IF y ~IN [mnp.topY .. mnp.bottomY) THEN RETURN; selectMode _ IF firstX IN [inD.lineBarLeftX .. inD.leftMargin) THEN IF mode = paragraph THEN paragraph ELSE line ELSE mode; x _ MAX[firstX, inD.leftMargin]; charIndex _ MapCursorToCharIndex[x, y, mnp]; IF multiClick AND charIndex IN [selPtr.start .. selPtr.end) THEN IF firstX IN [inD.lineBarLeftX .. inD.leftMargin) THEN {selectMode _ IF selectMode = everything THEN paragraph ELSE line} ELSE selectMode _ SELECT selectMode FROM everything => paragraph, paragraph => line, line => word, ENDCASE => char; IF charIndex < selPtr.start THEN {floatingEnd _ selPtr.start; fixedEnd _ selPtr.end} ELSE {fixedEnd _ selPtr.start; floatingEnd _ selPtr.end}; minEnd _ selPtr.start; maxEnd _ selPtr.end; thisRange _ MapCharIndexToRange[charIndex, mnp.message, selectMode]; ExtendGuaranteeUnprotected[@thisRange]; pendingDelete _ pendingDelete OR (modelessTargetSelection AND intC.bluePendingDelete); IF pendingDelete # selPtr.pendingDelete THEN BEGIN DeUnderlineSelection[selPtr, underline]; Underline[selPtr.start, selPtr.end, underline, pendingDelete, mnp]; END; thisCaretIndex _ FindCaretIndex[thisRange.start, thisRange.end, x, y, mnp]; lastRange _ vmD.MessageRange[0, 0, mnp.message]; DO -- extend select track loop -- invariant: floatingEnd to fixedEnd interval is underlined at this point. x _ cursorX^ + xOffset; y _ cursorY^ + yOffset; IF y ~IN [mnp.topY .. mnp.bottomY) OR x < inD.lineBarLeftX OR ((firstX IN [inD.lineBarLeftX .. inD.leftMargin)) # (x IN [inD.lineBarLeftX .. inD.leftMargin))) THEN BEGIN IF normalModelessTargetSelection THEN StopBlinkingCaret[]; DeUnderline[minEnd, maxEnd, underline, pendingDelete, mnp]; UnderlineSelection[selPtr, underline]; IF normalModelessTargetSelection THEN SetCaretBlinking[intC.target.point, mnp]; RETURN; END; IF ControlTap[] AND mnp.editable THEN BEGIN DeUnderline[minEnd, maxEnd, underline, pendingDelete, mnp]; pendingDelete _ ~pendingDelete; Underline[minEnd, maxEnd, underline, pendingDelete, mnp]; controlKeyUsed _ TRUE; END; x _ MAX[x, inD.leftMargin]; thisRange _ MapCharIndexToRange [MapCursorToCharIndex[x, y, mnp], mnp.message, selectMode]; IF thisRange # lastRange THEN BEGIN IF normalModelessTargetSelection THEN StopBlinkingCaret[]; lastRange _ thisRange; SELECT TRUE FROM (fixedEnd <= thisRange.start AND thisRange.end <= floatingEnd) => BEGIN -- Inside the old interval. Shrink from high end. DeUnderline[thisRange.end, floatingEnd, underline, pendingDelete, mnp]; floatingEnd _ thisRange.end; END; (floatingEnd <= thisRange.start AND thisRange.start < fixedEnd) => BEGIN -- Inside old interval. Shrink from low end. DeUnderline[floatingEnd, thisRange.start, underline, pendingDelete, mnp]; floatingEnd _ thisRange.start; END; (fixedEnd <= floatingEnd AND floatingEnd < thisRange.end) => BEGIN -- Extend on high end. unprotRange _ vmD.MessageRange[start: floatingEnd, end: thisRange.end, message: mnp.message]; ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: TRUE]; Underline[floatingEnd, unprotRange.end, underline, pendingDelete, mnp]; floatingEnd _ unprotRange.end; END; (thisRange.start >= fixedEnd AND fixedEnd > floatingEnd) => BEGIN -- Crossover to high side of considered range and extend on high end. DeUnderline[floatingEnd, fixedEnd, underline, pendingDelete, mnp]; fixedEnd _ selPtr.start; unprotRange _ vmD.MessageRange[start: selPtr.end, end: thisRange.end, message: mnp.message]; ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: TRUE]; floatingEnd _ unprotRange.end; Underline[fixedEnd, floatingEnd, underline, pendingDelete, mnp]; END; (thisRange.start <= floatingEnd AND floatingEnd <= fixedEnd) => BEGIN -- Extend on low end. unprotRange _ vmD.MessageRange[start: thisRange.start, end: floatingEnd, message: mnp.message]; ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: FALSE]; Underline[unprotRange.start, floatingEnd, underline, pendingDelete, mnp]; floatingEnd _ unprotRange.start; END; (thisRange.start <= fixedEnd AND fixedEnd < floatingEnd) => BEGIN -- Crossover to low side of considered range and extend on low end. DeUnderline[fixedEnd, floatingEnd, underline, pendingDelete, mnp]; fixedEnd _ selPtr.end; unprotRange _ vmD.MessageRange[start: thisRange.start, end: selPtr.start, message: mnp.message]; ExtendGuaranteeUnprotected[rangePtr: @unprotRange, fixStart: FALSE]; floatingEnd _ unprotRange.start; Underline[floatingEnd, fixedEnd, underline, pendingDelete, mnp]; END; ENDCASE => exD.SysBug[]; minEnd _ MIN[floatingEnd, fixedEnd]; maxEnd _ MAX[floatingEnd, fixedEnd]; thisCaretIndex _ FindCaretIndex[minEnd, maxEnd, x, y, mnp]; IF normalModelessTargetSelection THEN SetCaretBlinking[thisCaretIndex, mnp]; END; IF MouseButton[right, up] THEN BEGIN key: CARDINAL _ selPtr.key; clickUp _ realTimeClock^; selPtr^ _ TextSelection [mnp, minEnd, maxEnd, thisCaretIndex, key, selectMode, pendingDelete]; IF underline = target THEN intC.newTargetSelection _ TRUE; intC.pendingDeleteSetByControl _ pendingDelete AND controlKeyUsed; RETURN; END; MonitorTaps[]; IdleLoop[]; ENDLOOP; -- extend select track loop END; -- of ExtendSelector -- MapCharIndexToRange: PROCEDURE [index: CharIndex, message: vmD.VirtualMessagePtr, mode: SelectionMode] RETURNS [range: vmD.MessageRange] = -- Returns the half open interval according to mode in which index lies. -- A word is any consecutive string of white space characters or of non white -- space characters. BEGIN charProp: dsD.CharProperty; i: CharIndex; char: CHARACTER; messageLength: CharIndex = vmD.GetMessageSize[message]; IF index >= messageLength THEN RETURN[vmD.MessageRange[messageLength, messageLength, message]]; range _ vmD.MessageRange[index, index + 1, message]; SELECT mode FROM char => NULL; word => BEGIN charProp _ dsD.GetCharBreakProp[vmD.GetMessageChar[message, index]]; IF charProp = punctuation THEN RETURN; FOR i DECREASING IN [0 .. index) UNTIL charProp # dsD.GetCharBreakProp[char _ vmD.GetMessageChar[message, i]] OR (char = Ascii.CR) DO range.start _ i; ENDLOOP; FOR i IN [index .. messageLength) UNTIL charProp # dsD.GetCharBreakProp[char _ vmD.GetMessageChar[message, i]] DO range.end _ i + 1; IF (char = Ascii.CR) THEN RETURN; ENDLOOP; END; line => BEGIN FOR i DECREASING IN [0 .. index) UNTIL ((char _ vmD.GetMessageChar[message, i]) = Ascii.CR) OR (char >= ovD.LineBreakValue) DO range.start _ i; ENDLOOP; FOR i IN [index .. messageLength) DO char _ vmD.GetMessageChar[message, i]; range.end _ i + 1; IF (char = Ascii.CR) OR (char >= ovD.LineBreakValue) THEN RETURN; ENDLOOP; END; paragraph => BEGIN FOR i DECREASING IN [0 .. index) UNTIL (vmD.GetMessageChar[message, i] = Ascii.CR) AND (i > 0) AND (vmD.GetMessageChar[message, i - 1] = Ascii.CR) DO range.start _ i; ENDLOOP; FOR i IN [index .. messageLength) DO range.end _ i + 1; IF (vmD.GetMessageChar[message, i] = Ascii.CR) AND (i > 0) AND (vmD.GetMessageChar[message, i - 1] = Ascii.CR) THEN RETURN; ENDLOOP; END; everything => {range.start _ 0; range.end _ messageLength}; ENDCASE => exD.SysBug[]; END; -- of MapCharIndexToRange -- FindCaretIndex: PROCEDURE [start, end: CharIndex, x: ScreenXCoord, y: ScreenYCoord, mnp: MessageTextNbrPtr] RETURNS [caretIndex: CharIndex] = BEGIN message: vmD.VirtualMessagePtr = mnp.message; charIndex: CharIndex _ MapCursorToCharIndex[x, y, mnp]; charLine: LinePtr; afterFlag, atEOL: BOOLEAN; char: CHARACTER; leftCharX: ScreenXCoord; charWidth: CARDINAL; length: CARDINAL = end - start; -- IF charIndex = 0 THEN RETURN[0]; IF charIndex >= vmD.GetMessageSize[message] THEN RETURN[end]; IF length = 0 THEN RETURN[start]; charLine _ MapCharIndexToLine[charIndex, mnp]; IF charLine = NIL THEN exD.SysBug[]; leftCharX _ MapCharIndexInLineToLeftX[charIndex, charLine, message]; charWidth _ dsD.GetCharRightX[vmD.GetMessageChar[message, charIndex], leftCharX] - leftCharX; afterFlag _ (length MOD 2 = 0) OR (x >= leftCharX + charWidth/2); char _ vmD.GetMessageChar[message, end-1]; atEOL _ (char = Ascii.CR OR char >= ovD.LineBreakValue); caretIndex _ (SELECT (start + length / 2) FROM = charIndex => IF afterFlag THEN end ELSE start, > charIndex => start, < charIndex => end, ENDCASE => ERROR); -- Now backup the caret one character if it's at the end of the line. IF atEOL AND caretIndex = end THEN caretIndex _ end - 1; END; -- of FindCaretIndex -- END. -- of IntTrackEdit --z20461(529)