<<>> <> <> <> <> <<>> DIRECTORY Ascii, CardTab, FanoutStream, IO, KeySymsKB, KeyTypes, KeySymsOSF, KeySymsSun, KeySymsHP, Process, RefText, Rope, Xl, XlAscii, XlCursor, XlCutBuffers, XTk, XTkEditWidgets, XTkFriends, XTkInputFocus, XTkXBiScroller, XTkWidgets; XTkEditWidgetsImpl: CEDAR MONITOR IMPORTS CardTab, FanoutStream, IO, Process, RefText, Rope, Xl, XlAscii, XlCursor, XlCutBuffers, XTk, XTkFriends, XTkInputFocus, XTkXBiScroller, XTkWidgets EXPORTS XTkEditWidgets = BEGIN SetSelection: PUBLIC PROC [widget: XTk.Widget, start: INT ¬ FIRST[INT], length: INT ¬ FIRST[INT], where: XTkEditWidgets.SelectionLocation ¬ before] ~ { oRef: REF OutputRec ¬ GetOutputData[widget]; IF oRef = NIL THEN RETURN; StartBlink[oRef]; [] ¬ InvertSelection[oRef, oRef.selStart, oRef.selEnd]; IF start < 0 THEN { lData: LineData ¬ GetLineData[oRef, oRef.lastLine]; oRef.pos.y ¬ oRef.topOffset + oRef.lastLine * oRef.lineDY; oRef.pos.x ¬ oRef.leftSpace + lData.text.Length[] * oRef.charDX; oRef.selStart ¬ [oRef.leftSpace, oRef.topOffset]; oRef.selEnd ¬ oRef.pos; [] ¬ InvertSelection[oRef, oRef.selStart, oRef.selEnd]; RETURN; } ELSE { charsSeen: INT ¬ 0; startLine : CARD ¬ LAST[CARD]; endLine : CARD ¬ LAST[CARD]; startChar: INT ¬ FIRST[INT]; endChar: INT ¬ FIRST[INT]; lData: LineData; FOR x: CARD IN [0..oRef.lastLine] DO lData ¬ GetLineData[oRef, x]; charsSeen ¬ charsSeen + lData.text.Length[]; IF charsSeen > start AND startLine = LAST[CARD] THEN { startLine ¬ x; startChar ¬ start - charsSeen; }; IF length >= 0 THEN IF charsSeen > (start+length) AND endLine = LAST[CARD] THEN { endLine ¬ x; endChar ¬ start+length - charsSeen; }; ENDLOOP; IF startLine = LAST[CARD] THEN { SetSelection[widget]; RETURN; }; IF endLine = LAST[CARD] THEN { endLine ¬ oRef.lastLine; lData ¬ GetLineData[oRef, endLine]; endChar ¬ lData.text.Length[]; }; oRef.selStart ¬ [oRef.leftSpace + startChar * oRef.charDX, oRef.topOffset + startLine * oRef.lineDY]; oRef.selEnd ¬ [oRef.leftSpace + endChar * oRef.charDX, oRef.topOffset + endLine * oRef.lineDY]; IF where = after THEN oRef.pos ¬ oRef.selEnd ELSE oRef.pos ¬ oRef.selStart; [] ¬ InvertSelection[oRef, oRef.selStart, oRef.selEnd]; }; }; SetText: PUBLIC PROC [widget: XTk.Widget, text: Rope.ROPE] RETURNS [BOOL ¬ TRUE] ~ { oRef: REF OutputRec ¬ GetOutputData[widget]; IF oRef = NIL THEN RETURN[FALSE]; oRef.refreshing ¬ FALSE; FOR x: CARD DECREASING IN [1..oRef.lastLine] DO RemoveLine[oRef, x]; ENDLOOP; Xl.ClearArea[ c: oRef.widget.connection, window: oRef.widget.window, pos: [0, oRef.topOffset - oRef.ascent], size: [oRef.wSize.width, oRef.lineDY]]; [] ¬ CardTab.Store[oRef.lineTab, 0, NEW[LineDataRec ¬ ["", FALSE]]]; oRef.lastLine ¬ 0; oRef.currentLine ¬ 0; HomePos[oRef]; oRef.followCursor ¬ FALSE; Show[oRef.widget, text]; oRef.refreshing ¬ TRUE; oRef.followCursor ¬ TRUE; AdjustPos[oRef]; Redraw[oRef]; }; GetText: PUBLIC PROC [widget: XTk.Widget] RETURNS [text: Rope.ROPE ¬ "", did: BOOL ¬ TRUE] ~ { oRef: REF OutputRec ¬ GetOutputData[widget]; lData: LineData; prevLineLinked: BOOL ¬ TRUE; IF oRef = NIL THEN RETURN["", FALSE]; FOR loop: CARD IN [0..oRef.lastLine] DO lData ¬ GetLineData[oRef, loop]; IF prevLineLinked THEN text ¬ text.Concat[lData.text] ELSE text ¬ text.Cat["\n", lData.text]; prevLineLinked ¬ lData.linkedToNext; ENDLOOP; }; SetWindowSize: PROC [oRef: REF OutputRec, s: Xl.Size] = { IF oRef#NIL THEN oRef.wSize ¬ [s.width, IF oRef.scrollable THEN oRef.scrollWidget.actual.size.height ELSE s.height] }; HomePos: PROC [oRef: REF OutputRec] = { IF oRef#NIL THEN { oRef.pos.x ¬ oRef.leftSpace; oRef.pos.y ¬ oRef.topOffset; }; }; PosAndIncrement: PROC [oRef: REF OutputRec, rr: INT] RETURNS [pos: Xl.Point ¬ [0, 0]] = { <> IF oRef#NIL THEN { pos ¬ oRef.pos; oRef.pos.x ¬ oRef.pos.x+rr; }; }; <> GetLineData: PROC [oRef: REF OutputRec, line: CARD ¬ LAST[CARD]] RETURNS [lData: LineData ¬ NIL] ~ { data: REF; found: BOOLEAN; IF line = LAST[CARD] THEN line ¬ oRef.currentLine; [found, data] ¬ CardTab.Fetch[oRef.lineTab, line]; IF ~found THEN ERROR; lData ¬ NARROW[data]; }; UnflushedOutChar: ENTRY PROC [oRef: REF OutputRec, ch: CHAR] = { w: XTk.Widget ~ oRef.widget; lData: LineData; IF w.fastAccessAllowed#ok THEN RETURN; IF ~oRef.acceptKeyboard THEN { EraseMark[oRef]; oRef.pos.y ¬ oRef.topOffset + oRef.lineDY * oRef.lastLine; oRef.currentLine ¬ oRef.lastLine; oRef.pos.x ¬ FinalCharPos[oRef]; DrawMark[oRef]; }; lData ¬ GetLineData[oRef]; SuspendMark[oRef]; [] ¬ InvertSelection[oRef, oRef.selStart, oRef.selEnd]; SELECT ch FROM Ascii.CR, Ascii.LF => { IF CheckShove[oRef] THEN NewLine[oRef]; }; < {}; -- why does the 'Delete' key send Ascii.NUL?>> Ascii.NUL, Ascii.DEL => { IF oRef.selStart # oRef.selEnd THEN { sX, eX: INT; sY, eY: CARD; [sX, sY] ¬ PosFromPoint[oRef, oRef.selStart]; [eX, eY] ¬ PosFromPoint[oRef, oRef.selEnd]; oRef.pos ¬ oRef.selStart; [sX, sY, eX, eY] ¬ OrderPos[sX, sY, eX, eY]; IF eY > sY THEN { <> lData: LineData¬ GetLineData[oRef, sY]; FOR x: CARD DECREASING IN [1..eX] DO [] ¬ RemovePrevChar[oRef, eY, x]; ENDLOOP; <> FOR line: CARD DECREASING IN (sY..eY) DO RemoveLine[oRef, line]; ENDLOOP; <> FOR x: CARD DECREASING IN (sX..lData.text.Length[]] DO [] ¬ RemovePrevChar[oRef, sY, x]; ENDLOOP; [] ¬ RemovePrevChar[oRef,sY+1, 0]; } ELSE <> FOR x: CARD DECREASING IN (sX..eX] DO [] ¬ RemovePrevChar[oRef, sY, x]; ENDLOOP; }; }; Ascii.BS => { <> charPos: INT ¬ ((oRef.pos.x-oRef.leftSpace) / oRef.charDX); prevL, delP: BOOL; c: CHAR; prevX: INT ¬ 0; IF oRef.currentLine # 0 THEN { prevData: LineData ¬ GetLineData[oRef, oRef.currentLine - 1]; prevX ¬ oRef.leftSpace + oRef.charDX * prevData.text.Length[]; }; IF oRef.currentLine # 0 OR oRef.pos.x # oRef.leftSpace THEN { oRef.pos.x ¬ oRef.pos.x - oRef.charDX; [c, delP, prevL] ¬ RemovePrevChar[oRef, oRef.currentLine, charPos]; IF prevL THEN { oRef.pos.x ¬ prevX; oRef.pos.y ¬ oRef.pos.y - oRef.lineDY; IF delP THEN oRef.pos.x ¬ oRef.pos.x - oRef.charDX; oRef.currentLine ¬ oRef.currentLine - 1; }; }; }; ENDCASE => { charPos: INT ¬ ((oRef.pos.x-oRef.leftSpace) / oRef.charDX); wrapped, did: BOOLEAN; [wrapped, did] ¬ AddCharToLine[oRef, ch, oRef.currentLine, charPos]; IF ~did THEN RETURN; oRef.pos.x ¬ oRef.pos.x + oRef.charDX; IF wrapped THEN { oRef.pos.x ¬ oRef.leftSpace + oRef.charDX; oRef.pos.y ¬ oRef.pos.y + oRef.lineDY; oRef.currentLine ¬ oRef.currentLine + 1; }; }; AdjustPos[oRef]; EnableMark[oRef]; oRef.selStart ¬ oRef.selEnd ¬ oRef.pos; }; OrderPos: PROC [startX: INT, startY: CARD, endX: INT, endY: CARD] RETURNS [lowX: INT, lowY: CARD, highX: INT, highY: CARD] ~ { IF (endY < startY) OR (endY = startY AND endX < startX) THEN RETURN [endX, endY, startX, startY]; RETURN[startX, startY, endX, endY]; }; AdjustPos: PROCEDURE [oRef: REF OutputRec] ~ { IF oRef.scrollable AND oRef.followCursor THEN { where: INT ¬ - XTkXBiScroller.GetState[oRef.scrollWidget].y; diff: INT ¬ oRef.pos.y - (where + oRef.wSize.height); tooHigh: INT ¬ where - oRef.pos.y ; IF diff > -oRef.descent THEN XTkXBiScroller.PublicSetState[oRef.scrollWidget, [0, -(where + (oRef.descent + diff + 4))]] ELSE IF tooHigh > -oRef.ascent THEN XTkXBiScroller.PublicSetState[oRef.scrollWidget, [0, -(where - (oRef.ascent - tooHigh))]]; }; }; SuspendMark: PROCEDURE [oRef: REF OutputRec] ~ { EraseMark[oRef]; oRef.showMark ¬ FALSE; }; InvertMark: PROCEDURE [oRef: REF OutputRec] ~ { IF oRef.showMark AND oRef.widget.fastAccessAllowed=ok THEN IF oRef.markOn THEN EraseMark[oRef] ELSE DrawMark[oRef]; }; EraseMark: PROCEDURE [oRef: REF OutputRec] ~ { IF oRef.markOn AND oRef.widget.fastAccessAllowed=ok THEN { Xl.SetGCFunction[oRef.gc, xor]; DoDraw[oRef, oRef.pos.x, oRef.pos.y]; oRef.markOn ¬ FALSE; Xl.SetGCFunction[oRef.gc, copy]; }; }; EnableMark: PROCEDURE [oRef: REF OutputRec] ~ { oRef.showMark ¬ TRUE; DrawMark[oRef]; }; DoDraw: PROC [oRef: REF OutputRec, x, y: INT] ~ { Xl.DrawLine[c: oRef.widget.connection, drawable: oRef.widget.window.drawable, gc: oRef.gc, p1: [x,y+1], p2: [x,y+4]]; Xl.DrawLine[c: oRef.widget.connection, drawable: oRef.widget.window.drawable, gc: oRef.gc, p1: [x-1,y+2], p2: [x-1,y+2]]; Xl.DrawLine[c: oRef.widget.connection, drawable: oRef.widget.window.drawable, gc: oRef.gc, p1: [x+1,y+2], p2: [x+1,y+2]]; DoFlush[oRef.widget] }; DrawMark: PROCEDURE [oRef: REF OutputRec] ~ { IF ~oRef.markOn AND oRef.showMark AND oRef.blinking­ AND oRef.widget.fastAccessAllowed=ok THEN { Xl.SetGCFunction[oRef.gc, xor]; DoDraw[oRef, oRef.pos.x, oRef.pos.y]; oRef.markOn ¬ TRUE; Xl.SetGCFunction[oRef.gc, copy]; }; }; RemovePrevChar: PROC [oRef: REF OutputRec, line: CARD ¬ LAST[CARD], where: INT ¬ FIRST[INT]] RETURNS [ch: CHAR, deletedOnPrevLine, charOnPrevLine: BOOL ¬ FALSE] ~ { w: XTk.Widget ~ oRef.widget; pos: Xl.Point; lData: LineData ¬ GetLineData[oRef, line]; text: Rope.ROPE ¬ lData.text; linked: BOOL ¬ lData.linkedToNext; prevLData: LineData ¬ IF line # 0 THEN GetLineData[oRef, line-1] ELSE NIL; IF line = LAST[CARD] THEN line ¬ oRef.currentLine; IF where < 0 OR where > lData.text.Length[] THEN where ¬ lData.text.Length[]; pos.x ¬ oRef.leftSpace + where * oRef.charDX; pos.y ¬ oRef.topOffset + line * oRef.lineDY; IF where = 0 AND line # 0 THEN { --last char of prev line charOnPrevLine ¬ TRUE; IF prevLData.linkedToNext THEN { deletedOnPrevLine ¬ TRUE; [] ¬ RemovePrevChar[oRef, line-1]; } ELSE { prevLData.linkedToNext ¬ TRUE; FixLine[oRef, line-1]; } } ELSE IF ~(line = 0 AND where = 0) THEN { -- some random character in line ch ¬ lData.text.Fetch[where-1]; lData.text ¬ lData.text.Replace[where-1, 1, ""]; Xl.ClearArea[ c: w.connection, window: w.window, pos: [pos.x - oRef.charDX, pos.y - oRef.ascent], size: [oRef.charDX , oRef.lineDY] ]; Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [pos.x, pos.y - oRef.ascent], dstP: [pos.x - oRef.charDX, pos.y - oRef.ascent], size: [oRef.wSize.width, oRef.lineDY], gc: oRef.gc]; IF linked THEN { c: CHAR; nextLData: LineData ¬ GetLineData[oRef, line+1]; IF ~nextLData.text.IsEmpty[] THEN { [c,,] ¬ RemovePrevChar[oRef, line+1, 1]; IF AddCharToLine[oRef, c, line].thisCharWrapped THEN ERROR; }; IF nextLData.text.IsEmpty[] THEN { RemoveLine[oRef, line+1]; lData.linkedToNext ¬ FALSE; }; }; }; }; AddCharToLine: PROC [oRef: REF OutputRec, ch: CHAR, line: CARD ¬ LAST[CARD], where: INT ¬ FIRST[INT]] RETURNS [thisCharWrapped: BOOL ¬ FALSE, did: BOOL ¬ TRUE] ~ { <> w: XTk.Widget ~ oRef.widget; pos: Xl.Point; c: CHAR; lData: LineData ¬ GetLineData[oRef, line]; <> IF where < 0 THEN where ¬ lData.text.Length[]; IF ~CheckLineSpace[oRef] THEN { did ¬ FALSE; RETURN; }; pos.x ¬ oRef.leftSpace + where * oRef.charDX; pos.y ¬ oRef.topOffset + line * oRef.lineDY; IF where # lData.text.Length[] THEN { <> Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [pos.x, pos.y - oRef.ascent], dstP: [pos.x + oRef.charDX, pos.y - oRef.ascent], size: [oRef.wSize.width, oRef.lineDY], gc: oRef.gc]; }; Xl.ImageChar[w.connection, w.window.drawable, pos, oRef.gc, ch]; lData.text ¬ lData.text.Replace[where, 0, Rope.FromChar[ch]]; IF oRef.leftSpace + lData.text.Length[] * oRef.charDX + oRef.rightSpace > oRef.wSize.width THEN { <> IF where = lData.text.Length[] - 1 THEN thisCharWrapped ¬ TRUE; Xl.ClearArea[ c: w.connection, window: w.window, pos: [oRef.leftSpace + (lData.text.Length[] - 1) * oRef.charDX, pos.y - oRef.ascent], size: [oRef.charDX , oRef.lineDY] ]; <> c ¬ lData.text.Fetch[lData.text.Length[] - 1]; lData.text ¬ lData.text.Replace[lData.text.Length[] - 1, 1, ""]; <> <> IF ~lData.linkedToNext THEN { ShoveDown[oRef, line+1]; [] ¬ CardTab.Store[oRef.lineTab, line+1, NEW[LineDataRec ¬ ["", FALSE]]]; lData.linkedToNext ¬ TRUE; }; [] ¬ AddCharToLine[oRef, c, line+1, 0] }; }; CheckLineSpace: PROC [oRef: REF OutputRec, line: CARD ¬ LAST[CARD]] RETURNS [BOOL ¬ FALSE] ~ { lData: LineData; IF line = LAST[CARD] THEN line ¬ oRef.currentLine; lData ¬ GetLineData[oRef, line]; IF oRef.leftSpace + (lData.text.Length[] + 1) * oRef.charDX + oRef.rightSpace > oRef.wSize.width THEN IF lData.linkedToNext THEN RETURN[CheckLineSpace[oRef, line+1]] ELSE RETURN CheckShove[oRef]; RETURN[TRUE]; }; CheckShove: PROC [oRef: REF OutputRec, line: CARD ¬ LAST[CARD]] RETURNS [BOOL] ~ { l: INT ¬ IF line>=LAST[INT] THEN oRef.lastLine + 1 ELSE line; IF ~oRef.scrollable AND oRef.topOffset + l * oRef.lineDY + oRef.bottomOffset > oRef.wSize.height THEN RETURN[FALSE]; RETURN[TRUE]; }; RemoveLine: PROC [oRef: REF OutputRec, line: CARD] = { w: XTk.Widget ~ oRef.widget; pos: Xl.Point ¬ [0, oRef.topOffset + line * oRef.lineDY]; IF oRef.refreshing THEN { Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [pos.x, pos.y + oRef.lineDY - oRef.ascent], dstP: [pos.x, pos.y - oRef.ascent], size: [oRef.wSize.width, oRef.backingSize.height], gc: oRef.gc]; Xl.ClearArea[ c: w.connection, window: w.window, pos: [0, oRef.topOffset + oRef.lastLine * oRef.lineDY - oRef.ascent], size: [oRef.wSize.width, oRef.lineDY]]; }; FOR loop: CARD IN [line+1..oRef.lastLine] DO lData: LineData ¬ GetLineData[oRef, loop]; [] ¬ CardTab.Store[oRef.lineTab, loop-1, lData]; ENDLOOP; IF oRef.lastLine # 0 THEN oRef.lastLine ¬ oRef.lastLine - 1; GrowBacking[oRef, -1]; }; sbts: INT ¬ 10; --scrollbar total size GrowBacking: PROC [oRef: REF OutputRec, increment: INT ¬ 1] ~ { g: Xl.Geometry ¬ oRef.widget.actual; increaseSize: INT ¬ increment * oRef.lineDY; IF oRef.backingSize.height + increaseSize >= oRef.wSize.height THEN { g.size.height ¬ g.size.height + increaseSize; oRef.backingSize.height ¬ oRef.backingSize.height + increaseSize; oRef.widget.s.geometry.size.height ¬ g.size.height; IF oRef.scrollable THEN { child: XTk.Widget ¬ XTkXBiScroller.Child[oRef.scrollWidget]; geometry: Xl.Geometry ¬ []; geometry.size.width ¬ child.parent.actual.size.width-sbts; geometry.size.height ¬ oRef.backingSize.height; XTk.NoteAndStartReconfigure[child, geometry]; }; oRef.pleaseReconfigure ¬ oRef.widget; }; }; ShoveDown: PROC [oRef: REF OutputRec, startLine: CARD] = { w: XTk.Widget ~ oRef.widget; pos: Xl.Point ¬ [0, oRef.topOffset + startLine * oRef.lineDY]; size: CARD ¬ (oRef.lastLine + 1) * oRef.lineDY; IF startLine<= oRef.lastLine THEN { Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [pos.x, pos.y - oRef.ascent], dstP: [pos.x, pos.y + oRef.lineDY - oRef.ascent], size: [oRef.wSize.width, oRef.backingSize.height - pos.y - oRef.ascent], gc: oRef.gc]; Xl.ClearArea[ c: w.connection, window: w.window, pos: [0, pos.y - oRef.ascent], size: [oRef.wSize.width, oRef.lineDY]]; FOR loop: CARD DECREASING IN [startLine..oRef.lastLine] DO lData: LineData ¬ GetLineData[oRef, loop]; [] ¬ CardTab.Store[oRef.lineTab, loop+1, lData]; ENDLOOP; } ELSE [] ¬ CardTab.Store[oRef.lineTab, oRef.lastLine+1, NEW[LineDataRec ¬ ["", FALSE]]]; oRef.lastLine ¬ oRef.lastLine + 1; IF oRef.topOffset + size + oRef.bottomOffset > CARD[oRef.backingSize.height] THEN { GrowBacking[oRef]; }; }; NewLine: PROC [oRef: REF OutputRec] = { w: XTk.Widget ~ oRef.widget; nextLineText: Rope.ROPE ¬ ""; lData: LineData; doGrow: BOOL ¬ FALSE; IF oRef.currentLine # oRef.lastLine THEN ShoveDown[oRef, oRef.currentLine + 1] ELSE { size: CARD ¬ (oRef.lastLine + 1) * oRef.lineDY; oRef.lastLine ¬ oRef.lastLine + 1; IF oRef.topOffset + size + oRef.bottomOffset > CARD[oRef.backingSize.height] THEN { doGrow ¬ TRUE; }; }; lData ¬ GetLineData[oRef]; IF oRef.pos.x # oRef.leftSpace + oRef.charDX * (lData.text.Length[]) THEN { <> Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [oRef.pos.x, oRef.pos.y - oRef.ascent], dstP: [oRef.leftSpace, oRef.pos.y + oRef.lineDY - oRef.ascent], size: [oRef.wSize.width - oRef.pos.x, oRef.lineDY], gc: oRef.gc ]; Xl.ClearArea[ c: w.connection, window: w.window, pos: [oRef.pos.x, oRef.pos.y - oRef.ascent], size: [oRef.wSize.width - oRef.pos.x, oRef.lineDY]]; nextLineText ¬ lData.text.Substr[(oRef.pos.x - oRef.leftSpace) / oRef.charDX]; lData.text ¬ lData.text.Substr[0, (oRef.pos.x - oRef.leftSpace) / oRef.charDX]; }; oRef.currentLine ¬ oRef.currentLine+1; [] ¬ CardTab.Store[oRef.lineTab, oRef.currentLine, NEW[LineDataRec ¬ [nextLineText, lData.linkedToNext]]]; <> oRef.pos ¬ [oRef.leftSpace, oRef.pos.y + oRef.lineDY]; lData.linkedToNext ¬ FALSE; FixLine[oRef, oRef.currentLine]; IF doGrow THEN GrowBacking[oRef]; }; FixLine: PROC [oRef: REF OutputRec, line: CARD] = { lineChecking: CARD ¬ line; done: BOOL ¬ FALSE; w: XTk.Widget ~ oRef.widget; WHILE ~done DO lineData: LineData ¬ GetLineData[oRef, lineChecking]; IF lineChecking # oRef.lastLine AND lineData.linkedToNext AND oRef.leftSpace + lineData.text.Length[] * oRef.charDX + oRef.rightSpace <= oRef.wSize.width THEN { spaceLeft: INT ¬ (oRef.wSize.width - (lineData.text.Length[] * oRef.charDX + oRef.leftSpace + oRef.rightSpace)) / oRef.charDX; nextLine: LineData ¬ GetLineData[oRef, lineChecking+1]; nText: Rope.ROPE ¬ nextLine.text; charsAvail: INT ¬ nText.Length[]; IF charsAvail <= spaceLeft THEN { -- copy whole next line up, and done. done ¬ TRUE; Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [oRef.leftSpace, oRef.topOffset + (lineChecking+1) * oRef.lineDY - oRef.ascent], dstP: [oRef.leftSpace + lineData.text.Length[] * oRef.charDX, oRef.topOffset + lineChecking * oRef.lineDY - oRef.ascent], size: [charsAvail * oRef.charDX, oRef.lineDY], gc: oRef.gc]; lineData.text ¬ lineData.text.Concat[nText]; RemoveLine[oRef, lineChecking+1]; lineData.linkedToNext ¬ FALSE; } ELSE { Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, srcP: [oRef.leftSpace, oRef.topOffset + (lineChecking+1) * oRef.lineDY - oRef.ascent], dstP: [oRef.leftSpace + lineData.text.Length[] * oRef.charDX, oRef.topOffset + lineChecking * oRef.lineDY - oRef.ascent], size: [spaceLeft * oRef.charDX, oRef.lineDY], gc: oRef.gc]; lineData.text¬ lineData.text.Concat[nText.Substr[0, spaceLeft]]; nextLine.text ¬ nextLine.text.Replace[0, spaceLeft, ""]; Xl.CopyArea[ c: w.connection, src: w.window.drawable, dst: w.window.drawable, dstP: [oRef.leftSpace, oRef.topOffset + (lineChecking+1) * oRef.lineDY - oRef.ascent], srcP: [oRef.leftSpace + spaceLeft * oRef.charDX, oRef.topOffset + (lineChecking + 1) * oRef.lineDY - oRef.ascent], size: [(charsAvail - spaceLeft) * oRef.charDX, oRef.lineDY], gc: oRef.gc]; Xl.ClearArea[ c: w.connection, window: w.window, pos: [oRef.leftSpace + (charsAvail - spaceLeft) * oRef.charDX, oRef.topOffset + (lineChecking + 1) * oRef.lineDY - oRef.ascent], size: [spaceLeft * oRef.charDX, oRef.lineDY]]; } } ELSE done ¬ TRUE; lineChecking ¬ lineChecking + 1; ENDLOOP; }; outputStreamProcs: REF IO.StreamProcs ¬ IO.CreateStreamProcs[ variety: $output, class: $XlTexts, putChar: OutputTextWindowStreamPutChar, putBlock: OutputTextWindowStreamPutBlock, eraseChar: OutputTextWindowStreamEraseChar ]; OutputTextWindowStreamPutChar: PROC [self: IO.STREAM, char: CHAR] = { ENABLE UNCAUGHT => GOTO Oops; oRef: REF OutputRec ~ NARROW[self.streamData]; w: XTk.Widget ~ oRef.widget; IF w.fastAccessAllowed#ok THEN RETURN; UnflushedOutChar[oRef, char]; DoFlush[w]; EXITS Oops => {} }; OutputTextWindowStreamPutBlock: PROC [self: IO.STREAM, block: REF READONLY TEXT, startIndex: NAT, count: NAT] = { ENABLE UNCAUGHT => GOTO Oops; oRef: REF OutputRec ~ NARROW[self.streamData]; w: XTk.Widget ~ oRef.widget; Action: PROC[c: CHAR] RETURNS [BOOL¬FALSE] = {UnflushedOutChar[oRef, c]}; IF w.fastAccessAllowed#ok THEN RETURN; [] ¬ RefText.Map[s: block, action: Action, len: count, start: startIndex]; DoFlush[w]; EXITS Oops => {} }; OutputTextWindowStreamEraseChar: PROC [self: IO.STREAM, char: CHAR] = { OutputTextWindowStreamPutChar[self, Ascii.BS] }; outputClass: XTk.ImplementorClass ¬ XTkFriends.CreateClass[[key: $EditStreamWidget, wDataNum: 1, classNameHint: $Typescript, configureLR: Configure, initInstPart: StreamInitInstPart, forgetScreenLR: ForgetScreen]]; OutputRec: TYPE = RECORD [ widget: XTk.Widget, --backpointer for liveness scrollWidget: XTk.Widget, driblee: IO.STREAM ¬ NIL, font: Xl.Font ¬ Xl.nullFont, wSize: Xl.Size ¬ [0, 0], --size of window backingSize: Xl.Size ¬ [0, 0], --size of scrollable part...only height is used. lineDY: INT ¬ 15, --distance between lines leftSpace: NAT ¬ 2, --distance between left border and first character x origin rightSpace: NAT ¬ 2, --distance between window right border and last characters right border topOffset, bottomOffset: CARD ¬ 2, --distance between baseline and border ascent, descent: NAT ¬ 2, charDX: NAT ¬ 10, lineSpace: CARD ¬ 2, pos: Xl.Point ¬ [2, 2], --position for next character; not yet clipped lastLine: CARD ¬ 0, --position for next character; not yet clipped gc: Xl.GContext ¬ NIL, --could we somehow share gc's? currentLine: CARD ¬ 0, data: Rope.ROPE ¬ NIL, lineTab: CardTab.Ref, markPixmap: Xl.Pixmap, showMark: BOOL ¬ TRUE, startedBlinking: BOOL ¬ FALSE, markOn: BOOL ¬ FALSE, buttonOneDown:REF BOOL ¬ NEW[BOOL ¬ FALSE], buttonTwoDown:REF BOOL ¬ NEW[BOOL ¬ FALSE], buttonThreeDown:REF BOOL ¬ NEW[BOOL ¬ FALSE], selStart, selEnd: Xl.Point ¬ [2, 2], blinking: REF BOOL ¬ NEW[BOOL ¬ FALSE], scrollable: BOOL ¬ FALSE, backHeight: INT ¬ FIRST[INT], acceptKeyboard: BOOL ¬ TRUE, pleaseReconfigure: XTk.Widget ¬ NIL, followCursor: BOOL ¬ TRUE, refreshing: BOOL ¬ TRUE ]; LineData: TYPE ~ REF LineDataRec ¬ NIL; LineDataRec: TYPE ~ RECORD [ text: Rope.ROPE ¬ "", linkedToNext: BOOL ¬ FALSE ]; sampleORec: REF OutputRec ¬ NEW[OutputRec]; GetOutputData: PROC [w: XTk.Widget] RETURNS [REF OutputRec] = INLINE { data: REF ¬ XTkFriends.InstPart[w, outputClass]; WITH data SELECT FROM oRec: REF OutputRec => RETURN[oRec]; ENDCASE => { data ¬ XTk.GetWidgetProp[w, sampleORec]; IF data = NIL THEN RETURN[NIL] ELSE RETURN[NARROW[data]]; }; }; Configure: XTk.ConfigureProc = { oRef: REF OutputRec ~ GetOutputData[widget]; existW: BOOL ¬ widget.actualMapping> }; StartBlink: PROCEDURE [oRef: REF OutputRec] ~ { XTkInputFocus.SetFocus[oRef.widget]; IF ~(oRef.blinking­) THEN { bProcess: PROCESS; oRef.blinking­ ¬ TRUE; bProcess ¬ FORK BlinkProc[oRef]; DrawMark[oRef]; Process.Detach[bProcess]; }; }; Redraw: PROCEDURE [data: REF] ~ { oRef: REF OutputRec ¬ NARROW[data]; lData: LineData; IF oRef.refreshing THEN FOR x: CARD DECREASING IN [0..oRef.lastLine] DO lData ¬ GetLineData[oRef, x]; Xl.ImageRope[oRef.widget.connection, oRef.widget.window.drawable, [oRef.leftSpace, oRef.topOffset + oRef.lineDY * x-- - oRef.ascent--], oRef.gc, lData.text]; DoFlush[oRef.widget]; ENDLOOP; }; BlinkProc: PROCEDURE [oRef: REF OutputRec] ~ { <> WHILE oRef.blinking­ DO Process.PauseMsec[500]; InvertMark[oRef]; IF oRef.pleaseReconfigure # NIL THEN { XTk.NoteAndStartReconfigure[oRef.pleaseReconfigure]; oRef.pleaseReconfigure ¬ NIL; }; ENDLOOP; }; DoRect: PROCEDURE [oRef: REF OutputRec, x1: INT, y1: CARD, x2: INT, y2: CARD] ~ { Xl.FillRectangle[ oRef.widget.connection, oRef.widget.window.drawable, oRef.gc, [oRef.leftSpace + oRef.charDX * x1, oRef.topOffset + y1 * oRef.lineDY - oRef.ascent], [oRef.charDX * (x2 - x1), oRef.lineDY * (y2 - y1)] ]; }; InvertSelection: PROCEDURE[oRef: REF OutputRec, start,end: Xl.Point] RETURNS [text: Rope.ROPE ¬ ""] ~ { sX, eX: INT; sY, eY: CARD; [sX, sY] ¬ PosFromPoint[oRef, start]; [eX, eY] ¬ PosFromPoint[oRef, end]; [sX, sY, eX, eY] ¬ OrderPos[sX, sY, eX, eY]; IF eY > sY THEN { prevLineLinked: BOOL ¬ TRUE; lineData: LineData ¬ GetLineData[oRef, sY]; Xl.SetGCFunction[oRef.gc, xor]; <> DoRect[oRef, sX, sY, lineData.text.Length[], sY+1]; text ¬ text.Concat[lineData.text.Substr[sX]]; prevLineLinked ¬ lineData.linkedToNext; <> DoRect[oRef, 0, eY, eX, eY+1]; <> FOR line: CARD IN (sY..eY) DO lineData ¬ GetLineData[oRef, line]; DoRect[oRef, 0, line, lineData.text.Length[], line+1]; IF prevLineLinked THEN text ¬ text.Concat[lineData.text] ELSE text ¬ text.Cat["\n", lineData.text]; prevLineLinked ¬ lineData.linkedToNext ENDLOOP; lineData ¬ GetLineData[oRef, eY]; IF prevLineLinked THEN text ¬ text.Concat[lineData.text.Substr[0, eX]] ELSE text ¬ text.Cat["\n", lineData.text.Substr[0, eX]]; Xl.SetGCFunction[oRef.gc, copy]; } ELSE IF eX # sX THEN { <> lineData: LineData ¬ GetLineData[oRef, sY]; Xl.SetGCFunction[oRef.gc, xor]; DoRect[oRef, sX, sY, eX, sY+1]; text ¬ lineData.text.Substr[sX, eX-sX+1]; Xl.SetGCFunction[oRef.gc, copy]; }; }; ExtendProc: PROCEDURE [oRef: REF OutputRec, trigger: REF BOOL] ~ { WHILE trigger­ DO tmpStart: Xl.Point ¬ oRef.selStart; tmpEnd: Xl.Point ¬ oRef.selEnd; oRef.selEnd ¬ Xl.QueryPointer[oRef.widget.connection, oRef.widget.window].pos; [] ¬ InvertSelection[oRef, tmpStart, tmpEnd]; XlCutBuffers.Put[oRef.widget.connection, InvertSelection[oRef, oRef.selStart, oRef.selEnd]]; ENDLOOP; }; PosFromPoint: PROCEDURE [oRef: REF OutputRec, point: Xl.Point] RETURNS [x: INT, y: CARD] ~ { tmpY: INT ¬ (point.y + oRef.ascent - oRef.topOffset) / oRef.lineDY; tmpX: INT ¬ (point.x + oRef.charDX / 2 - oRef.leftSpace) / oRef.charDX; lData: LineData; y ¬ IF tmpY < 0 THEN 0 ELSE tmpY; IF y > oRef.lastLine THEN y ¬ oRef.lastLine; lData ¬ GetLineData[oRef, y]; x ¬ IF tmpX < 0 THEN 0 ELSE tmpX; IF x > lData.text.Length[] THEN x ¬ lData.text.Length[]; }; SelectProc: PROCEDURE [oRef: REF OutputRec, trigger: REF BOOL] ~ { [] ¬ InvertSelection[oRef, oRef.selStart, oRef.selEnd]; oRef.selStart ¬ oRef.pos; oRef.selEnd ¬ oRef.pos; Process.PauseMsec[150]; ExtendProc[oRef, trigger]; }; HandleButtonPress: PROCEDURE [event: Xl.Event, widget: XTk.Widget] ~ { bp: Xl.ButtonPressEvent ¬ NARROW[event]; oRef: REF OutputRec ~ GetOutputData[widget]; tmpY: INT ¬ (bp.pos.y + oRef.ascent - oRef.topOffset) / oRef.lineDY; yLines: CARD; xChar: INT; << _ IF tmpY < 0 THEN 0 ELSE tmpY; >> <> << _ IF tmpX < 0 THEN 0 ELSE tmpX;>> lData: LineData; sProcess: PROCESS ¬ NIL; p: Xl.PointerMapping ¬ Xl.GetPointerMapping[widget.connection]; changePos: BOOL ¬ FALSE; [xChar, yLines] ¬ PosFromPoint[oRef, bp.pos]; StartBlink[oRef]; <> SELECT p[bp.button] FROM 1 => { oRef.buttonOneDown­ ¬ TRUE; changePos¬ TRUE; }; 2 => oRef.buttonTwoDown­ ¬ TRUE; 3 => oRef.buttonThreeDown­ ¬ TRUE; ENDCASE => {}; IF changePos THEN { t: CARD _ tmpY; IF t > oRef.lastLine THEN xChar ¬ LAST[INT]; lData ¬ GetLineData[oRef, yLines]; IF xChar > lData.text.Length[] THEN xChar ¬ lData.text.Length[]; EraseMark[oRef]; oRef.pos ¬ [oRef.leftSpace + xChar * oRef.charDX, oRef.topOffset + yLines * oRef.lineDY]; DrawMark[oRef]; oRef.currentLine ¬ yLines; DoFlush[oRef.widget]; }; SELECT p[bp.button] FROM 1 => sProcess ¬ FORK SelectProc[oRef, oRef.buttonOneDown]; 2 => sProcess ¬ FORK SelectProc[oRef, oRef.buttonTwoDown]; 3 => sProcess ¬ FORK ExtendProc[oRef, oRef.buttonThreeDown]; ENDCASE => {}; IF sProcess # NIL THEN Process.Detach[sProcess]; }; HandleButtonRelease: PROCEDURE [event: Xl.Event, widget: XTk.Widget] ~ { bp: Xl.ButtonReleaseEvent ¬ NARROW[event]; oRef: REF OutputRec ~ GetOutputData[widget]; p: Xl.PointerMapping ¬ Xl.GetPointerMapping[widget.connection]; <> SELECT p[bp.button] FROM 1 => oRef.buttonOneDown­ ¬ FALSE; 2 => oRef.buttonTwoDown­ ¬ FALSE; 3 => oRef.buttonThreeDown­ ¬ FALSE; ENDCASE => {}; }; EventProc: Xl.EventProcType = { ENABLE { Xl.XError => GOTO oops; }; widget: XTk.Widget ~ NARROW[clientData]; oRef: REF OutputRec ~ GetOutputData[widget]; WITH event SELECT FROM focusOut: Xl.FocusOutEvent => { StopBlink[oRef]; }; buttonPress: Xl.ButtonPressEvent => { HandleButtonPress[buttonPress, widget]; }; expose: Xl.ExposeEvent => { Redraw[oRef]; }; buttonRelease: Xl.ButtonReleaseEvent => { HandleButtonRelease[buttonRelease, widget]; }; keyPress: Xl.KeyPressEvent => { char: CHAR; keysym: Xl.KeySym; matched: Xl.KeySym; isModifier: BOOL; IF ~oRef.acceptKeyboard THEN RETURN; [char: char, keysym: keysym, matched: matched, isModifier: isModifier] ¬ XlAscii.Convert[event.connection, keyPress.keyCode, keyPress.state, listOfSpecials]; IF isModifier THEN RETURN; IF matched = KeySymsSun.Paste OR matched = KeySymsOSF.Paste OR matched = KeySymsHP.Paste THEN { Show[widget, XlCutBuffers.Get[widget.connection]]; RETURN }; IF matched = KeySymsKB.Next OR matched = KeySymsKB.MoveRight THEN { XTkInputFocus.SetFocus[XTkInputFocus.NextFocusTarget[oRef.widget]]; RETURN; }; IF matched = KeySymsKB.Stop THEN { StopBlink[oRef]; RETURN; }; <> PushChar[widget, char]; <<};>> }; ENDCASE => {}; EXITS oops => {}; }; StreamInitInstPart: XTk.InitInstancePartProc = { }; DefaultHeight: INT ¬ 17; DefaultWidth: INT ¬ 200; QueryKeyboardAccepting: PUBLIC PROC [w: XTk.Widget] RETURNS [ans: BOOL ¬ FALSE] ~ { oRef: REF OutputRec ¬ GetOutputData[w]; IF oRef # NIL THEN RETURN[oRef.acceptKeyboard]; }; EnableKeyboardInput: PUBLIC PROC [w: XTk.Widget] ~ { oRef: REF OutputRec ¬ GetOutputData[w]; IF oRef # NIL THEN oRef.acceptKeyboard ¬ TRUE; }; DisableKeyboardInput: PUBLIC PROC [w: XTk.Widget] ~ { oRef: REF OutputRec ¬ GetOutputData[w]; IF oRef # NIL THEN oRef.acceptKeyboard ¬ FALSE; }; CreateEditWidget: PUBLIC PROC [widgetSpec: XTk.WidgetSpec ¬ [], scrollable: BOOL ¬ FALSE, backingHeight: INT ¬ FIRST[INT], keyboardAccepting: BOOL ¬ TRUE, widgetStream: IO.STREAM ¬ NIL] RETURNS [widget: XTk.Widget] = { oRef: REF OutputRec ~ NEW[OutputRec]; lData: LineData ~ NEW[LineDataRec]; parent: XTk.Widget ¬ NIL; IF widgetSpec.geometry.size.height <= 0 OR widgetSpec.geometry.size.height = Xl.dontUse THEN widgetSpec.geometry.size.height ¬ DefaultHeight; IF widgetSpec.geometry.size.width <= 0 OR widgetSpec.geometry.size.width = Xl.dontUse THEN widgetSpec.geometry.size.width ¬ DefaultWidth; IF scrollable THEN { child: XTk.Widget; oRef.scrollable ¬ TRUE; SELECT TRUE FROM backingHeight <= 0 => backingHeight ¬ oRef.backHeight ¬ widgetSpec.geometry.size.height; ENDCASE => oRef.backHeight ¬ backingHeight; child ¬ XTkWidgets.CreateXStack[ widgetSpec: [geometry: [[0, 0], [100, backingHeight], 0]] ]; parent ¬ XTkXBiScroller.CreateXBiScroller[ child: child, widgetSpec: widgetSpec, hsbar: FALSE]; widgetSpec.geometry.pos ¬ [0, 0]; widgetSpec.geometry.size.width ¬ widgetSpec.geometry.size.width - 15; widgetSpec.geometry.size.height ¬ backingHeight; }; widget ¬ XTk.CreateWidget[widgetSpec, outputClass]; IF widgetStream#NIL THEN BindStream[widget, widgetStream]; <> <> <> oRef.lineTab ¬ CardTab.Create[]; [] ¬ CardTab.Store[oRef.lineTab, 0, lData]; oRef.acceptKeyboard ¬ keyboardAccepting; oRef.driblee ¬ IO.CreateStream[streamProcs: outputStreamProcs, streamData: oRef]; oRef.widget ¬ widget; XTk.AddPermanentMatch[widget, [proc: EventProc, handles: events, tq: Xl.CreateTQ[], data: widget], [keyPress: TRUE, buttonPress: TRUE, buttonRelease: TRUE, focusChange: TRUE, exposure: TRUE]]; XTkFriends.AssignInstPart[widget, outputClass, oRef]; XTk.RegisterNotifier[oRef.widget, XTk.postConfigureKey, ReconfigureProc, oRef]; IF scrollable THEN { oRef.backingSize.height ¬ backingHeight; XTkWidgets.AppendChild[XTkXBiScroller.Child[parent], widget]; XTk.PutWidgetProp[parent, sampleORec, oRef]; oRef.scrollWidget ¬ parent; widget ¬ parent; }; }; ReconfigureProc: XTk.WidgetNotifyProc ~ { oRef: REF OutputRec ¬ NARROW[registerData]; IF oRef.scrollWidget#NIL THEN { child: XTk.Widget ¬ XTkXBiScroller.Child[oRef.scrollWidget]; g: Xl.Geometry; g.size.width ¬ child.parent.actual.size.width; IF g.size.width#child.actual.size.width THEN { g.size.height ¬ oRef.backingSize.height; XTk.NoteAndStartReconfigure[child, g]; RETURN; }; }; Redraw[oRef]; }; CreateStream: PUBLIC PROC [w: XTk.Widget ¬ NIL] RETURNS [widgetStream: IO.STREAM] = { widgetStream ¬ FanoutStream.Create[reportErrors: FALSE, forwardClose: FALSE]; IF w#NIL THEN BindStream[w, widgetStream]; }; <<>> BindStream: PUBLIC PROC [w: XTk.Widget, widgetStream: IO.STREAM] = { oRef: REF OutputRec ~ GetOutputData[w]; FanoutStream.AddSlave[master: widgetStream, slave: oRef.driblee]; }; END.