unit XTreeView2;{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, Types, Classes, QGraphics, QControls, QForms, QDialogs,
  QStdCtrls, QActnList, QMenus, QTypes, QComCtrls, QExtCtrls, Qt,
  xdom_2_3;
{xmlu}

type
  TXTreeView = class;
  TXEditor = class;

  TXEdit = class(TEdit)
  private
    function GetXTreeView: TXTreeView;
    function GetEditor: TXEditor;
  protected
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  public
    property XTreeView: TXTreeView read GetXTreeView;
    property Editor: TXEditor read GetEditor;
  end;

  TXMemo = class(TMemo)
  private
    function GetXTreeView: TXTreeView;
    function GetEditor: TXEditor;
  protected
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  public
    property XTreeView: TXTreeView read GetXTreeView;
    property Editor: TXEditor read GetEditor;
  end;

  TXEditor = class( TPanel )
  private
    FEdit: TEdit;
    FMemo: TMemo;
    FXTreeView: TXTreeView;
    FNode: TTreeNode;
    FOrigText: string;
    FLastKey: integer;
    procedure SetNode(ANode: TTreeNode);
    procedure SetText(const AText: string);
    function GetText: string;
    procedure SetEditorVisible(OnOff: boolean);
    function GetNodeType: TDomNodeType;
  protected
    procedure DoExit; override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure Cancel;
    property LastKey: integer read FLastKey write FLastKey;
  public
    constructor Create(AOwner: TComponent); override;
    property Node: TTreeNode read FNode write SetNode;
    property Text: string read GetText write SetText;
    property EditorVisible: boolean write SetEditorVisible;
    property NodeType: TDomNodeType read GetNodeType;
  end;

  TXTreeView = class(TTreeView)
  private
    { Private declarations }
{    FDomNode: TDomNode;
    FGrid: TULStringGrid;
    FPanel: TULObjEditPanel;
    FOwnsObjUsr: boolean;
}
    FDomNode: TDomNode; // root element
    FFileName: TFileName;
    FEditor: TXEditor;
    procedure SetDomNode(ADomNode: TDomNode);
    procedure AddChilds(ANode: TTreeNode);
    procedure RemoveChilds(ANode: TTreeNode);
    procedure SetFileName(const AFileName: TFileName);

    function DoDomNode(dn: TDomNode; AOwnerTreeNode: TTreeNode): TTreeNode;
    function TreeNodeAdd(AOwnerNode: TTreeNode; ADomNode: TDomNode): TTreeNode;
    procedure TreeViewChange(Sender: TObject; Node: TTreeNode);
    procedure TreeViewExpanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure TreeViewCollapsed(Sender: TObject; Node: TTreeNode);
  protected
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure EndEdit;
    procedure CancelEdit;
    procedure Edit;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); reintroduce;
    destructor Destroy; override;

    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 DomNode: TDomNode read FDomNode write SetDomNode;
  end;

procedure TreeNodeUpdateChilds(ANode: TTreeNode);

implementation

var
  DI: TDomImplementation;

function DomNodeCaption(ADomNode: TDomNode): string;
var
  // xdom_2_3
  ats: TdomNamedNodeMap;
  a: TDomAttr;
  i: integer;
begin
  if ADomNode is TDomElement then begin
    with TDomElement(ADomNode) do begin
      Result := tagName;
      if hasAttributes then begin
        ats := Attributes;
        for i := 0 to ats.length - 1 do begin
          a := TDomAttr(ats.Item(i));
          Result := Result + ' ' + a.name + '="' + a.value + '"';
        end;
      end;
    end;
  end else if ADomNode is TDomText then begin
    Result := TDomText(ADomNode).Data;
  end else begin
    Result := ADomNode.nodeName + ' ' + ADomNode.nodeValue;
  end;
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: TDomNode;
  i: integer;
  n: TTreeNode;
begin
  u := TDomNode(ANode.Data);
  i := 0;
  while i < ANode.Count do begin
    c := TDomNode(ANode.Item[i].Data);
    if u.childNodes.indexOf(c) < 0 then begin
      ANode.Item[i].Delete;
    end else begin
      inc(i);
    end;
  end;
  for i := 0 to u.childNodes.length - 1 do begin
    c := u.childNodes.item(i);
    if not TreeNodeHasChildData(ANode, c) then begin
      {
      TDomNodeTreeView(ANode.TreeView).TreeNodeAdd(ANode, c);}
      n := TTreeView(ANode.TreeView).Items.AddChildObject(ANode, DomNodeCaption(c), c);
      //if c is TDomNodeNode then with c as TDomNodeNode do begin
      //  TreeNodeRegister(n);
      //end;
      n.HasChildren := true;
    end;
  end;
end;

{TXEdit.}
function TXEdit.GetEditor: TXEditor;
begin
  Result := TXEditor(Owner);
end;

function TXEdit.GetXTreeView: TXTreeView;
begin
  Result := Editor.FXTreeView;
end;

procedure TXEdit.KeyDown(var Key: Word; Shift: TShiftState);
begin
  Editor.LastKey := Key;
  case Key of
    Key_Return, Key_Up, Key_Down: XTreeView.EndEdit;
    Key_Escape: XTreeView.CancelEdit;
  else
    inherited;
    exit;
  end;
  Key := 0;
end;
{/TXEdit.}

{TXMemo.}
function TXMemo.GetEditor: TXEditor;
begin
  Result := TXEditor(Owner);
end;

function TXMemo.GetXTreeView: TXTreeView;
begin
  Result := Editor.FXTreeView;
end;

procedure TXMemo.KeyDown(var Key: Word; Shift: TShiftState);
begin
  Editor.LastKey := Key;
  case Key of
    //Key_Return, Key_Up, Key_Down: XTreeView.EndEdit;
    Key_Escape: XTreeView.CancelEdit;
  else
    inherited;
    exit;
  end;
  Key := 0;
end;
{/TXMemo.}

{TXEditor.}
constructor TXEditor.Create(AOwner: TComponent);
begin
  inherited;
  Visible := false;
  Parent := TWidgetControl(AOwner);

  FEdit := TXEdit.Create(Self);
  FEdit.Parent := Self;
  FEdit.Align := alClient;
  FEdit.Visible := false;

  FMemo := TXMemo.Create(Self);
  FMemo.Parent := Self;
  FMemo.Align := alClient;
  FMemo.Visible := false;

  if AOwner is TXTreeView then begin
    FXTreeView := TXTreeView(AOwner)
  end else begin
    raise Exception.Create('Owner of TXEditor must be TXTreeView');
  end;
end;

procedure TXEditor.SetNode(ANode: TTreeNode);
var
  r: TRect;
  h: integer;
begin                       //ttreeview
  if ANode = FNode then
    exit;

  if FNode <> nil then begin
    FNode.Text := Text;

    if ANode = nil then begin
      case FLastKey of
        Key_Return, Key_Down: begin
          FXTreeView.Selected := FNode.GetNext;
        end;
        Key_Up: begin
          FXTreeView.Selected := FNode.GetPrev;
        end;
        //ttreenode enabled
      end;
      Text := '';
      EditorVisible := false;
    end;
  end;

  FNode := ANode;

  if FNode <> nil then begin
    Text := FNode.Text;
    r := FNode.DisplayRect;
    if r.Left = 0 then begin
      if FXTreeView.Owner is TForm then begin
        r.Left := (FNode.Level + 1) * TForm(FXTreeView.Owner).Canvas.TextWidth('+++');
      end;
    end;
    h := r.Bottom - r.Top;
    case NodeType of
      ntText_Node: r.Bottom := r.Bottom + 4 * h;
    end;
    r.Bottom := r.Bottom + round(h/10);
    BoundsRect := r;
    EditorVisible := true;
  end;
end;

procedure TXEditor.DoExit; //ttreeview tedit
begin
  Node := nil;
end;

procedure TXEditor.Cancel;
begin
  if FNode <> nil then begin
    Text := FOrigText;
    Node := nil;
  end;
end;

procedure TXEditor.KeyDown(var Key: Word; Shift: TShiftState);
begin//qt
  FLastKey := Key;
  case Key of
    Key_Up, Key_Return, Key_Down: begin
      Key := 0;
      FXTreeView.SetFocus;
    end;
    Key_Escape: begin
      Key := 0;
      Node := nil;
      FXTreeView.SetFocus;
    end;
  end;
end;

procedure TXEditor.SetText(const AText: string);
begin
  if FNode = nil then
    exit;
  FOrigText := AText;
  case NodeType of
    ntElement_Node: FEdit.Text := AText;
    ntText_Node: FMemo.Text := AText;
  end;
end;

function TXEditor.GetText: string;
begin
  if FNode = nil then
    Result := ''
  else begin
    case NodeType of
      ntElement_Node: Result := FEdit.Text;
      ntText_Node: Result := FMemo.Text;
    end;
  end;
end;

procedure TXEditor.SetEditorVisible(OnOff: boolean);
var
  c: TWidgetControl;
begin
  if OnOff then begin

    c := nil;
    if (FNode <> nil) then begin

      case NodeType of
        ntElement_Node: begin
          c := FEdit;
        end;
        ntText_Node: begin
          c := FMemo;
        end;
      end;

      if c <> nil then begin
        c.Visible := true;
        Visible := true;
        c.SetFocus;
      end;
    end;

    OnOff := c <> nil;
  end;

  if not OnOff then begin
    FEdit.Visible := false;
    FMemo.Visible := false;
    Visible := false;
  end;
end;

function TXEditor.GetNodeType: TDomNodeType;
begin
  if FNode = nil then
    Result := ntUnknown
  else
    Result := TDomNode(FNode.Data).nodeType;
end;
{/TXEditor.}

{TXTreeView.}

procedure TXTreeView.SetFileName(const AFileName: TFileName);
var
  xp: TXmlToDomParser;
begin
  if DI = nil then
    DI := TDomImplementation.Create(nil);
  xp := TXmlToDomParser.create(nil);
  try
    xp.domImpl := DI;
    try
      DomNode := xp.FileToDom(AFileName);
      {AComponent := }
      //DoDomNode(FDomDocument.DocumentElement, nil);//ttreeview
    finally
    end;
  finally
    xp.Free;
  end;
end;

function TXTreeView.DoDomNode(dn: TDomNode; AOwnerTreeNode: TTreeNode): TTreeNode;
var
  o, c: 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.childNodes.length - 1 do begin
    DoDomNode(dn.childNodes.item(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.SetDomNode(ADomNode: TDomNode);
begin
  {ttreeview}
  if FDomNode = ADomNode then
    exit;
  if FDomNode <> nil then begin
    //if FOwnsDomNode then
    if FDomNode is TDomDocument then
      DI.FreeDocument(TDomDocument(FDomNode));
  end;
  FDomNode := ADomNode;
  Items.Clear;
  if FDomNode <> nil then begin
    if FDomNode is TDomDocument then
      TreeNodeAdd(nil, TDomDocument(FDomNode).documentElement)
    else
      TreeNodeAdd(nil, FDomNode);
  end;
end;

procedure TXTreeView.AddChilds(ANode: TTreeNode);
var
  ADomNode: TDomNode;
  i: integer;
begin
  ADomNode := TDomNode(ANode.Data);
  for i := 0 to ADomNode.childNodes.length - 1 do begin
    if not TreeNodeHasChildData(ANode, ADomNode.childNodes.item(i)) then
      TreeNodeAdd(ANode, ADomNode.childNodes.item(i));
  end;
end;

function TXTreeView.TreeNodeAdd(AOwnerNode: TTreeNode; ADomNode: TDomNode): TTreeNode;
begin
  Result := Items.AddChildObject(AOwnerNode, DomNodeCaption(ADomNode), ADomNode);
//  if ADomNode is TDomNodeNode then with ADomNode as TDomNodeNode do begin
//    TreeNodeRegister(Result);
//  end;
  Result.HasChildren := true;
end;

procedure TXTreeView.RemoveChilds(ANode: TTreeNode);
var
  n: TTreeNode;
  u: TDomNode;
begin
  while ANode.Count > 0 do begin
    n := ANode.Item[0];
    RemoveChilds(n);
    u := TDomNode(n.Data);
    //if u is TDomNodeNode then with u as TDomNodeNode 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;

procedure TXTreeView.TreeViewChange(Sender: TObject;
  Node: TTreeNode);
begin
//  if FGrid <> nil then
//    FGrid.Obj := TDomNode(Node.Data).Obj;
//  if FPanel <> nil then
//    FPanel.Obj := TDomNode(Node.Data).Obj;
end;

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

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

procedure TXTreeView.EndEdit;
begin
  if FEditor <> nil then
    FEditor.Node := nil;
  SetFocus;
end;

procedure TXTreeView.CancelEdit;
begin
  if FEditor <> nil then begin
    FEditor.Cancel;
  end;
  SetFocus;
end;

procedure TXTreeView.Edit;
begin
  if Selected = nil then
    exit;
  if FEditor = nil then begin
    FEditor := TXEditor.Create(Self);
  end;
  FEditor.Node := Selected;
end;

procedure TXTreeView.KeyDown(var Key: Word; Shift: TShiftState);
var
  r: TRect;
  e: TEdit;
begin
  if Key = Key_Return then begin
    Edit;
  end;
end;
procedure TXTreeView.TreeViewCollapsed(Sender: TObject;
  Node: TTreeNode); {ttreeview}
begin
  RemoveChilds(Node);
  Node.HasChildren := true;
end;
{/TXTreeView.}
initialization
  DI := nil;
finalization
  DI.Free;
end.
