unit GvOutln;

{ Graphics Vision Outline,
  Copr. 1996 Matthias K"oppe.

  Derived from Outline.pas, which is copr. 1992 Borland Intl.
  Portions copr. 1992 Borland Intl.
}

interface

uses Objects, Drivers, GvViews;

const
  ovExpanded = $01;
  ovChildren = $02;
  ovLast     = $04;

const
  cmOutlineItemSelected = 301;

const
  COutlineViewer = CScroller + #8#8;

type

{ TOutlineViewer  object }

  { Palette layout }
  { 1 = Normal color }
  { 2 = Focus color }
  { 3 = Select color }
  { 4 = Not expanded color }

  POutlineViewer = ^TOutlineViewer;
  TOutlineViewer = object(TScroller)
    Foc: Integer;
    constructor Init(var Bounds: TRect; AHScrollBar,
      AVScrollBar: PScrollBar);
    constructor Load(var S: TStream);
    procedure Adjust(Node: Pointer; Expand: Boolean); virtual;
    function CreateGraph(Level: Integer; Lines: LongInt; Flags: Word;
      LevWidth, EndWidth: Integer; const Chars: String): String;
    procedure Draw; virtual;
    procedure ExpandAll(Node: Pointer);
    function FirstThat(Test: Pointer): Pointer;
    procedure Focused(I: Integer); virtual;
    function ForEach(Action: Pointer): Pointer;
    function GetChild(Node: Pointer; I: Integer): Pointer; virtual;
    function GetGraph(Level: Integer; Lines: LongInt; Flags: Word): String; virtual;
    function GetNumChildren(Node: Pointer): Integer; virtual;
    function GetNode(I: Integer): Pointer;
    function GetPalette: PPalette; virtual;
    function GetRoot: Pointer; virtual;
    function GetText(Node: Pointer): String; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    function HasChildren(Node: Pointer): Boolean; virtual;
    function IsExpanded(Node: Pointer): Boolean; virtual;
    function IsSelected(I: Integer): Boolean; virtual;
    procedure Selected(I: Integer); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure Store(var S: TStream);
    procedure Update;
  private
    procedure AdjustFocus(NewFocus: Integer);
    function Iterate(Action: Pointer; CallerFrame: Word;
      CheckRslt: Boolean): Pointer;
  end;

{ TNode }

  PNode = ^TNode;
  TNode = record
    Next: PNode;
    Text: PString;
    ChildList: PNode;
    Expanded: Boolean;
  end;

{ TOutline object }

  { Palette layout }
  { 1 = Normal color }
  { 2 = Focus color }
  { 3 = Select color }

  POutline = ^TOutline;
  TOutline = object(TOutlineViewer)
    Root: PNode;

    constructor Init(var Bounds: TRect; AHScrollBar,
      AVScrollBar: PScrollBar; ARoot: PNode);
    constructor Load(var S: TStream);
    destructor Done; virtual;

    procedure Adjust(Node: Pointer; Expand: Boolean); virtual;
    function GetRoot: Pointer; virtual;
    function GetNumChildren(Node: Pointer): Integer; virtual;
    function GetChild(Node: Pointer; I: Integer): Pointer; virtual;
    function GetText(Node: Pointer): String; virtual;
    function IsExpanded(Node: Pointer): Boolean; virtual;
    function HasChildren(Node: Pointer): Boolean; virtual;
    procedure Store(var S: TStream);
  end;

const
  ROutline: TStreamRec = (
     ObjType: 91;
     VmtLink: Ofs(TypeOf(TOutline)^);
     Load:    @TOutline.Load;
     Store:   @TOutline.Store
  );

procedure RegisterOutline;
function NewNode(const AText: String; AChildren, ANext: PNode): PNode;
procedure DisposeNode(Node: PNode);

implementation

{ TOutlineViewer }

constructor TOutlineViewer.Init(var Bounds: TRect; AHScrollBar,
  AVScrollBar: PScrollBar);
begin
  inherited Init(Bounds, AHScrollBar, AVScrollBar);
  GrowMode := gfGrowHiX + gfGrowHiY;
  Foc := 0;
end;

constructor TOutlineViewer.Load(var S: TStream);
begin
  inherited Load(S);
  S.Read(Foc, SizeOf(Foc));
end;

{ Called when the user requests Node to be contracted or
  expanded (i.e. its children to be hidden or shown) }

procedure TOutlineViewer.Adjust(Node: Pointer; Expand: Boolean);
begin
  Abstract;
end;

{ Called internally to ensure the focus is within range and displayed }

procedure TOutlineViewer.AdjustFocus(NewFocus: Integer);
begin
  if NewFocus < 0 then NewFocus := 0
  else if NewFocus >= Limit.Y then NewFocus := Limit.Y - 1;
  if Foc <> NewFocus then Focused(NewFocus);
  if NewFocus < Delta.Y then
    ScrollTo(Delta.X, NewFocus)
  else if NewFocus - ShownRows >= Delta.Y then
    ScrollTo(Delta.X, NewFocus - ShownRows + 1);
end;

{ Called to draw the outline }

procedure TOutlineViewer.Draw;
var
  NrmColor, SelColor, FocColor: Word;
  B: TDrawBuffer;
  I: Integer;

  function DrawTree(Cur: Pointer; Level, Position: Integer; Lines: LongInt;
    Flags: Word): Boolean; {$ifndef FPK}far;{$endif}
  var
    Color: Word;
    S: String;
  begin
    DrawTree := False;

    if Position >= Delta.Y then
    begin
      if Position >= Delta.Y + ShownRows then
      begin
	DrawTree := True;
	Exit;
      end;

      if (Position = Foc) and (State and sfFocused <> 0) then
	Color := FocColor
      else if IsSelected(Position) then
	Color := SelColor
      else
	Color := NrmColor;
      MoveChar(B, ' ', Color, Size.X);
      S := GetGraph(Level, Lines, Flags);
      if Flags and  ovExpanded = 0 then
	S := Concat(S, '~', GetText(Cur), '~')
      else
	S := Concat(S, GetText(Cur));
      MoveCStr(B, Copy(S, Delta.X + 1, 255), Color);
      WriteLine(0, Position - Delta.Y, Size.X, 1, B);
      I := Position;
    end;
  end;

begin
  NrmColor := GetColor($0401);
  FocColor := GetColor($0202);
  SelColor := GetColor($0303);
  FirstThat(@DrawTree);
  MoveChar(B, ' ', NrmColor, Size.X);
  WriteLine(0, I + 1, Size.X, ShownRows - (I - Delta.Y), B);
end;

{ ExpandAll expands the current node and all child nodes }

procedure TOutlineViewer.ExpandAll(Node: Pointer);
var
  I, N: Integer;
begin
  if HasChildren(Node) then
  begin
    Adjust(Node, True);
    N := GetNumChildren(Node) - 1;
    for I := 0 to N do
      ExpandAll(GetChild(Node, I));
  end;
end;

{ Draws a graph string suitable for returning from GetGraph.  Level
  indicates the outline level.  Lines is the set of bits decribing
  the which levels have a "continuation" mark (usually a vertical
  lines).  If bit 3 is set, level 3 is continued beyond this level.
  Flags gives extra information about how to draw the end of the
  graph (see the ovXXX constants).  LevWidth is how many characters
  to indent for each level.  EndWidth is the length the end characters.

  The graphics is divided into two parts: the level marks, and the end
  or node graphic.  The level marks consist of the Level Mark character
  separated by Level Filler.  What marks are present is determined by
  Lines.  The end graphic is constructed by placing on of the End First
  charcters followed by EndWidth-4 End Filler characters, followed by the
  End Child character, followed by the Retract/Expand character.  If
  EndWidth equals 2, End First and Retract/Expand are used.  If EndWidth
  equals 1, only the Retract/Expand character is used.  Which characters
  are selected is determined by Flags.

  The layout for the characters in the Chars is:

   1: Level Filler
     Typically a space.  Used between level markers.
   2: Level Mark
     Typically a vertical bar.  Used to mark the levels currenly active.
   3: End First (not last child)
     Typically a sideways T.  Used as the first character of the end part
     of a node graphic if the node is not the last child of the parent.
   4: End First (last child)
     Typically a L shape.  Used as the first character of the end part
     of a node graphic if the node is the last child of the parent.
   5: End Filler
     Typically a horizontal line.  Used as filler for the end part of a
     node graphic.
   6: End Child position
     Typically not used.  If EndWidth > LevWidth this character will
     be placed on top of the markers for next level.  If used it is
     typically a T.
   7: Retracted character
     Typically a '+'.  Displayed as the last character of the end
     node if the level has children and they are not expanded.
   8: Expanded character
     Typically as straight line. Displayed as the last character of
     the end node if the level has children and they are expanded.

  As an example GetGraph calls CreateGraph with the following paramters:

    CreateGraph(Level, Lines, Flags, 3, 3, ' '#179#195#192#196#196'+'#196);

  To use double, instead of single lines use:

    CreateGraph(Level, Lines, Flags, 3, 3, ' '#186#204#200#205#205'+'#205);

  To have the children line drop off prior to the text instead of underneath,
  use the following call:

    CreateGraph(Level, Lines, Flags, 2, 4, ' '#179#195#192#196#194'+'#196);

  }

function TOutlineViewer.CreateGraph(Level: Integer; Lines: LongInt;
  Flags: Word; LevWidth, EndWidth: Integer;
  const Chars: String): String; assembler;
const
  FillerOrBar   = 0;
  YorL          = 2;
  StraightOrTee = 4;
  Retracted     = 6;
var
  Last, Children, Expanded: Boolean;
asm
        PUSH    DS
        CLD

        { Break out flags }
        XOR     BX,BX
        MOV     AX,Flags
        MOV     Expanded,BL
        SHR     AX,1
	ADC     Expanded,BL
        MOV     Children,BL
        SHR     AX,1
        ADC     Children,BL
        MOV     Last,BL
        SHR     AX,1
        ADC     Last,BL

        { Load registers }
        LDS     SI,Chars
        INC     SI
        LES     DI,@Result
        INC     DI
        MOV     AX,Lines.Word[0]
        MOV     DX,Lines.Word[2]
        INC     Level

        { Write bar characters }
        JMP     @@2
@@1:    XOR     BX,BX
	SHR     DX,1
        RCR     AX,1
        RCL     BX,1
        PUSH    AX
        MOV     AL,[SI].FillerOrBar[BX]
        STOSB
        MOV     AL,[SI].FillerOrBar
        MOV     CX,LevWidth
        DEC     CX
        REP     STOSB
        POP     AX
@@2:    DEC     Level
        JNZ     @@1

        { Write end characters }
        MOV     BH,0
        MOV     CX,EndWidth
        DEC     CX
        JZ      @@4
        MOV     BL,Last
	MOV     AL,[SI].YorL[BX]
        STOSB
        DEC     CX
        JZ      @@4
        DEC     CX
        JZ      @@3
        MOV     AL,[SI].StraightOrTee
        REP     STOSB
@@3:    MOV     BL,Children
        MOV     AL,[SI].StraightOrTee[BX]
        STOSB
@@4:    MOV     BL,Expanded
        MOV     AL,[SI].Retracted[BX]
        STOSB
        MOV     AX,DI
        LES     DI,@Result
        SUB     AX,DI
        DEC     AX
        STOSB
        POP     DS
end;

{ Internal function used to fetch the caller's stack frame }

function CallerFrame: Word; inline(
  $8B/$46/$00           { MOV   AX,[BP] }
);


{ FirstThat iterates over the nodes of the outline until the given
  local function returns true. The declaration for the local function
  must look like (save for the names, of course):

    function MyIter(Cur: Pointer; Level, Position: Integer;
      Lines: LongInt; Flags: Word); far;

  The parameters are as follows:

    Cur:        A pointer to the node being checked.
    Level:      The level of the node (how many node above it it has)
		Level is 0 based.  This can be used to a call to
                either GetGraph or CreateGraph.
    Position:   The display order position of the node in the list.
                This can be used in a call to Focused or Selected.
                If in range, Position - Delta.Y is location the node
                is displayed on the view.
    Lines:      Bits indicating the active levels.  This can be used in a
                call to GetGraph or CreateGraph. It dicatates which
                horizontal lines need to be drawn.
    Flags:      Various flags for drawing (see ovXXXX flags).  Can be used
                in a call to GetGraph or CreateGraph. }

function TOutlineViewer.FirstThat(Test: Pointer): Pointer;
begin
  FirstThat := Iterate(Test, CallerFrame, True);
end;

{ Called whenever Node is receives focus }

procedure TOutlineViewer.Focused(I: Integer);
begin
  Foc := I;
end;

{ Iterates over all the nodes.  See FirstThat for a more details }

function TOutlineViewer.ForEach(Action: Pointer): Pointer;
begin
  Iterate(Action, CallerFrame, False);
end;

{ Returns the outline palette }

function TOutlineViewer.GetPalette: PPalette;
const
  P: String[Length(COutlineViewer)] = COutlineViewer;
begin
  GetPalette := @P;
end;

{ Overridden to return a pointer to the root of the outline }

function TOutlineViewer.GetRoot: Pointer;
begin
  Abstract;
end;

{ Called to retrieve the characters to display prior to the
  text returned by GetText.  Can be overridden to return
  change the appearance of the outline. My default calls
  CreateGraph with the default. }

function TOutlineViewer.GetGraph(Level: Integer; Lines: LongInt;
  Flags: Word): String;
{const
  LevelWidth = 2;
  EndWidth   = LevelWidth + 2;
  GraphChars = ' '#179#195#192#196#194'+'#196;
{  GraphChars = ' '#186#204#200#205#203'+'#205;}
const
  LevelWidth = 3;
  EndWidth   = LevelWidth;
  GraphChars = ' '#179#195#192#196#196'+'#196;
{  GraphChars = ' '#186#204#200#205#205'+'#205;}
begin
  GetGraph := Copy(CreateGraph(Level, Lines, Flags, LevelWidth, EndWidth,
    GraphChars), EndWidth, 255);
end;

{ Returns a pointer to the node that is to be shown on line I }

function TOutlineViewer.GetNode(I: Integer): Pointer;
var
  Cur: Pointer;

  function IsNode(Node: Pointer; Level, Position: Integer; Lines: LongInt;
    Flags: Word): Boolean; {$ifndef FPK}far;{$endif}
  begin
    IsNode := I = Position;
  end;

begin
  GetNode := FirstThat(@IsNode);
end;

{ Overridden to return the number of children in Node. Will not be
  called if HasChildren returns false.  }

function TOutlineViewer.GetNumChildren(Node: Pointer): Integer;
begin
  Abstract;
end;

{ Overriden to return the I'th child of Node. Will not be called if
  HasChildren returns false. }

function TOutlineViewer.GetChild(Node: Pointer; I: Integer): Pointer;
begin
  Abstract;
end;

{ Overridden to return the text of Node }

function TOutlineViewer.GetText(Node: Pointer): String;
begin
  Abstract;
end;

{ Overriden to return if Node's children should be displayed.  Will
  never be called if HasChildren returns False. }

function TOutlineViewer.IsExpanded(Node: Pointer): Boolean;
begin
  Abstract;
end;

{ Returns if Node is selected.  By default, returns true if Node is
  Focused (i.e. single selection).  Can be overriden to handle
  multiple selections. }

function TOutlineViewer.IsSelected(I: Integer): Boolean;
begin
  IsSelected := Foc = I;
end;

{ Internal function used by both FirstThat and ForEach to do the
  actual iteration over the data. See FirstThat for more details }

function TOutlineViewer.Iterate(Action: Pointer; CallerFrame: Word;
  CheckRslt: Boolean): Pointer;
var
  Position: Integer;

  function TraverseTree(Cur: Pointer; Level: Integer;
    Lines: LongInt; LastChild: Boolean): Pointer; {$ifndef FPK}far;{$endif}
  label
    Retn;
  var
    J, ChildCount: Integer;
    Ret: Pointer;
    Flags: Word;
    Children: Boolean;
  begin
    TraverseTree := Cur;
    if Cur = nil then Exit;

    Children := HasChildren(Cur);

    Flags := 0;
    if LastChild then Inc(Flags, ovLast);
    if Children and IsExpanded(Cur) then Inc(Flags, ovChildren);
    if not Children or IsExpanded(Cur) then Inc(Flags, ovExpanded);

    Inc(Position);

    { Perform call }
    asm
	LES     DI,Cur                     { Push Cur }
	PUSH    ES
	PUSH    DI
	MOV     BX,[BP+6]                  { Load parent frame into BX }
	PUSH    Level
	PUSH    WORD PTR SS:[BX].offset Position
	PUSH    Lines.Word[2]
	PUSH    Lines.Word[0]
	PUSH    Flags
	PUSH    WORD PTR SS:[BX].offset CallerFrame
        CALL    DWORD PTR SS:[BX].offset Action
        OR      AL,AL
        MOV     BX,[BP+6]                   { Load parent frame into BX }
        AND     AL,SS:[BX].offset CheckRslt { Force to 0 if CheckRslt False }
        JNZ     Retn
    end;

    if Children and IsExpanded(Cur) then
    begin
      ChildCount := GetNumChildren(Cur);

      if not LastChild then Lines := Lines or (1 shl Level);
      for J := 0 to ChildCount - 1 do
      begin
        Ret := TraverseTree(GetChild(Cur, J), Level + 1, Lines,
          J = (ChildCount - 1));
	TraverseTree := Ret;
	if Ret <> nil then Exit;
      end;
    end;
    TraverseTree := nil;
  Retn:
  end;

begin
  Position := -1;

  asm                           { Convert 0, 1 to 0, FF }
	DEC     CheckRslt
	NOT     CheckRslt
  end;

  Iterate := TraverseTree(GetRoot, 0, 0, True);
end;

{ Called to handle an event }

procedure TOutlineViewer.HandleEvent(var Event: TEvent);
const
  MouseAutoToSkip = 3;
var
  Mouse: TPoint;
  Cur: Pointer;
  NewFocus: Integer;
  Count: Integer;
  Graph: String;
  Dragged: Byte;

  function GetFocusedGraphic(var Graph: String): Pointer;
  var
    Lvl: Integer;
    Lns: LongInt;
    Flgs: Word;

    function IsFocused(Cur: Pointer; Level, Position: Integer;
      Lines: LongInt; Flags: Word): Boolean; {$ifndef FPK}far;{$endif}
    begin
      if Position = Foc then
      begin
        IsFocused := True;
        Lvl := Level;
        Lns := Lines;
        Flgs := Flags;
      end
      else IsFocused := False;
    end;

  begin
    GetFocusedGraphic := FirstThat(@IsFocused);
    Graph := GetGraph(Lvl, Lns, Flgs);
  end;


begin
  inherited HandleEvent(Event);
  case Event.What of
    evMouseDown:
      begin
        Count := 0;
        Dragged := 0;
        repeat
          if Dragged < 2 then Inc(Dragged);
          MakeLocal(Event.Where, Mouse);
          if MouseInView(Event.Where) then
            NewFocus := Delta.Y + Mouse.Y
          else
          begin
            if Event.What = evMouseAuto then Inc(Count);
            if Count = MouseAutoToSkip then
            begin
              Count := 0;
              if Mouse.Y < 0 then Dec(NewFocus);
	      if Mouse.Y >= ShownRows then Inc(NewFocus);
            end;
          end;
          if Foc <> NewFocus then
	  begin
            AdjustFocus(NewFocus);
            DrawView;
          end;
        until not MouseEvent(Event, evMouseMove + evMouseAuto);
        if Event.Double then Selected(Foc)
        else
        begin
          if Dragged < 2 then
          begin
            Cur := GetFocusedGraphic(Graph);
            if Mouse.X < Length(Graph) then
            begin
              Adjust(Cur, not IsExpanded(Cur));
              Update;
              DrawView;
	    end;
          end;
        end;
      end;
    evKeyboard:
      begin
        NewFocus := Foc;
        case CtrlToArrow(Event.KeyCode) of
          kbUp, kbLeft:    Dec(NewFocus);
          kbDown, kbRight: Inc(NewFocus);
	  kbPgDn:          Inc(NewFocus, ShownRows - 1);
	  kbPgUp:          Dec(NewFocus, ShownRows - 1);
          kbHome:          NewFocus := Delta.Y;
	  kbEnd:           NewFocus := Delta.Y + ShownRows - 1;
          kbCtrlPgUp:      NewFocus := 0;
	  kbCtrlPgDn:      NewFocus := Limit.Y - 1;
	  kbCtrlEnter,
	  kbEnter:         Selected(NewFocus);
	else
	  case Event.CharCode of
	    '-', '+': Adjust(GetNode(NewFocus), Event.CharCode = '+');
            '*':      ExpandAll(GetNode(NewFocus));
          else
            Exit;
          end;
          Update;
        end;
        ClearEvent(Event);
        AdjustFocus(NewFocus);
        DrawView;
      end;
  end;
end;

{ Called to determine if the given node has children }

function TOutlineViewer.HasChildren(Node: Pointer): Boolean;
begin
  Abstract;
end;

{ Called whenever Node is selected by the user either via keyboard
  control or by the mouse. }

procedure TOutlineViewer.Selected(I: Integer);
begin
end;

{ Redraws the outline if the outliner sfFocus state changes }

procedure TOutlineViewer.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  if AState and sfFocused <> 0 then DrawView;
end;

{ Store the object to a stream }

procedure TOutlineViewer.Store(var S: TStream);
begin
  inherited Store(S);
  S.Write(Foc, SizeOf(Foc));
end;

{ Updates the limits of the outline viewer.  Should be called whenever
  the data of the outline viewer changes.  This includes during
  the initalization of base classes.  TOutlineViewer assumes that
  the outline is empty.  If the outline becomes non-empty during the
  initialization, Update must be called. Also, if during the operation
  of the TOutlineViewer the data being displayed changes, Update
  and DrawView must be called. }

procedure TOutlineViewer.Update;
var
  Count, MaxX: Integer;

  function CountNode(P: Pointer; Level, Position: Integer; Lines: LongInt;
    Flags: Word): Boolean; {$ifndef FPK}far;{$endif}
  var
    Len: Integer;
  begin
    Inc(Count);
    Len := Length(GetText(P)) + Length(GetGraph(Level, Lines, Flags));
    if MaxX < Len then MaxX := Len;
    CountNode := False;
  end;

begin
  Count := 0;
  MaxX := 0;
  FirstThat(@CountNode);
  SetLimit(MaxX, Count);
  AdjustFocus(Foc);
end;

{ TOutline }

constructor TOutline.Init(var Bounds: TRect; AHScrollBar, AVScrollBar: PScrollBar;
  ARoot: PNode);
begin
  inherited Init(Bounds, AHScrollBar, AVScrollBar);
  Root := ARoot;
  Update;
end;

constructor TOutline.Load(var S: TStream);

  function LoadNode: PNode;
  var
    IsNode: Boolean;
    Node: PNode;
  begin
    S.Read(IsNode, SizeOf(IsNode));
    if IsNode then
    begin
      New(Node);
      with Node^ do
      begin
        S.Read(Expanded, SizeOf(Expanded));
        Text := S.ReadStr;
        ChildList := LoadNode;
        Next := LoadNode;
      end;
      LoadNode := Node;
    end
    else
      LoadNode := nil;
  end;

begin
  inherited Load(S);
  Root := LoadNode;
end;

destructor TOutline.Done;
begin
  DisposeNode(Root);
  inherited Done;
end;

procedure TOutline.Adjust(Node: Pointer; Expand: Boolean);
begin
  PNode(Node)^.Expanded := Expand;
end;

function TOutline.GetRoot: Pointer;
begin
  GetRoot := Root;
end;

function TOutline.GetNumChildren(Node: Pointer): Integer;
var
  I: Integer;
  P: PNode;
begin
  P := PNode(Node)^.ChildList;
  I := 0;
  while P <> nil do
  begin
    P := P^.Next;
    Inc(I);
  end;
  GetNumChildren := I;
end;

function TOutline.GetChild(Node: Pointer; I: Integer): Pointer;
var
  P: PNode;
begin
  P := PNode(Node)^.ChildList;
  while (I <> 0) and (P <> nil) do
  begin
    P := P^.Next;
    Dec(I);
  end;
  GetChild := P;
end;

function TOutline.GetText(Node: Pointer): String;
begin
  GetText := PNode(Node)^.Text^;
end;

function TOutline.IsExpanded(Node: Pointer): Boolean;
begin
  IsExpanded := PNode(Node)^.Expanded;
end;

function TOutline.HasChildren(Node: Pointer): Boolean;
begin
  HasChildren := PNode(Node)^.ChildList <> nil;
end;

procedure TOutline.Store(var S: TStream);

  procedure StoreNode(Node: PNode);
  var
    IsNode: Boolean;
  begin
    IsNode := Node <> nil;
    S.Write(IsNode, SizeOf(IsNode));
    if IsNode then
    begin
      with Node^ do
      begin
        S.Write(Expanded, SizeOf(Expanded));
        S.WriteStr(Text);
        StoreNode(ChildList);
        StoreNode(Next);
      end;
    end;
  end;
   
begin
  inherited Store(S);
  StoreNode(Root);
end;

function NewNode(const AText: String; AChildren, ANext: PNode): PNode;
var
  P: PNode;
begin
  New(P);
  with P^ do
  begin
    Text := NewStr(AText);
    Next := ANext;
    ChildList := AChildren;
    Expanded := True;
  end;
  NewNode := P;
end;

procedure DisposeNode(Node: PNode);
begin
  if Node <> nil then
    with Node^ do
    begin
      if ChildList <> nil then DisposeNode(ChildList);
      if Next <> nil then DisposeNode(Next);
    end;
  Dispose(Node);
end;

procedure RegisterOutline;
begin
  RegisterType(ROutline);
end;

end.
