<> <> <> DIRECTORY Atom USING [GetPName], Convert, EditSpan USING [CannotDoEdit, Move], Imager, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, ProcessProps, PutGet USING [FromRope, ToRope], Rope, RuntimeError USING [UNCAUGHT], Scaled, TEditDocument USING [Selection], TEditFormat, TEditInput USING [CommandProc, currentEvent, Register], TEditInputOps, TEditSelection USING [Alloc, Copy, Free, GrowSelection, MakeSelection, pSel], TextEdit, TextNode, TextLooks USING [allLooks, noLooks], TiogaImager; ArtworkCenteredImpl: CEDAR PROGRAM IMPORTS Atom, Convert, EditSpan, Imager, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, ProcessProps, PutGet, Rope, RuntimeError, Scaled, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextNode, TiogaImager ~ BEGIN ROPE: TYPE ~ Rope.ROPE; Cant: ERROR [cantMessage: ROPE _ NIL] ~ CODE; IsCenteredDisplay: PROC [node: TextNode.Ref] RETURNS [BOOL] ~ { prop: REF ~ NodeProps.GetProp[node, $Artwork]; WITH prop SELECT FROM a: ATOM => RETURN [a = $CenteredDisplay]; r: ROPE => RETURN [Rope.Equal[r, "CenteredDisplay"]]; ENDCASE => RETURN [FALSE]; }; <> MakeBranchSelection: PROC ~ { tSel: TEditDocument.Selection; IF TEditSelection.pSel=NIL OR TEditSelection.pSel.viewer=NIL THEN RETURN; WHILE TEditSelection.pSel.granularity # branch DO TEditSelection.GrowSelection[]; ENDLOOP; tSel _ TEditSelection.Alloc[]; TEditSelection.Copy[source: TEditSelection.pSel, dest: tSel]; tSel.pendingDelete _ TRUE; tSel.granularity _ char; -- necessary for inserting text without destroying node properties! tSel.insertion _ after; TEditSelection.MakeSelection[new: tSel]; TEditSelection.Free[tSel] }; DoWithLocks: PROC [proc: PROC[root: TextNode.Ref, tSel: TEditDocument.Selection]] ~ { error: {none, cant, uncaught} _ none; errorMessage: ROPE _ NIL; TEditInputOps.CallWithLocks[proc, write ! Cant => {error _ cant; errorMessage _ cantMessage; CONTINUE}; RuntimeError.UNCAUGHT => {error _ uncaught; CONTINUE}; ]; SELECT error FROM none => NULL; cant => { TEditInputOps.EditFailed[errorMessage]; }; uncaught => proc[NIL, TEditSelection.Alloc[]]; -- do without lock for easy debug ENDCASE => ERROR; }; OpenCenteredDisplayOp: TEditInput.CommandProc ~ { <> OpenCenteredDisplay: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { displayNode: TextNode.Ref; MakeBranchSelection[]; displayNode _ TEditSelection.pSel.start.pos.node; IF displayNode = NIL OR NOT IsCenteredDisplay[displayNode] THEN Cant["Not a proper CenteredDisplay node (artwork property missing)"] ELSE { displayRope: ROPE _ GetCDRope[displayNode]; IF NOT Rope.Match["Artwork=CenteredDisplay*", displayRope] THEN Cant["Not a proper CenteredDisplay node (begin with Artwork=CenteredDisplay)"] ELSE { branch: TextNode.Ref _ PutGet.FromRope[rope: displayRope]; branchSpan: TextNode.Span _ TextNode.MakeNodeSpan[TextNode.FirstChild[branch], TextNode.LastWithin[branch]]; result: TextNode.Span _ EditSpan.Move[destRoot: TextNode.Root[displayNode], sourceRoot: branch, dest: TextNode.MakeNodeLoc[displayNode], source: branchSpan, where: after, nesting: 1, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => Cant[]]; UnCacheCD[displayNode]; tSel.pendingDelete _ FALSE; tSel.start.pos _ [displayNode, 0]; tSel.end.pos _ [result.end.node, MAX[TextEdit.Size[result.end.node]-1, 0]]; tSel.granularity _ branch; TEditSelection.MakeSelection[new: tSel, selection: primary]; }; }; }; DoWithLocks[OpenCenteredDisplay]; quit _ TRUE; }; CloseCenteredDisplayOp: TEditInput.CommandProc ~ { <> CloseCenteredDisplay: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { displayNode: TextNode.Ref; displayRope: ROPE; MakeBranchSelection[]; displayNode _ TEditSelection.pSel.start.pos.node; IF displayNode = TEditSelection.pSel.end.pos.node THEN Cant["Please select a branch"]; IF NodeProps.GetProp[displayNode, $Artwork] # NIL THEN Cant["Already an Artwork node (of some kind)."]; displayRope _ PutGet.ToRope[node: displayNode].output; IF NOT Rope.Match["Artwork=CenteredDisplay*", displayRope] THEN Cant["Not a proper CenteredDisplay node (begin with Artwork=CenteredDisplay)"]; TEditInputOps.ChangeCaretLooks[add: TextLooks.noLooks, remove: TextLooks.allLooks]; TEditInputOps.InsertRope[TextNode.NodeRope[displayNode]]; UnCacheCD[displayNode]; NodeProps.PutProp[displayNode, $Artwork, Atom.GetPName[$CenteredDisplay]]; NodeProps.PutProp[displayNode, $CenteredDisplayRope, displayRope]; [] _ GetCDBox[displayNode]; }; DoWithLocks[CloseCenteredDisplay]; quit _ TRUE; }; <> Format: TEditFormat.FormatProc ~ { <<[lineInfo: TEditFormat.LineInfo, node: TextEdit.RefTextNode, startOffset: TextEdit.Offset, nodeStyle: NodeStyle.Ref, lineWidth: Scaled.Value, doLigsAndKern: BOOLEAN _ FALSE, kind: NodeStyleOps.OfStyle _ screen]>> <> Inner: PROC ~ { formattedBox: TiogaImager.Box _ GetCDBox[node, lineWidth, nodeStyle]; IF formattedBox # NIL THEN { extents: Imager.Box _ formattedBox.bounds; lineInfo.artworkData _ formattedBox; <> lineInfo.xmin _ 0; lineInfo.ymin _ 0; lineInfo.xmax _ Ceiling[extents.xmax-extents.xmin]; lineInfo.ymax _ Ceiling[extents.ymax-extents.ymin]; { <> extraIndent: REAL ~ nodeStyle.GetFirstIndent; leftIndent: REAL ~ nodeStyle.GetLeftIndent + extraIndent; rightIndent: REAL ~ nodeStyle.GetRightIndent; clippedLineWidth: REAL ~ PositiveMin[nodeStyle.GetLineLength, lineWidth.Float]; trimmedLineWidth: REAL ~ MAX[clippedLineWidth-leftIndent-rightIndent, 0]; SELECT nodeStyle.GetLineFormatting[] FROM FlushLeft => {lineInfo.xOffset _ Scaled.FromReal[leftIndent];}; FlushRight => {lineInfo.xOffset _ Scaled.FromReal[MAX[lineWidth.Float-leftIndent-extents.xmax, 0]]}; Centered, Justified => {lineInfo.xOffset _ Scaled.FromReal[leftIndent+(trimmedLineWidth-extents.xmax)/2]}; ENDCASE => ERROR; }; }; }; lineInfo.startPos _ [node, 0]; lineInfo.nextPos _ [node, TextEdit.Size[node]]; lineInfo.nChars _ TextEdit.Size[node]; DoWithCatch[Inner]; }; DoWithCatch: PROC [proc: PROC] ~ { error: {none, cant, uncaught} _ none; errorMessage: ROPE _ NIL; Log: PROC [msg: ROPE] ~ { WITH ProcessProps.GetProp[$StdOut] SELECT FROM errout: IO.STREAM => { IO.PutRope[errout, msg]; IO.PutRope[errout, "\n"]; }; ENDCASE => { MessageWindow.Append[message: msg, clearFirst: TRUE]; MessageWindow.Blink[]; }; }; proc[ ! Cant => {error _ cant; errorMessage _ cantMessage; CONTINUE}; RuntimeError.UNCAUGHT => {error _ uncaught; CONTINUE}; ]; SELECT error FROM none => NULL; cant => { Log[errorMessage]; }; uncaught => proc[]; ENDCASE => ERROR; }; pointsPerMeter: REAL _ Imager.pointsPerInch/Imager.metersPerInch; infiniteDepth: REAL _ LAST[INT]; UnCacheCD: PROC [node: TextNode.Ref] ~ { NodeProps.RemProp[node, $Artwork]; NodeProps.RemProp[node, $Postfix]; NodeProps.RemProp[node, $CenteredDisplayRope]; NodeProps.RemProp[node, $CenteredDisplayRef]; NodeProps.RemProp[node, $CenteredDisplayBox]; }; GetCDRope: PROC [node: TextNode.Ref] RETURNS [displayRope: ROPE] ~ { ref: REF _ NodeProps.GetProp[node, $CenteredDisplayRope]; WITH ref SELECT FROM r: ROPE => displayRope _ r; ENDCASE => displayRope _ NIL; }; GetCDRef: PROC [node: TextNode.Ref, styleName: ATOM] RETURNS [branch: TextNode.Ref] ~ { ref: REF _ NodeProps.GetProp[node, $CenteredDisplayRef]; WITH ref SELECT FROM n: TextNode.Ref => branch _ n; ENDCASE => branch _ NIL; IF branch = NIL THEN { displayRope: ROPE _ GetCDRope[node]; IF displayRope.IsEmpty THEN RETURN [NIL] ELSE { branch _ PutGet.FromRope[rope: displayRope]; NodeProps.PutProp[branch, $Prefix, NodeProps.DoSpecs[$Prefix, Rope.Cat["\"", Atom.GetPName[styleName], "\" style"]]]; NodeProps.PutProp[branch, $Format, NodeProps.DoSpecs[$Format, "root"]]; NodeProps.RemProp[branch, $Postfix]; NodeProps.PutProp[node, $CenteredDisplayRef, branch]; }; }; }; GetCDBox: PROC [cdNode: TextNode.Ref, lineWidth: Scaled.Value _ Scaled.zero, nodeStyle: NodeStyle.Ref _ NIL] RETURNS [box: TiogaImager.Box] ~ { <> ref: REF _ NodeProps.GetProp[cdNode, $CenteredDisplayBox]; WITH ref SELECT FROM b: TiogaImager.Box => box _ b; ENDCASE => box _ NIL; IF box = NIL THEN { branch: TextNode.Ref _ GetCDRef[cdNode, nodeStyle.GetStyleName]; box _ FormatBranchToBox[branch, lineWidth, nodeStyle]; NodeProps.PutProp[cdNode, $CenteredDisplayBox, box]; NodeProps.PutProp[cdNode, $Postfix, NodeProps.DoSpecs[$Postfix, Rope.Cat[Convert.FtoRope[box.bounds.ymax-box.bounds.ymin, 2], " pt topLeading the topLeading topIndent 0 pt bottomLeading"]]]; }; }; FormatBranchToBox: PROC [branch: TextNode.Ref, lineWidth: Scaled.Value, nodeStyle: NodeStyle.Ref _ NIL] RETURNS [box: TiogaImager.Box] ~ { IF nodeStyle = NIL THEN { nodeStyle _ NodeStyleOps.Create[]; NodeStyleOps.ApplyAll[nodeStyle, branch, print]; { <> nColumns: INT ~ MAX[nodeStyle.GetColumns, 1]; columnGap: REAL ~ GetStyleParam[nodeStyle, $columnGap, 36]; pageWidth: REAL ~ nodeStyle.GetPageWidth; pageLength: REAL ~ nodeStyle.GetPageLength; leftMargin: REAL ~ nodeStyle.GetLeftMargin; rightMargin: REAL ~ nodeStyle.GetRightMargin; bindingMargin: REAL ~ nodeStyle.GetBindingMargin; totalWidth: REAL ~ MAX[pageWidth-leftMargin-rightMargin-bindingMargin, 0.0]; lineWidth _ Scaled.FromReal[MAX[(totalWidth-(nColumns-1)*columnGap)/nColumns, 0.0]]; }; }; { <> extraIndent: REAL ~ nodeStyle.GetFirstIndent; leftIndent: REAL ~ nodeStyle.GetLeftIndent + extraIndent; rightIndent: REAL ~ nodeStyle.GetRightIndent; clippedLineWidth: REAL ~ PositiveMin[nodeStyle.GetLineLength, lineWidth.Float]; trimmedLineWidth: REAL ~ MAX[clippedLineWidth-leftIndent-rightIndent, 0]; <> inBounds: Imager.VEC _ [trimmedLineWidth, infiniteDepth]; formatted: TiogaImager.FormattedNodes _ TiogaImager.FormatNodes[start: [TextNode.FirstChild[branch], 0], bounds: inBounds, screenStyle: FALSE, filter: NIL, sep: NIL]; depth: REAL _ GetDepth[branch, nodeStyle]; box _ TiogaImager.BoxFromList[list: TiogaImager.UnBox[formatted.box], yFix: IF depth = infiniteDepth THEN [] ELSE [expansion, depth]]; }; }; GetStyleParam: PROC [style: NodeStyle.Ref, param: ATOM, default: REAL] RETURNS [REAL] ~ { <> val: REAL _ default; val _ NodeStyleOps.GetStyleParam[s: style, name: param, styleName: style.name[style], kind: IF style.print THEN print ELSE screen]; RETURN [val] }; GetDepth: PROC [node: TextNode.Ref, nodeStyle: NodeStyle.Ref] RETURNS [depth: REAL _ 0.0] ~ { <> rope: ROPE ~ TextEdit.GetRope[node]; size: INT ~ Rope.Size[rope]; IF size # 0 THEN { i: INT _ -1; Getc: PROC RETURNS [CHAR] ~ {i_i+1; RETURN [IF i> startTok: INT _ 0; sizeTok: INT _ 0; GetTok: PROC ~ { startTok _ i; UNTIL c = ' DO c _ Getc[] ENDLOOP; sizeTok _ i-startTok; WHILE c = ' AND i=size DO GetTok[]; -- ignored GetTok[]; SELECT TRUE FROM Match["depth"] => depth _ GetReal[]; ENDCASE => depth _ infiniteDepth; ENDLOOP; } ELSE { depth _ infiniteDepth; }; }; PositiveMin: PROC [a, b: REAL] RETURNS [REAL] ~ { IF a < 0 THEN RETURN [MAX[b, 0.01]]; IF b < 0 THEN RETURN [a]; RETURN [MIN[a, b]] }; Floor: PROC [r: REAL] RETURNS [i: INT] ~ { i _ Real.Round[r]; IF i > r THEN i _ i-1; }; Ceiling: PROC [r: REAL] RETURNS [i: INT] ~ { i _ Real.Round[r]; IF i < r THEN i _ i+1; }; Resolve: TEditFormat.ResolveProc ~ { loc _ lineInfo.startPos; xmin _ lineInfo.xmin+lineInfo.xOffset.integerPart; width _ lineInfo.xmax; rightOfLine _ FALSE; }; CharPosition: TEditFormat.CharPositionProc ~ { x _ lineInfo.xmin+lineInfo.xOffset.integerPart; width _ lineInfo.xmax; }; BoundingBox: TEditFormat.BoundingBoxProc ~ { RETURN [[lineInfo.xmin, lineInfo.ymin, lineInfo.xmax, lineInfo.ymax]] }; Paint: TEditFormat.PaintProc ~ { <<[lineInfo: TEditFormat.LineInfo, context: Imager.Context]>> Action: PROC ~ { box: TiogaImager.Box _ NARROW[lineInfo.artworkData]; Imager.Trans[context]; TiogaImager.Render[box, context, [box.bounds.xmin, -box.bounds.ymin]]; }; Imager.DoSave[context, Action]; }; <> centeredDisplayClass: TEditFormat.ArtworkClass ~ NEW[TEditFormat.ArtworkClassRep _ [ name: $CenteredDisplay, format: Format, paint: Paint, resolve: Resolve, charPosition: CharPosition, boundingBox: BoundingBox ]]; TEditFormat.RegisterArtwork[centeredDisplayClass]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayRope, attribute: $ClientOnly]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayRef, attribute: $ClientOnly]; NodeProps.DeclarePropertyAttribute[name: $CenteredDisplayBox, attribute: $ClientOnly]; TEditInput.Register[name: $OpenCenteredDisplay, proc: OpenCenteredDisplayOp, before: TRUE]; TEditInput.Register[name: $CloseCenteredDisplay, proc: CloseCenteredDisplayOp, before: TRUE]; END.