DIRECTORY Ascii USING [CR, LF], Atom USING [GetPropFromList, PropList, PutPropOnList], Char USING [XCHAR, Widen], Commander USING [CommandProc, Register], CommanderOps USING [ArgumentVector, Failed, Parse], Imager, NodeStyle USING [GetInt, Style], NodeStyleOps USING [Alloc, ApplyAll, Free, OfStyle], PFS USING [Error, PathFromRope], TiogaIO USING [FromFile], Real USING [Round], Rope USING [Concat, ROPE], RopeReader, Scaled USING [FromInt, Round, Value], TEdit, TEditFormat USING [Allocate, FormatLine, LineInfo, Paint, Release], TextEdit, TextNode, Tioga, Xl, XTk, XTkBitmapWidgets, XTkContainers, XTkFriends, XTkScroller, XTkTioga, XTkWidgets; XTkTiogaImpl: CEDAR PROGRAM IMPORTS Atom, Char, Commander, CommanderOps, Imager, NodeStyle, NodeStyleOps, PFS, Real, Rope, RopeReader, Scaled, TiogaIO, TEditFormat, TextEdit, TextNode, Xl, XTk, XTkBitmapWidgets, XTkContainers, XTkFriends, XTkScroller, XTkWidgets EXPORTS XTkTioga ~ BEGIN OPEN TEdit; ROPE: TYPE ~ Rope.ROPE; Node: TYPE ~ Tioga.Node; Doc: TYPE ~ TEdit.Doc; View: TYPE ~ TEdit.View; CreateDoc: PROC [fileName: ROPE] RETURNS [Doc] ~ { root: Node ~ TiogaIO.FromFile[PFS.PathFromRope[fileName]].root; doc: Doc ~ NEW[TEdit.DocRep ¬ [root: root]]; FOR id: TEdit.SelectionId IN TEdit.SelectionId DO doc.selection[id] ¬ NEW[TEdit.SelectionRep ¬ []]; ENDLOOP; RETURN[doc]; }; CreateView: PROC [doc: Doc] RETURNS [View] ~ { view: View ~ NEW[TEdit.ViewRep ¬ [doc: doc]]; doc.views ¬ CONS[view, doc.views]; view.scrollLoc ¬ [TextNode.FirstChild[doc.root], 0]; RETURN[view]; }; commentTest: ARRAY TEdit.CommentFilter OF ARRAY BOOL--node.comment-- OF BOOL ~ [ includeComments: [FALSE: TRUE, TRUE: TRUE], excludeComments: [FALSE: TRUE, TRUE: FALSE], onlyComments: [FALSE: FALSE, TRUE: TRUE] ]; Level: PROC [node: Node, level: INTEGER ¬ 0] RETURNS [INTEGER] ~ --INLINE?-- { RETURN[IF level>0 THEN level ELSE TextNode.Level[node]]; }; NodeIsViewable: PROC [view: View, node: Node, level: INTEGER ¬ 0] RETURNS [BOOL] ~ { RETURN[commentTest[view.commentFilter][node.comment] AND Level[node, level]<=view.clipLevel]; }; FirstViewableNode: PROC [view: View, start: Node, startLevel: INTEGER ¬ 0] RETURNS [node: Node, level: INTEGER] ~ { level ¬ Level[(node ¬ start), startLevel]; UNTIL node=NIL OR NodeIsViewable[view, node, level] DO [node, level] ¬ TextNode.ForwardClipped[node: node, maxLevel: view.clipLevel, nodeLevel: level]; ENDLOOP; }; FirstPosInView: PROC [view: View, start: TextNode.Location, startLevel: INTEGER ¬ 0] RETURNS [node: Node ¬ NIL, where: INT ¬ 0, level: INTEGER ¬ 0] ~ { level ¬ Level[(node ¬ start.node), startLevel]; WHILE level>view.clipLevel DO node ¬ TextNode.Parent[node]; level ¬ level-1; ENDLOOP; [node, level] ¬ FirstViewableNode[view, node, level]; where ¬ IF node=start.node THEN start.where ELSE 0; IF view.firstLinesOnly THEN where ¬ 0; }; NextNodeInView: PROC [view: View, start: Node, startLevel: INTEGER ¬ 0] RETURNS [node: Node, level: INTEGER] ~ { [node, level] ¬ TextNode.ForwardClipped[node: start, maxLevel: view.clipLevel, nodeLevel: startLevel]; RETURN FirstViewableNode[view, node, level]; }; PrevNodeInView: PROC [view: View, start: Node, startParent: Node ¬ NIL, startLevel: INTEGER ¬ 0 ] RETURNS [node: Node, parent: Node, level: INTEGER] ~ { [node, parent, level] ¬ TextNode.BackwardClipped[node: start, maxLevel: view.clipLevel, parent: startParent, nodeLevel: startLevel]; UNTIL parent=NIL OR NodeIsViewable[view, node, level] DO [node, parent, level] ¬ TextNode.BackwardClipped[node: node, maxLevel: view.clipLevel, parent: parent, nodeLevel: level]; ENDLOOP; }; NextPosInView: PROC [view: View, line: TEdit.Line, lineLevel: INTEGER ¬ 0] RETURNS [node: Node ¬ NIL, where: INT ¬ 0, level: INTEGER ¬ 0] ~ { level ¬ Level[(node ¬ line.info.startPos.node), lineLevel]; IF line.info.break=eon OR view.firstLinesOnly THEN [node, level] ¬ NextNodeInView[view, node, level] ELSE where ¬ line.info.startPos.where+line.info.nChars; }; GetLine: PROC [view: View, index: NAT] RETURNS [TEdit.Line] ~ { size: NAT ~ IF view.lines=NIL THEN 0 ELSE view.lines.size; IF NOT index= view.ch THEN EXIT; -- off bottom }; line ¬ GetLine[view, nLines]; TEditFormat.FormatLine[lineInfo: line.info, node: node, startOffset: where, nodeStyle: GetStyle[node], lineWidth: lineWidth]; minLineGap ¬ NodeStyle.GetInt[GetStyle[node], minLineGap]; IF (bottomPrev+minLineGap)>(baseline-line.info.ymax) THEN { baseline ¬ bottomPrev+minLineGap+line.info.ymax; IF (NOT node.hasArtwork) AND (baseline >= view.ch) THEN EXIT; -- now off bottom }; line.baseline ¬ baseline; nLines ¬ nLines+1; -- add the line IF level>maxLevel THEN maxLevel ¬ level; [node, where, level] ¬ NextPosInView[view, line, level]; ENDLOOP; view.nLines ¬ nLines; NodeStyleOps.Free[cachedStyle]; view.stopLoc ¬ [node, where]; view.maxLevel ¬ maxLevel; ComputeLineResolveValues[view]; SetScroll[view.window, (view.range ¬ ComputeVisibleRange[view])]; IF paint THEN PaintView[view, TRUE]; }; ComputeVisibleRange: PROC [view: View] RETURNS [VisibleRange] ~ { IF view.nLines>0 THEN { ENABLE TextNode.BadArgs => CONTINUE; root: Node ~ view.doc.root; stop: TextNode.Location ~ view.stopLoc; pos0: TextNode.Location ~ [TextNode.FirstChild[root], 0]; -- first loc in doc pos3: TextNode.Location ~ TextNode.LastLocWithin[root]; -- last loc in doc pos1: TextNode.Location ~ view.lines[0].info.startPos; -- first loc in view pos2: TextNode.Location ~ IF stop.node#NIL THEN stop ELSE pos3; -- loc after view c1: INT ~ TextNode.LocOffset[pos0, pos1]; -- to top of view c2: INT ~ c1+TextNode.LocOffset[pos1, pos2]; -- to bottom of view c3: INT ~ c2+TextNode.LocOffset[pos2, pos3]; -- to end of document divisor: REAL ~ c3; RETURN[[c1/divisor, c2/divisor]]; }; RETURN[[0, 1]]; }; ComputeLineResolveValues: PROC [view: View] ~ { prev: TEdit.Line ¬ NIL; FOR i: NAT IN[0..view.nLines) DO line: TEdit.Line ~ view.lines[i]; top: INTEGER ~ line.baseline-line.info.ymax; bot: INTEGER ~ line.baseline-line.info.ymin; line.resolve ¬ bot; IF prev#NIL THEN { mid: INTEGER ~ (prev.resolve+top)/2; prev.resolve ¬ MAX[prev.baseline, MIN[mid, line.baseline]]; }; prev ¬ line; ENDLOOP; }; PaintView: PROC [view: View, clear: BOOL ¬ FALSE] ~ { action: PROC [context: Imager.Context] ~ { IF clear THEN { Imager.SetGray[context, 0]; Imager.MaskRectangleI[context, 0, 0, view.cw, view.ch]; Imager.SetGray[context, 1]; }; FOR i: NAT IN[0..view.nLines) DO line: TEdit.Line ~ view.lines[i]; Imager.SetXYI[context, Scaled.Round[line.info.xOffset], view.ch-line.baseline]; TEditFormat.Paint[line.info, context]; IF view.firstLinesOnly AND line.info.break#eon THEN Imager.ShowRope[context, "..."]; ENDLOOP; }; Paint[view.window, action]; }; ResolveToLine: PROC [view: View, y: INTEGER] RETURNS [line: Line ¬ NIL, belowLine: BOOL ¬ FALSE] ~ { FOR i: NAT IN[0..view.nLines) DO line ¬ view.lines[i]; IF y0 DO SELECT TextEdit.FetchChar[node, start-1] FROM xCR, xLF => EXIT; ENDCASE => start ¬ start - 1; ENDLOOP; NodeStyleOps.ApplyAll[style, node, kind]; DO -- find the line containing [node, where] TEditFormat.FormatLine[lineInfo: lineInfo, node: node, startOffset: start, nodeStyle: style, lineWidth: Scaled.FromInt[view.cw]]; next ¬ lineInfo.startPos.where+lineInfo.nChars; IF where {where ¬ where+1; EXIT}; ENDCASE; ENDLOOP }; IF styleNode#node THEN NodeStyleOps.ApplyAll[style, styleNode ¬ node, kind]; leading ¬ NodeStyle.GetInt[style, leading]; topLeading ¬ IF where=0 THEN NodeStyle.GetInt[style, topLeading] ELSE leading; bottomLeading ¬ totalLeading ¬ IF pos.node = node THEN leading ELSE MAX[prevTopLeading, NodeStyle.GetInt[style, bottomLeading]]; topIndent ¬ NodeStyle.GetInt[style, topIndent]; -- in case this line appears at top of viewer IF where=size THEN { lines ¬ 1; prev ¬ [node, where]; RETURN }; tPos ¬ [node, where]; lines ¬ 0; lineInfo ¬ TEditFormat.Allocate[]; DO lastBreak ¬ tPos.where; TEditFormat.FormatLine[lineInfo: lineInfo, node: tPos.node, startOffset: tPos.where, nodeStyle: style, lineWidth: Scaled.FromInt[view.cw]]; tPos ¬ lineInfo.nextPos; IF lines > 0 THEN totalLeading ¬ totalLeading+leading; lines ¬ lines+1; IF tPos.node#node OR tPos.where>=endOffset OR view.firstLinesOnly THEN EXIT; IF lastBreak#where THEN breakList ¬ CONS[lastBreak, breakList]; ENDLOOP; lineInfo.Release[]; lineInfo ¬ NIL; IF totalLeading+topIndent >= goal AND leading > 0 THEN { discardLines: INTEGER ¬ (totalLeading+topIndent-goal)/leading; -- too many lines SELECT discardLines FROM <= 0 => {}; -- don't discard any >= lines-1 => { -- discard all but one where ¬ lastBreak; lines ¬ 1; totalLeading ¬ bottomLeading ; }; ENDCASE => { count: INTEGER; -- how far to go on list to find the break lines ¬ lines-discardLines; count ¬ lines-1; -- subtract 1 because lastBreak is not on list totalLeading ¬ totalLeading-discardLines*leading; FOR list: LIST OF INT ¬ breakList, list.rest DO IF (count ¬ count-1) = 0 THEN { where ¬ list.first; EXIT }; ENDLOOP; }; }; prev ¬ [node, where]; }; IF pos.where=0 THEN { NodeStyleOps.ApplyAll[style, pos.node, kind]; topLeading ¬ NodeStyle.GetInt[style, topLeading]; }; newPos ¬ pos; lines ¬ totalLeading ¬ topIndent ¬ 0; UNTIL remainder<=0 DO leading, newLines, newTopIndent: INTEGER; [newPos, leading, newLines, newTopIndent, topLeading] ¬ IncrBackUp[newPos, remainder, topLeading]; IF newLines <= 0 THEN EXIT; -- didn't get anything that time. at start of document. totalLeading ¬ totalLeading+leading; lines ¬ lines+newLines; topIndent ¬ newTopIndent; IF totalLeading+topIndent >= goal THEN EXIT; -- don't need to back up any farther remainder ¬ remainder - leading; ENDLOOP; NodeStyleOps.Free[style]; RopeReader.FreeRopeReader[rdr]; }; Thumb: PROC [view: View, value: REAL] ~ { root: Node ~ view.doc.root; size: INT ~ TextNode.LocNumber[TextNode.LastLocWithin[root]]-1; -- -1 for root node? count: INT ~ Real.Round[value*size]; loc: TextNode.Location ~ TextNode.LocWithin[root, count]; view.scrollLoc ¬ StartOfLineContaining[view, loc]; FormatView[view]; }; ScrollUp: PROC [view: View, d: INTEGER] ~ { loc: TextNode.Location ¬ [NIL, 0]; SELECT view.nLines FROM 0 => NULL; 1 => { -- if only one line, scroll to next line [loc.node, loc.where] ¬ NextPosInView[view, view.lines[0]]; }; ENDCASE => { -- move indicated line to top FOR i: NAT IN[0..view.nLines) DO line: Line ~ view.lines[i]; loc ¬ line.info.startPos; IF d0 THEN { startPos: TextNode.Location ~ view.lines[0].info.startPos; loc ¬ BackUp[view, startPos, d].newPos; }; IF loc.node#NIL THEN { view.scrollLoc ¬ loc; FormatView[view] }; }; CommentOp: TYPE ~ TEdit.CommentFilter; SetCommentFilter: PROC [view: View, op: CommentOp] ~ { view.commentFilter ¬ op; FormatView[view]; }; LevelsOp: TYPE ~ {first, more, fewer, all}; SetLevelClipping: PROC [view: View, op: LevelsOp] ~ { level: NestingLevel ~ SELECT op FROM first => 1, more => view.maxLevel+1, fewer => (IF view.maxLevel>1 THEN view.maxLevel-1 ELSE 1), ENDCASE => NestingLevel.LAST; IF view.clipLevel#level THEN { view.clipLevel ¬ level; FormatView[view]; }; }; StyleOp: TYPE ~ TEdit.StyleKind; SetStyleKind: PROC [view: View, op: StyleOp] ~ { view.styleKind ¬ op; FormatView[view]; }; LinesOp: TYPE ~ {all, first}; SetLines: PROC [view: View, op: LinesOp] ~ { view.firstLinesOnly ¬ (op=first); FormatView[view]; }; SetViewSize: PROC [view: View, width, height: INT, paint: BOOL] ~ { view.cw ¬ width; view.ch ¬ height; FormatView[view, paint]; }; Notify: PROC [view: View, data: REF] ~ { WITH data SELECT FROM data: REF CommentOp => SetCommentFilter[view, data­]; data: REF LevelsOp => SetLevelClipping[view, data­]; data: REF StyleOp => SetStyleKind[view, data­]; data: REF LinesOp => SetLines[view, data­]; ENDCASE; }; tiogaWidgetClass: PUBLIC XTk.Class ~ XTkFriends.CreateClass[[super: XTkContainers.yStack, key: $Tioga, initInstPart: TiogaWidgetInitInstPart, configureLR: TiogaWidgetConfigure, destroyWidget: TiogaWidgetDestruction, wDataNum: 1]]; inheritedConfigureLR: XTk.ConfigureProc ~ XTkFriends.InheritedConfigureLRProc[tiogaWidgetClass.super]; ToolData: TYPE ~ REF ToolDataRep; ToolDataRep: TYPE ~ RECORD [ view: View, context: Imager.Context ¬ NIL, -- context for painting into inner inner: XTk.Widget ¬ NIL, -- innermost widget, for document contents scroller: XTk.Widget ¬ NIL -- scrollbar widget, if any ]; HandleButton: XTk.WidgetNotifyProc ~ { data: ToolData ~ NARROW[registerData]; view: View ~ data.view; IF view#NIL THEN Notify[view, callData]; }; HandleScroll: XTkScroller.ScrollProc ~ { data: ToolData ~ NARROW[clientData]; view: View ~ data.view; IF view#NIL THEN { height: INT ~ scroller.actual.size.height; d: INT ~ Real.Round[value*height]; SELECT action FROM forward => ScrollUp[view, d]; backward => ScrollDown[view, d]; thumb => { min: INT ~ MIN[5, height/2]; max: INT ~ height-min; IF dmax THEN value ¬ 1; Thumb[view, value]; }; ENDCASE; }; }; GetTiogaWidgetData: PROC [widget: XTk.Widget] RETURNS [ToolData] = INLINE { RETURN [NARROW[XTkFriends.InstPart[widget, tiogaWidgetClass]]]; }; TiogaWidgetInitInstPart: XTk.InitInstancePartProc = { addScrollbar: BOOL ¬ Atom.GetPropFromList[arguments, $scrollbar]#NIL; addMenu: BOOL ¬ Atom.GetPropFromList[arguments, $menu]#NIL; data: ToolData ~ NEW[ToolDataRep ¬ []]; tq: Xl.TQ ¬ Xl.CreateTQ[]; bottomPart: XTk.Widget ~ XTkWidgets.CreateXStack[[geometry: XTk.G[w: 300, h: 400]]]; data.inner ¬ XTkBitmapWidgets.CreateBitmapWidget[data: data]; IF addScrollbar THEN { data.scroller ¬ XTkScroller.CreateScroller[]; XTkScroller.InteractiveRegistrations[data.scroller, HandleScroll, data, tq]; XTkWidgets.AppendChild[bottomPart, data.scroller, FALSE]; }; XTkWidgets.AppendChild[bottomPart, data.inner]; IF addMenu THEN { MakeButton: PROC [name: ROPE, callData: REF] RETURNS [XTk.Widget] ~ { RETURN[XTkWidgets.CreateButton[text: Rope.Concat[name, " "], hitProc: HandleButton, registerData: data, callData: callData, tq: tq]]; }; filterChoices: XTkWidgets.ChoiceList ~ LIST[ [text: "includeComments", callData: NEW[CommentOp ¬ includeComments]], [text: "excludeComments", callData: NEW[CommentOp ¬ excludeComments]], [text: "onlyComments", callData: NEW[CommentOp ¬ onlyComments]] ]; filterToggle: XTk.Widget ~ XTkWidgets.CreateToggle[choices: filterChoices, hitProc: HandleButton, registerData: data, tq: tq]; styleChoices: XTkWidgets.ChoiceList ~ LIST[ [text: "screenStyle", callData: NEW[StyleOp ¬ screen]], [text: "printStyle", callData: NEW[StyleOp ¬ print]] ]; styleToggle: XTk.Widget ~ XTkWidgets.CreateToggle[choices: styleChoices, hitProc: HandleButton, registerData: data, tq: tq]; linesChoices: XTkWidgets.ChoiceList ~ LIST[ [text: "allLines", callData: NEW[LinesOp ¬ all]], [text: "firstLineOnly", callData: NEW[LinesOp ¬ first]] ]; linesToggle: XTk.Widget ~ XTkWidgets.CreateToggle[choices: linesChoices, hitProc: HandleButton, registerData: data, tq: tq]; menu1: XTk.Widget ~ XTkWidgets.CreateXStack[stack: LIST[filterToggle, styleToggle, linesToggle]]; menu2: XTk.Widget ~ XTkWidgets.CreateXStack[stack: LIST[ XTkWidgets.CreateLabel[text: "Levels: "], MakeButton["First", NEW[LevelsOp ¬ first]], MakeButton["More", NEW[LevelsOp ¬ more]], MakeButton["Fewer", NEW[LevelsOp ¬ fewer]], MakeButton["All", NEW[LevelsOp ¬ all]] ]]; rule: XTk.Widget ~ XTkWidgets.CreateRuler[widgetSpec: [geometry: XTk.G[h: 1]]]; header: XTk.Widget ~ XTkWidgets.CreateYStack[stack: LIST[menu1, menu2, rule]]; XTkWidgets.Choose[filterToggle, filterChoices.first]; XTkWidgets.AppendChild[widget, header, FALSE]; }; XTkWidgets.AppendChild[widget, bottomPart, FALSE]; XTkContainers.SetVaryingSize[widget]; XTkFriends.AssignInstPart[widget, tiogaWidgetClass, data]; }; TiogaWidgetDestruction: XTk.WidgetProc = { data: ToolData ~ GetTiogaWidgetData[widget]; }; TiogaWidgetConfigure: XTk.ConfigureProc = { NoteNewSize: PROC [data: ToolData, paint: BOOL] ~ { w: XTk.Widget ~ data.inner;-- view: View ~ data.view; size: Xl.Size ~ w.actual.size; IF size.width<5 AND size.height<2 THEN RETURN; XTkBitmapWidgets.CreateAndSetBitmap[widget: w, size: [f: size.width, s: size.height]]; data.context ¬ XTkBitmapWidgets.CreateContext[w]; IF view#NIL THEN SetViewSize[view, size.width, size.height, paint]; }; existW: BOOL ~ widget.actualMapping { proc: PROC ~ { action[data.context] }; Imager.DoSaveAll[data.context, proc]; }; ENDCASE; }; XWindowSetScroll: PROC [self: Window, range: VisibleRange] ~ { WITH self.data SELECT FROM data: ToolData => { scroller: XTk.Widget ~ data.scroller; IF scroller#NIL THEN XTkScroller.SetState[data.scroller, [range.top, range.bot]]; }; ENDCASE; }; windowClass: WindowClass ~ NEW[WindowClassRep ¬ [ Paint: XWindowPaint, SetScroll: XWindowSetScroll ]]; CreateWindow: PROC [data: ToolData] RETURNS [Window] ~ { RETURN[NEW[WindowRep ¬ [class: windowClass, data: data]]]; }; Paint: PROC [window: Window, action: PROC [Imager.Context]] ~ { class: WindowClass ~ window.class; IF class.Paint#NIL THEN class.Paint[window, action]; }; SetScroll: PROC [window: Window, range: VisibleRange] ~ { class: WindowClass ~ window.class; IF class.SetScroll#NIL THEN class.SetScroll[window, range]; }; OpenTiogaWindow: PROC [name: ROPE] ~ { doc: Doc ~ CreateDoc[name]; contents: XTk.Widget ~ CreateTiogaWidget[ widgetSpec: [geometry: XTk.G[w: 600, h: 800]], doc: doc ]; shell: XTk.Widget ~ XTkWidgets.CreateShell[ child: contents, windowHeader: name, standardMigration: TRUE, className: $Tioga ]; XTkWidgets.RealizeShell[shell]; }; XOpenCommand: Commander.CommandProc ~ { ENABLE { PFS.Error => ERROR CommanderOps.Failed[error.explanation]; }; argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; FOR i: NAT IN [1..argv.argc) DO name: ROPE ~ argv[i]; OpenTiogaWindow[name]; ENDLOOP; }; Commander.Register["XOpen", XOpenCommand, "Open an X window browser on a tioga document."]; END. ͺXTkTiogaImpl.mesa Copyright Σ 1991, 1992 by Xerox Corporation. All rights reserved. Christian Jacobi, April 29, 1992 12:10 pm PDT Doug Wyatt, April 1, 1992 6:30 pm PST Tioga Formatting / Painting Do this first, hoping that the style for nodePrev is still cached. Show artwork if any of it is visible, text only if the baseline is visible. PreviousLineStart: PROC [view: View, loc: TextNode.Location] RETURNS [TextNode.Location] ~ { node: Node ~ loc.node; where: INT ~ MAX[0, MIN[loc.where, TextEdit.Size[node]-1]]; start, next: INT _ where; backStop: INT ~ MAX[0, where-300]; -- limit looking back too far when searching for CR's WHILE start>backStop DO SELECT TextEdit.FetchChar[node, start-1].char FROM Ascii.CR, Ascii.LF => EXIT; ENDCASE => start _ start - 1; ENDLOOP; }; pos is the current top line in the viewer; goal is the distance to back up. algorithm works by incrementally formatting lines prior to pos until it reaches goal height. The incremental backup procedure preserves the following invariants: newPos is the current line start totalLeading is the leading from the baseline of newPos to the baseline of pos topIndent is the distance from the viewer top to baseline of newPos if newPos goes at top topLeading is the style value for topLeading corresponding to newPos no more characters in the node. shows as blank line format lines from tPos to starting pos When reach here, have found all the line breaks from [node, where] to initial pos. where holds the offset for the first one lastBreak holds the offset for the last one breakList holds the offsets for the previous ones have enough. find correct line use breakList to find correct break need to get topLeading for pos.node Once implement minGaps between lines, will also need topAscent for this line. Should be able to get that from the line table. (Except if continue to use this for top offset in ScrollToPosition... perhaps can just change that to back up a fixed number of lines.) This could be more clever it the width hasn't changed. X window class @@@@@ Eventually we should watch whether widget is visible or not and free the bitmap when not visible. Or when not used for 5 minutes, or, ... @@@@@ Even better: We should switch back to use ImagerX11. I have used BitmapWidgets to be able to use Tioga widgets independent of the maintenace state of ImagerX11. (And also because the time used for initial font caching did drive me nuts). @@@@@ fill in removal of view from doc @@@@@ Note that this is not a garbage collection finalizer but active widget destruction w#tiogaWidget ! This is called after calling the inherited ConfigureProc; therefore w has already been configured and its size is accurate. Window object Commands Κ–(cedarcode) style•NewlineDelimiter ˜codešœ™Kšœ Οeœ7™BK™-K™%K™—šΟk ˜ Kšœžœžœžœ˜Kšœžœ,˜6Kšœžœžœ ˜Kšœ žœ˜(Kšœ žœ!˜3Kšœ˜Kšœ žœ˜ Kšœ žœ"˜4Kšžœžœ˜ Kšœžœ ˜Kšœžœ ˜Kšœžœ žœ˜Kšœ ˜ Kšœžœ˜%Kšœ˜Kšœ žœ2˜CKšœ ˜ Kšœ ˜ K˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ K˜—KšΟn œžœž˜KšžœGžœ™˜κKšžœ ˜šœžœžœ˜K˜Kšžœžœžœ˜Kšœžœ˜Kšœžœ ˜Kšœžœ˜—head™šŸ œžœ žœžœ ˜2K˜?Kšœ žœ˜,šžœžœž˜1Kšœžœ˜1Kšžœ˜—Kšžœ˜ K˜K˜—šŸ œžœ žœ ˜.Kšœ žœ˜-Kšœ žœ˜"K˜4Kšžœ˜ K˜K˜—šœ žœžœžœžΟcœžœžœ˜PKš œžœžœžœžœ˜+Kš œžœžœžœžœ˜,Kš œžœžœžœžœ˜(K˜K˜—š Ÿœžœžœžœžœ  œ˜NKšžœžœ žœžœ˜8K˜K˜—š Ÿœžœ!žœžœžœ˜TKšžœ/žœ%˜^K˜K˜—š Ÿœžœ'žœžœžœ˜tK˜*šžœžœžœ#ž˜6K˜aKšžœ˜—K˜K˜—šŸœžœ4žœžœžœ žœ žœ ˜˜K˜/šžœž˜K˜.Kšžœ˜—K˜5Kšœžœžœ žœ˜3Kšžœžœ ˜&K˜K˜—š Ÿœžœ'žœžœžœ˜qK˜gKšžœ&˜,K˜K˜—š Ÿœžœ0žœžœžœ#žœ˜™K˜…šžœžœžœ#ž˜8K˜zKšžœ˜—K˜K˜—šŸ œžœ+žœžœžœ žœ žœ ˜ŽK˜;šžœžœ˜-Kšžœ2˜6Kšžœ3˜7—K˜K˜—šŸœžœžœžœ˜?Kš œžœžœ žœžœžœ˜:šžœžœ žœ ˜4Kšœ žœ˜Kšœžœ˜=Kš žœžœžœ žœžœ˜>K˜K˜—šžœžœžœ ˜2Kšœ8˜8Kšœžœ#˜:K˜—Kšžœ˜K˜K˜—šŸ œžœžœžœ˜5Kšœ2˜2K˜4Kšœžœ˜šŸ œžœžœ˜=Kšœ9˜9Kšœžœ˜,K˜—šŸœžœžœžœ˜@Kšžœžœžœ žœ˜IK˜—Kšœžœ ˜'Kšœžœ˜Kšœ žœ žœ˜!Kšœžœ˜K˜<šžœžœž˜Kšœ˜Kšœ9žœ˜Ašžœžœžœ ˜(Kšœ žœ˜Kšœ7 ˜PK˜—šžœ ˜K˜Gšžœ žœ ˜'Kšœ)˜)šœžœ7˜MK™B—Kšœ žœ0˜?Kšœ žœ ˜-K˜—Kšžœ6 ˜PK˜—K˜ šžœžœžœ -˜@K™KKš œžœžœžœ žœ ˜>Kšžœžœžœ  ˜,K˜—K˜K˜~K˜:šžœ3žœ˜;K˜0Kš žœžœžœžœžœ ˜OK˜—K˜Kšœ ˜"Kšžœžœ˜(K˜8Kšžœ˜—K˜Kšœ˜K˜K˜Kšœ˜K˜AKšžœžœžœ˜$K˜K˜—šŸœžœžœ˜Ašžœžœžœžœ˜Kšœ˜—K˜šŸœžœžœ.žœžœžœžœ˜”Kšœžœ˜Kšžœ žœC˜TKšžœžœ9˜EK˜HKšžœžœžœ˜.Kšžœ˜Kšœ˜—K˜šŸ œž œ(˜@Kšœ1˜1Kšœ Πbc˜.K˜!Kš žœ žœžœžœ 1˜NK˜Kšœ˜——™ šŸ œžœžœ˜Dšžœ žœž˜šœ˜Kšœžœ˜&K˜%K˜—Kšžœ˜—K˜K˜—šŸœžœ(˜>šžœ žœž˜šœ˜Kšœ%˜%Kšžœ žœžœ=˜QK˜—Kšžœ˜—K˜K˜—šœžœ˜1KšŸœ˜KšŸ œ˜Kšœ˜K˜—šŸ œžœžœ ˜8Kšžœžœ0˜:K˜K˜—šŸœžœžœ˜@K˜"Kšžœ žœžœ˜4K˜K˜—šŸ œžœ+˜:K˜"Kšžœžœžœ ˜;K˜K˜——™šŸœžœžœ˜&Kšœ˜šœ)˜)Kšœ7˜7Kšœ˜—šœ+˜+Kšœ8žœ˜OKšœ˜—Kšœ˜Kšœ˜K˜—šŸ œ˜'Kšžœžœ žœ+˜FKšœ<˜<šžœžœžœž˜Kšœžœ ˜Kšœ˜Kšžœ˜—Kšœ˜K˜—Kšœ[˜[—K˜Kšžœ˜—…—Zf~!