unit XTreeViewu;{v0.62}{TreeView for rendering XML}
{
  (C) 2000 - 2001 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://orgchem.natur.cuni.cz/Chromulan/
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}
interface

uses
  SysUtils, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls,
  QExtCtrls, QComCtrls, XMLu, WinUtl;
{xmlu}

type
  TXEditPanel = TWidgetControl;

  TXTreeView = class(TTreeView)
  private
    { Private declarations }
{    FXMLNode: TXMLNode;
    FGrid: TULStringGrid;
    FPanel: TULObjEditPanel;
    FOwnsObjUsr: boolean;
}
    FEditPanel: TXEditPanel;
      // if non nil, then edit fields for attributes and text will be created
      // in the control (panel or form)
    FXMLNode: TXMLNode; // root element
    FFileName: TFileName;
    FLastNode: TTreeNode;// previous active node
    FModified: boolean; // FXMLNode modified during editing?
    procedure SeTXMLNode(AXMLNode: TXMLNode);
    procedure AddChilds(ANode: TTreeNode);
    procedure RemoveChilds(ANode: TTreeNode);
    procedure SetFileName(const AFileName: TFileName);

    function DoXMLNode(dn: TXMLNode; AOwnerTreeNode: TTreeNode): TTreeNode;
    function TreeNodeAdd(AOwnerNode: TTreeNode; AXMLNode: TXMLNode): TTreeNode;
    procedure TreeViewChange(Sender: TObject; Node: TTreeNode);
    procedure TreeViewExpanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure TreeViewCollapsed(Sender: TObject; Node: TTreeNode);
    procedure UpdateEditPanel(AXMLNode: TXMLNode; var OldNode: TXMLNode);
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); reintroduce;
    destructor Destroy; override;
    procedure Save;

    property FileName: TFileName read FFileName write SetFileName;
{    property OwnsObjUsr: boolean read FOwnsObjUsr write FOwnsObjUsr;
    property Grid: TULStringGrid read FGrid write FGrid;
    property Panel: TULObjEditPanel read FPanel write FPanel;}
    property XMLNode: TXMLNode read FXMLNode write SetXMLNode;
    property EditPanel: TXEditPanel read FEditPanel write FEditPanel;
    property Modified: boolean read FModified;
  end;


procedure TreeNodeUpdateChilds(ANode: TTreeNode);

implementation


function XMLNodeCaption(AXMLNode: TXMLNode): string;
var
  w, l: string;
  i: integer;
begin
//  if AXMLNode.NodeType = ntElement_Node then
  begin
    Result := AXMLNode.Name + ' ';
    if AXMLNode.Attrs.Count > 0 then begin
      for i := 0 to AXMLNode.Attrs.Count - 1 do begin
        Result := Result + AXMLNode.Attrs[i].Name + '="' + AXMLNode.Attrs[i].Value + '" ';
      end;
    end;
    if AXMLNode.Childs.Count = 0 then begin
      l := AXMLNode.Value;
      repeat //winutl
        ExtractWord([#13, #10], w, l);
        if (Trim(w) <> '') or (l = '') then
          break;
      until false;
      Result := Result + w;
    end;
  end;// else
    //Result := '';
end;

function TreeNodeHasChildData(ANode: TTreeNode; AData:pointer): boolean;
var
  i: integer;
begin
  Result := false;
  for i := 0 to ANode.Count - 1 do begin
    if ANode.Item[i].Data = AData then begin
      Result := true;
      exit;
    end;
  end;
end;

procedure TreeNodeUpdateChilds(ANode: TTreeNode);
var
  u, c: TXMLNode;
  i: integer;
  n: TTreeNode;
begin
  u := TXMLNode(ANode.Data);
  i := 0;
  while i < ANode.Count do begin
    c := TXMLNode(ANode.Item[i].Data);
    if u.childs.indexOf(c) < 0 then begin
      ANode.Item[i].Delete;
    end else begin
      inc(i);
    end;
  end;
  for i := 0 to u.childs.Count - 1 do begin
    c := u.childs[i];
    if not TreeNodeHasChildData(ANode, c) then begin
      {
      TXMLNodeTreeView(ANode.TreeView).TreeNodeAdd(ANode, c);}
      n := TTreeView(ANode.TreeView).Items.AddChildObject(ANode, XMLNodeCaption(c), c);
      //if c is TXMLNodeNode then with c as TXMLNodeNode do begin
      //  TreeNodeRegister(n);
      //end;
      n.HasChildren := true;
    end;
  end;
end;


{TXTreeView.}
procedure TXTreeView.SetFileName(const AFileName: TFileName);
var x: TXMLNode;
begin
  x := XMLNodeCreate(nil, nil);
  try
    x.LoadFromFile(AFileName);
    FFileName := AFileName;
    XMLNode := x;
  except
    x.Free;
  end;
end;

function TXTreeView.DoXMLNode(dn: TXMLNode; AOwnerTreeNode: TTreeNode): TTreeNode;
var
  o: TTreeNode;
  i: integer;
  //ats: TDomNamedNodeMap;
  //a: TDomAttr;
  //cel: TDomElement;
begin
  //o := TComponentClass(FindClass('T' + dn.TagName)).Create(AOwner);
  // create treenode o with owner ANode
  o := TreeNodeAdd(AOwnerTreeNode, dn);
  for i := 0 to dn.Childs.Count - 1 do begin
    DoXMLNode(dn.childs[i], o);
  end;
  {
  ats := dn.Attributes;
  for i := 0 to ats.length - 1 do begin
    a := TDomAttr(ats.Item(i));
    ClassSetPropStr(o, a.name, a.value);
  end;
  cel := dn.findFirstChildElement;
  while cel <> nil do begin
    DoElement(cel, o);
    cel := cel.findNextSiblingElement;
  end;
  }
  Result := o;
end;

procedure TXTreeView.SetXMLNode(AXMLNode: TXMLNode);
begin
  {ttreeview}
  if FXMLNode = AXMLNode then
    exit;
  if FXMLNode <> nil then begin
    //if FOwnsXMLNode then
    FXMLNode.Free;
    //if FXMLNode is TDomDocument then
    //  DI.FreeDocument(TDomDocument(FXMLNode));
  end;
  FXMLNode := AXMLNode;
  Items.Clear;
  if FXMLNode <> nil then begin
    TreeNodeAdd(nil, FXMLNode);
    //
    //if FXMLNode is TDomDocument then
    //  TreeNodeAdd(nil, TDomDocument(FXMLNode).documentElement)
    //else
    //  TreeNodeAdd(nil, FXMLNode);
  end;
end;

procedure TXTreeView.AddChilds(ANode: TTreeNode);
var
  AXMLNode: TXMLNode;
  i: integer;
begin
  AXMLNode := TXMLNode(ANode.Data);
  if AXMLNode = nil then
    exit;
  for i := 0 to AXMLNode.Childs.Count - 1 do begin
    if not TreeNodeHasChildData(ANode, AXMLNode.Childs[i]) then
      TreeNodeAdd(ANode, AXMLNode.Childs[i]);
  end;
end;

function TXTreeView.TreeNodeAdd(AOwnerNode: TTreeNode; AXMLNode: TXMLNode): TTreeNode;
begin
  Result := Items.AddChildObject(AOwnerNode, XMLNodeCaption(AXMLNode), AXMLNode);
//  if AXMLNode is TXMLNodeNode then with AXMLNode as TXMLNodeNode do begin
//    TreeNodeRegister(Result);
//  end;
  Result.HasChildren := true;
end;

procedure TXTreeView.RemoveChilds(ANode: TTreeNode);
var
  n: TTreeNode;
  //u: TXMLNode;
begin
  while ANode.Count > 0 do begin
    n := ANode.Item[0];
    RemoveChilds(n);
    //u := TXMLNode(n.Data);
    //if u is TXMLNodeNode then with u as TXMLNodeNode do begin
    //  TreeNodeUnregister(n);
    //end;
    n.Delete;
  end;
end;

constructor TXTreeView.Create(AOwner: TComponent);
begin
  { was in TreeForm:
  FGrid := TULStringGrid.Create(Self);
  FGrid.AutoSizeBrowseCols := true;
  FGrid.Align := alClient;
  FGrid.Left := 350;
  FGrid.Top := 5;
  FGrid.Width := 200;
  FGrid.Height := 400;
  FGrid.Parent := Self;
  }
  inherited Create(AOwner);
  OnChange := TreeViewChange;
  OnCollapsed := TreeViewCollapsed;
  OnExpanding := TreeViewExpanding;
end;

function XMLNodeNameToComponentName(const AName: string): string;
begin
  Result := StringReplace(AName, '#', '', [rfReplaceAll]);
end;

function XMLAttrNameToComponentName(const AName: string): string;
begin
  Result := StringReplace(AName, ':', '_', [rfReplaceAll]);
  Result := StringReplace(AName, '-', '_', [rfReplaceAll]);
end;

procedure XMLNodeToXEditPanel(ANode: TXMLNode; APanel: TXEditPanel);
var
  n: TXMLNode;
  i: integer;
  l, t, h: integer;
begin
  APanel.DestroyComponents;
  case ANode.NodeType of
    ntText_Node: begin
      with TMemo.Create(APanel) do begin
        Name := XMLNodeNameToComponentName(ANode.Name);
        Align := alClient;
        Parent := APanel;
        Text := ANode.Value;
        Modified := false;
      end;
    end;
    ntElement_Node: begin
      t := 4;
      l := 80;
      for i := 0 to ANode.Attrs.Count - 1 do begin
        n := ANode.Attrs.Nodes[i];
        with TEdit.Create(APanel) do begin
          Parent := APanel;
          Name := XMLAttrNameToComponentName(n.Name);
          Top := t;
          Left := l;
          Width := APanel.Width - l - 4;
          Anchors := [akLeft, akTop, akRight];
          Text := n.Value;
          h := Height;
          Modified := false;
        end;
        with TLabel.Create(APanel) do begin
          Left := 4;
          Parent := APanel;
          Caption := n.Name;
          Top := t;
        end;

        inc(t, h + 1);
      end;
    end;
  end;
end;

procedure XEditPanelToXMLNode(APanel: TXEditPanel; ANode: TXMLNode; var AModified: boolean);
var
  c: TComponent;
  i: integer;
  n: TXMLNode;
begin
  case ANode.NodeType of
    ntText_Node: begin
      c := APanel.FindComponent(XMLNodeNameToComponentName(ANode.Name));
      if c is TMemo then begin
        if (TMemo(c).Modified) and (ANode.Value <> TMemo(c).Text) then begin
          ANode.Value := TMemo(c).Text;
          AModified := true;
        end;
      end;
    end;
    ntElement_Node: begin
      for i := 0 to ANode.Attrs.Count - 1 do begin
        n := ANode.Attrs.Nodes[i];
        c := APanel.FindComponent(XMLAttrNameToComponentName(n.Name));
        if c is TEdit then begin
          if TEdit(c).Modified and (n.Value <> TEdit(c).Text) then begin
            AModified := true;
            n.Value := TEdit(c).Text;
          end;
        end;
      end;
    end;
  end;
end;

procedure XEditPanelUpdate(APanel: TXEditPanel; ANode: TXMLNode; var OldNode: TXMLNode; var AModified: boolean);
begin
  if APanel = nil then
    exit;
  if APanel.Tag = integer(ANode) then
    exit;
  OldNode := TXMLNode(APanel.Tag);
  if APanel.Tag <> 0 then
    XEditPanelToXMLNode(APanel, TXMLNode(APanel.Tag), AModified);
  APanel.Tag := integer(ANode);
  if APanel.Tag <> 0 then
    XMLNodeToXEditPanel(ANode, APanel);
end;

procedure TXTreeView.UpdateEditPanel(AXMLNode: TXMLNode; var OldNode: TXMLNode);
begin
  XEditPanelUpdate(FEditPanel, AXMLNode, OldNode, FModified);
end;

procedure TXTreeView.TreeViewChange(Sender: TObject;
  Node: TTreeNode);
var
  onode: TXMLNode;
begin
  UpdateEditPanel(TXMLNode(Node.Data), onode);
  if FLastNode <> nil then begin
    if TXMLNode(FLastNode.Data) = onode then begin
      FLastNode.Text := XMLNodeCaption(onode);
    end;
  end;
  FLastNode := Node;
//  if FGrid <> nil then
//    FGrid.Obj := TXMLNode(Node.Data).Obj;
//  if FPanel <> nil then
//    FPanel.Obj := TXMLNode(Node.Data).Obj;
end;

destructor TXTreeView.Destroy;
begin
  XMLNode := nil;
  inherited;
end;

procedure TXTreeView.TreeViewExpanding(Sender: TObject;
  Node: TTreeNode; var AllowExpansion: Boolean);
begin
  AddChilds(Node);
end;

procedure TXTreeView.TreeViewCollapsed(Sender: TObject;
  Node: TTreeNode); {ttreeview}
begin
  RemoveChilds(Node);
  Node.HasChildren := true;
end;

procedure TXTreeView.Save;
begin
  FXMLNode.SaveToFile(FileName);
end;

end.
