[Example] Document structure as a tree

Demos, code samples. Only questions related to the existing topics are allowed here.
Post Reply
Sergey Tkachenko
Site Admin
Posts: 13026
Joined: Sat Aug 27, 2005 10:28 am
Contact:

[Example] Document structure as a tree

Post by Sergey Tkachenko » Mon Jan 04, 2010 9:18 pm

The code below shows a document structure in TreeView.

The first level of this structure is "Document".

The second level: paragraphs (in TRichView, paragraphs are not represented as objects; items simply have "new paragraph" flags; but for the convenience of displaying a structure, paragraphs are shown as a structure level). For paragraphs, the following properties are shown:
- Outline level (if positive, it is named 'Heading level N' instead of 'Paragraph')
- ParaNo (index of paragraph style)
- page break (if this paragraph has "page break before" flag)
- text flow properties (clear left/right/both, if specified)

The third level: items. For text items, their text is shown. For non-text items, class of their items is shown. Index of text style/item type (StyleNo) is shown.
If the item is a hyperlink, or it has a non-empty tag, this information is shown.

If some item starts a line break inside a paragraph, "line break" is added before this item.

The fourth level: checkpoint (if the item has it). A name is displayed for the checkpoint.

Special cases:
1) Tables. For tables, the fourth level is rows, the fifth level is cells. Each cell is a document with its own structure levels.
2) Footnotes and endnotes. For notes, the fourth level is "Note text". This is a document with its own structure levels.
3) List markers. Information about list marker is shown in the fourth level.

Code: Select all

procedure ShowStructure(RVData: TCustomRVData; Node: TTreeNode); 
var i, StyleNo: Integer; 
    ParaNode, ItemNode: TTreeNode; 

    function GetExtraBreakProps(i: Integer): String; 
    begin 
      if RVData.PageBreaksBeforeItems[i] then begin 
        Result := '; page break'; 
        exit; 
      end;
      if RVData.ClearLeft[i] then 
        if RVData.ClearRight[i] then 
          Result := '; clear both' 
        else 
          Result := '; clear left' 
      else 
        if RVData.ClearRight[i] then 
          Result := '; clear right' 
        else 
          Result := ''; 
    end; 

    function GetExtraItemString(i: Integer): String; 
    begin 
      Result := ''; 
      if RVData.GetItem(i).GetBoolValueEx(rvbpJump, RVData.GetRVStyle) then 
        Result := '; hyperlink';
      if RVData.GetItemTag(i)<>'' then 
          Result := Result+ '; tag: "'+RVData.GetItemTag(i)+'"';
    end; 

    procedure AddCheckpoint(i: Integer); 
    var CPTag: TRVTag; 
        CPName: String; 
        CPRE: Boolean; 
    begin 
      if RVData.GetItemCheckpoint(i)<>nil then begin 
        RVData.GetCheckpointInfo(RVData.GetItemCheckpoint(i), CPTag, CPName, CPRE); 
        Node.Owner.AddChild(ItemNode, Format('Checkpoint "%s"', 
          [CPName])); 
      end; 
    end;

    procedure AddTableInfo(i: Integer); 
    var r, c: Integer; 
      table: TRVTableItemInfo; 
      RowNode, CellNode: TTreeNode; 
    begin 
      table := TRVTableItemInfo(RVData.GetItem(i)); 
      for r := 0 to table.RowCount-1 do begin 
        RowNode := Node.Owner.AddChild(ItemNode, Format('Row %d', [r])); 
        for c := 0 to table.ColCount-1 do begin 
          if table.Cells[r,c]<>nil then begin 
            CellNode := Node.Owner.AddChild(RowNode, Format('Cell %d', [c])); 
            ShowStructure(table.Cells[r,c].GetRVData, CellNode); 
          end; 
        end; 
      end 
    end;

    procedure AddListMarkerInfo(i: Integer); 
    var ListNo, Level, StartFrom: Integer; 
       UseStartFrom: Boolean; 
       s: String; 
    begin 
      RVData.GetListMarkerInfo(i, ListNo, Level, StartFrom, UseStartFrom); 
      s := Format('ListNo=%d, Level=%d',[ListNo, Level]); 
      if UseStartFrom then 
        s := s+Format(', start from %d', [StartFrom]); 
      Node.Owner.AddChild(ItemNode, s);
    end;

    function GetParaCaption(i: Integer): String;
    var Level: Integer;
    begin
      Level := RVData.GetRVStyle.ParaStyles[RVData.GetItemPara(i)].OutlineLevel;
      if Level<=0 then
        Result := 'Paragraph'
      else
        Result := Format('Heading level %d', [Level]);
    end;

begin
  ParaNode := nil;
  for i := 0 to RVData.ItemCount-1 do begin
    if RVData.IsParaStart(i) then
      ParaNode := Node.Owner.AddChild(Node, Format('%s, ParaNo=%d%s',
        [GetParaCaption(i), RVData.GetItemPara(i), GetExtraBreakProps(i)]))
    else if RVData.IsFromNewLine(i) then
      Node.Owner.AddChild(ParaNode, Format('Line break%s',
        [GetExtraBreakProps(i)]));
    StyleNo := RVData.GetItemStyle(i);
    if StyleNo>=0 then
      ItemNode := Node.Owner.AddChild(ParaNode, Format('Text "%s", StyleNo=%d%s',
        [RVData.GetItemText(i), StyleNo, GetExtraItemString(i)]))
    else begin
      ItemNode := Node.Owner.AddChild(ParaNode, Format('%s [StyleNo=%d]%s',
        [RVData.GetItem(i).ClassName, StyleNo, GetExtraItemString(i)]));
      if RVData.GetItem(i) is TRVTableItemInfo then
        AddTableInfo(i)
      else if RVData.GetItem(i) is TCustomRVNoteItemInfo then
        ShowStructure(TCustomRVNoteItemInfo(RVData.GetItem(i)).Document,
          Node.Owner.AddChild(ItemNode, 'Note text'))
      else if RVData.GetItem(i) is TRVMarkerItemInfo then
        AddListMarkerInfo(i);
    end; 
    AddCheckpoint(i); 
  end; 
end;
How to use:

Code: Select all

  TreeView1.Items.BeginUpdate;
  TreeView1.Items.Clear;
  TreeView1.Items.AddChildFirst(nil, 'Document');
  ShowStructure(RichViewEdit1.RVData, TreeView1.Items[0]);
  TreeView1.Items.EndUpdate;
Updates:
2011-Mar-31: displaying paragraph outline level
2011-Oct-2: for compatibility with TRichView 13.4
Last edited by Sergey Tkachenko on Sun Oct 02, 2011 7:57 pm, edited 2 times in total.

jonjon
Posts: 268
Joined: Sat Aug 27, 2005 4:19 pm

Post by jonjon » Sun Apr 25, 2010 9:16 am

Some additions to highlight the correct parts:

- In ShowStructure, instead of AddChild use AddChildObject. Example:

Code: Select all

// Node.Owner.AddChild(ItemNode, s);
Node.Owner.AddChildObject(ItemNode, s, Pointer(i));
- On mouse over the TreeView:

Code: Select all

procedure TForm1.TreeView1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  OverItem: TTreeNode;
  RVItemNb: Integer;
begin
  OverItem := TreeView1.GetNodeAt(X, Y);
  if (assigned(OverItem)) then
  begin
    RVItemNb := Integer(OverItem.Data);
    StatusBar1.SimpleText := 'Current Item #' + IntToStr(RVItemNb);
    Self.SelectRVItem(RVItemNb);
  end;
end;
- On mouse over the TRichViewEdit:

Code: Select all

procedure TForm1.RichViewEdit1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  RVData: TCustomRVFormattedData;
  ItemNo, OffsetInItem: integer;
  I: Integer;
begin
  if RichViewEdit1.GetItemAt(X, Y, RVData, ItemNo, OffsetInItem, True) then
  begin
    Self.SelectRVItem(ItemNo);
    GetNodeByData(TreeView1, ItemNo, True);
  end;
end;
- Helper functions:

Code: Select all

function TForm1.GetNodeByData(ATree : TTreeView; AData: Integer; AVisible: Boolean): TTreeNode;
var
  Node: TTreeNode;
begin
  Result := nil;
  if ATree.Items.Count = 0 then Exit;
  Node := ATree.Items[0];
  while Node <> nil do
  begin
    if Integer(Node.Data) = AData then
    begin
      Result := Node;
      if AVisible then
      begin
        Result.MakeVisible;
        Result.Selected := True;
        ATree.SetFocus;
      end;
      Break;
    end;
    Node := Node.GetNext;
  end;
end;

procedure TForm1.SelectRVItem(anItem: integer);
var
  Offs1, Offs2: integer;
begin
  Offs1 := RichViewEdit1.RVData.GetOffsBeforeItem(anItem);
  Offs2 := RichViewEdit1.RVData.GetOffsAfterItem(anItem);
  RichViewEdit1.RVData.SetSelectionBounds(anItem, Offs1, anItem, Offs2);
  RichViewEdit1.RVData.Invalidate;
end;

allanj42
Posts: 10
Joined: Mon Mar 02, 2009 11:56 pm
Location: Canada

C++ Version?

Post by allanj42 » Tue Feb 08, 2011 5:21 pm

Is there a C++ version of the document tree utility, preferably with the interactive enhancements?

Sergey Tkachenko
Site Admin
Posts: 13026
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko » Tue Feb 08, 2011 7:01 pm

The simplest way is creating a new pas-unit, copying this procedure to this unit, and including this pas-unit in C++project.
HPP-file will be generated on compiling.

Petko
Posts: 163
Joined: Tue Sep 06, 2005 12:42 pm

Post by Petko » Tue Mar 29, 2011 2:54 pm

Sergey, does this utility show paragraph outline levels?

Sergey Tkachenko
Site Admin
Posts: 13026
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko » Thu Mar 31, 2011 5:21 pm

I updated the code to show them.

Petko
Posts: 163
Joined: Tue Sep 06, 2005 12:42 pm

Post by Petko » Fri Apr 01, 2011 2:05 pm

Thanks!

Rael Bauer
Posts: 34
Joined: Tue Aug 21, 2007 4:47 am

Post by Rael Bauer » Wed Jan 27, 2016 2:51 am

I would like to implement something similar to JonJon, i.e. when a node is selected or mouse over then select that part of the document, and vice versa.

However, his code breaks when there are tables in the document.

Is this possible?

Thanks

Sergey Tkachenko
Site Admin
Posts: 13026
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko » Thu Jan 28, 2016 2:33 pm

I'll try to make the demo, but currently we are busy trying to release a new TRichView-based reporting components (Report Workshop) as soon as possible, so it may take some time.

Rael Bauer
Posts: 34
Joined: Tue Aug 21, 2007 4:47 am

Post by Rael Bauer » Thu Jan 28, 2016 2:47 pm

ok thanks. I can wait until then.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest