{ *************************************************************************** }
{                                                                             }
{ EControl Syntax Editor SDK                                                  }
{                                                                             }
{ Copyright (c) 2004 - 2008 EControl Ltd., Zaharov Michael                    }
{     www.econtrol.ru                                                         }
{     support@econtrol.ru                                                     }
{                                                                             }
{ *************************************************************************** }
{$I ecSyntEdit.INC}

unit ecSyntTree;

interface

uses Windows, Messages, Classes, Controls, ComCtrls, Forms, ExtCtrls,
     {$IFDEF EC_DOTNET}Types,{$ENDIF}
     ecSyntAnal, ecSyntMemo, ecLists, ecStrUtils;

type
  // Object assigned to TreeNode.Data
  TSyntNodeRange = class(TRange)
  protected
    FNode: TTreeNode;
    FStyleIdx: integer;
    FChildren: TzStringList;
  protected
    procedure AddChild(ANode: TTReeNode);
  public
    constructor Create(AStartPos, AEndPos: integer; ANode: TTreeNode; AStyleIdx: integer);
    destructor Destroy; override;

    function FindChild(const ChdName: string): TTreeNode;

    property Node: TTreeNode read FNode;
    property StyleIdx: integer read FStyleIdx;
  end;

  TSyntaxTreeAction = (staNone, staDefault, staMoveCaret, staSelect);
  TCreateTreeNodeEvent = procedure(Sender: TObject; Node: TTreeNode; Range: TTextRange) of object;
  TTreeLoadProgressEvent = procedure(Sender: TObject; Current, Total: integer) of object;
  TAcceptRangeEvent = procedure(Sender: TObject; Range: TTextRange; var Accept: Boolean) of object;
  TSelectParentNodeEvent = procedure(Sender: TObject; Range: TTextRange; var Node: TTreeNode) of object;

  TCustomSyntaxTreeView = class(TCustomTreeView, {$IFNDEF EC_DOTNET}IUnknown,{$ENDIF}IecTextClient, IecSyntMemoClient, IecSyntClient)
  private
    FSyntaxMemo: TCustomSyntaxMemo;
    FClickAction: TSyntaxTreeAction;
    FOnCreateTreeNode: TCreateTreeNodeEvent;
    FBreakUpdate: Boolean;
    FUpdating: Boolean;
    FRepeatUpdate: Boolean;
    FAutoSynchronize: Boolean;
    FRootNodes: TzStringList;
    FGroupIndex: integer;
    FNodeRanges: TRangeCollection;
    FChanges: TChangeFixer;
    FOpenedRanges: TList;
    FStyles: TList;
    FUpdateTimer: TTimer;
    FExpItems: TList;
    FSelNodeStrt: integer;
    FTopItemStrt: integer;
    FSavedStCount: integer;
    FAutoCollapse: Boolean;
    FNullWidth: Boolean;
    FOnTreeUpdated: TNotifyEvent;
    FOnLoadProgress: TTreeLoadProgressEvent;
    FOnAcceptRange: TAcceptRangeEvent;
    FMaxLevels: integer;
    FOnCreateGroupNode: TCreateTreeNodeEvent;
    FOnSelectParentNode: TSelectParentNodeEvent;
    procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
    procedure SetSyntaxMemo(const Value: TCustomSyntaxMemo);
    procedure SetGroupIndex(const Value: integer);
    procedure OnUpdateTimer(Sender: TObject);
    function GetNodeRange(Node: TTreeNode): TTextRange;
    function GetDragRanges(Source: TObject; X, Y: Integer; var R1, R2: TTextRange): Boolean;
    procedure SaveState;
    procedure RestoreState;
    procedure SetAutoCollapse(const Value: Boolean);
    procedure SetAutoSynchronize(const Value: Boolean);
    procedure SetMaxLevels(const Value: integer);
  private
    // IecSyntClient
    procedure FormatChanged;
    procedure Finished;
    // IecSyntMemoClient
    procedure TextChanged(Sender: TObject; Pos, Count, LineChange: integer);
    function CaretPosChanged: Boolean;
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure UpdateTree(const Synt: TClientSyntAnalyzer; Breakable: Boolean = True); overload;
    procedure WndProc(var Message: TMessage); override;
    procedure DblClick; override;
    procedure Resize; override;
{$IFNDEF EC_VCL5}
    function IsCustomDrawn(Target: TCustomDrawTarget; Stage: TCustomDrawStage): Boolean; override;
{$ELSE}
    procedure PureCustomDrawn(Sender: TCustomTreeView; Node: TTreeNode;
    State: TCustomDrawState; var DefaultDraw: Boolean);
{$ENDIF}
    function CustomDrawItem(Node: TTreeNode; State: TCustomDrawState;
      Stage: TCustomDrawStage; var PaintImages: Boolean): Boolean; override;
    procedure DragOver(Source: TObject; X: Integer; Y: Integer;
      State: TDragState; var Accept: Boolean); override;
    procedure DestroyWnd; override;
    procedure CreateWnd; override;
    procedure DoTreeUpdated; virtual;
    procedure DoCreateTreeNode(Node: TTreeNode; Range: TTextRange); virtual;
    procedure DoCreateGroupNode(Node: TTreeNode; Range: TTextRange); virtual;
    procedure DoSelectParentNode(var Node: TTreeNode; Range: TTextRange); virtual;
    function CanAcceptRange(Range: TTextRange): Boolean; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Clear;
    procedure UpdateTree; overload;
    property Items;
    procedure DragDrop(Source: TObject; X: Integer; Y: Integer); override;
    function AttachNodeData(Node: TTreeNode; StartPos, EndPos, StyleIdx: integer): TSyntNodeRange;
    function FindChild(Prn: TTreeNode; const Name: string): TTreeNode;
    function AddNode(Prn: TTreeNode; const Text: string; ImageIdx: integer = -1): TTreeNode;
  protected
    property SyntaxMemo: TCustomSyntaxMemo read FSyntaxMemo write SetSyntaxMemo;
    property ClickAction: TSyntaxTreeAction read FClickAction write FClickAction default staSelect;
    property AutoSynchronize: Boolean read FAutoSynchronize write SetAutoSynchronize;
    property GroupIndex: integer read FGroupIndex write SetGroupIndex default 0;
    property AutoCollapse: Boolean read FAutoCollapse write SetAutoCollapse default False;
    property MaxLevels: integer read FMaxLevels write SetMaxLevels default 0;
    property OnTreeUpdated: TNotifyEvent read FOnTreeUpdated write FOnTreeUpdated;
    property OnLoadProgress: TTreeLoadProgressEvent read FOnLoadProgress write FOnLoadProgress;
    property OnAcceptRange: TAcceptRangeEvent read FOnAcceptRange write FOnAcceptRange;
    property OnCreateTreeNode: TCreateTreeNodeEvent read FOnCreateTreeNode write FOnCreateTreeNode;
    property OnCreateGroupNode: TCreateTreeNodeEvent read FOnCreateGroupNode write FOnCreateGroupNode;
    property OnSelectParentNode: TSelectParentNodeEvent read FOnSelectParentNode write FOnSelectParentNode;
  end;

  TSyntaxTreeView = class(TCustomSyntaxTreeView)
  published
    property SyntaxMemo;
    property ClickAction;
    property AutoSynchronize;
    property GroupIndex;
    property AutoCollapse;
    property MaxLevels;
    property OnTreeUpdated;
    property OnAcceptRange;
    property OnLoadProgress;
    property OnCreateTreeNode;
    property OnCreateGroupNode;
    property OnSelectParentNode;

    property Align;
    property Anchors;
    property AutoExpand;
    property BevelEdges;
    property BevelInner;
    property BevelOuter;
    property BevelKind default bkNone;
    property BevelWidth;
    property BiDiMode;
    property BorderStyle;
    property BorderWidth;
    property ChangeDelay;
    property Color;
    property Ctl3D;
    property Constraints;
    property DragKind;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property HideSelection default False;
    property HotTrack;
    property Images;
    property Indent;
{$IFDEF EC_VCL6_UP}
    property MultiSelect;
    property MultiSelectStyle;
{$ENDIF}
    property ParentBiDiMode;
    property ParentColor default False;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ReadOnly default True;
    property RightClickSelect;
    property RowSelect;
    property ShowButtons;
    property ShowHint;
    property ShowLines;
    property ShowRoot;
    property SortType;
    property StateImages;
    property TabOrder;
    property TabStop default True;
    property ToolTips;
    property Visible;
{$IFDEF EC_VCL6_UP}
    property OnAddition;
{$ENDIF}
    property OnAdvancedCustomDraw;
    property OnAdvancedCustomDrawItem;
    property OnChange;
    property OnChanging;
    property OnClick;
    property OnCollapsed;
    property OnCollapsing;
    property OnCompare;
    property OnContextPopup;
{$IFDEF EC_VCL6_UP}
    property OnCreateNodeClass;
{$ENDIF}
    property OnCustomDraw;
{$IFNDEF EC_VCL5}
    property OnCustomDrawItem; // Used to make IsCustomDrawn = True
    property OnCancelEdit;
{$ENDIF}
    property OnDblClick;
    property OnDeletion;
    property OnDragDrop;
    property OnDragOver;
    property OnEdited;
    property OnEditing;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnExpanding;
    property OnExpanded;
    property OnGetImageIndex;
    property OnGetSelectedIndex;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartDock;
    property OnStartDrag;
    property OnConstrainedResize;
    property OnCanResize;
    property OnResize;
  end;

implementation

uses SysUtils, contnrs, Graphics, ecSysUtils, Math, CommCtrl{$IFNDEF EC_VCL5}, StrUtils{$ENDIF};

{ TSyntNodeRange }

procedure TSyntNodeRange.AddChild(ANode: TTReeNode);
begin
  FChildren.AddObject(ANode.Text, ANode);
end;

constructor TSyntNodeRange.Create(AStartPos, AEndPos: integer; ANode: TTreeNode; AStyleIdx: integer);
begin
  inherited Create(AStartPos, AEndPos);
  FNode := ANode;
  FStyleIdx := AStyleIdx;
  FChildren := TzStringList.Create;
  FChildren.Sorted := True;
  FChildren.CaseSensitive := False;
end;

destructor TSyntNodeRange.Destroy;
begin
  FreeAndNil(FChildren);
  inherited;
end;

function TSyntNodeRange.FindChild(const ChdName: string): TTreeNode;
var i: integer;
begin
  i := FChildren.IndexOf(ChdName);
  if i <> -1 then
    Result := TTreeNode(FChildren.Objects[i])
  else
    Result := nil;
end;

{ TCustomSyntaxTreeView }

constructor TCustomSyntaxTreeView.Create(AOwner: TComponent);
begin
  FExpItems := TList.Create;
  inherited;
  HideSelection := False;
  ReadOnly := True;
  FClickAction := staSelect;
  FRootNodes := TzStringList.Create;
  FRootNodes.CaseSensitive := False;
  FRootNodes.Sorted := True;
  FNodeRanges := TRangeCollection.Create;
  FChanges := TChangeFixer.Create;
  FOpenedRanges := TObjectList.Create;
  FStyles := TObjectList.Create;
{$IFDEF EC_VCL5}
  OnCustomDrawItem := PureCustomDrawn;
{$ENDIF}
  FUpdateTimer := TTimer.Create(nil);
  FUpdateTimer.Enabled := False;
  FUpdateTimer.Interval := 500;
  FUpdateTimer.OnTimer := OnUpdateTimer;
end;

destructor TCustomSyntaxTreeView.Destroy;
begin
  SafeDestroying(Self);
  inherited;
  FreeAndNil(FExpItems);
  FreeAndNil(FUpdateTimer);
  FreeAndNil(FRootNodes);
  FreeAndNil(FNodeRanges);
  FreeAndNil(FChanges);
  FreeAndNil(FOpenedRanges);
  FreeAndNil(FStyles);
end;

procedure TCustomSyntaxTreeView.Clear;
begin
  FBreakUpdate := True;
  Items.Clear;
  FRootNodes.Clear;
  FNodeRanges.Clear;
  FChanges.Clear;
  FOpenedRanges.Clear;
  FStyles.Clear;
end;

function TCustomSyntaxTreeView.CaretPosChanged: Boolean;
  function IsParent(Node, Prn: TTreeNode): Boolean;
  begin
    if Node = Prn then Result := True else
      if Node.Parent = nil then Result := False
        else Result := IsParent(Node.Parent, Prn);
  end;
var NewPos: integer;
    List: TList;
    i: integer;
begin
  Result := False;
  if  not Assigned(FSyntaxMemo) or not FAutoSynchronize or not Showing then Exit;
  FNullWidth := (Width = 0) or (Height = 0);
  if FNullWidth then
    Exit;
  List := TList.Create;
  try
    NewPos := FSyntaxMemo.CaretStrPos;
    NewPos := FChanges.CurToOld(NewPos);
    FNodeRanges.GetRangesAtPos(List, NewPos);
    if (List.Count > 0) and (TSyntNodeRange(List.Last).FNode <> nil) then
     begin
       Selected := TSyntNodeRange(List[List.Count - 1]).FNode;
       if FAutoCollapse then
         for i := 0 to Items.Count - 1 do
           if Items[i].Expanded and not IsParent(Selected, Items[i]) then
             Items[i].Collapse(True);
     end;
  finally
    List.Free;
  end;
end;

procedure TCustomSyntaxTreeView.DblClick;
var sp, ep: integer;
begin
  inherited;
  if Assigned(Selected) and Assigned(FSyntaxMemo) and
     (FClickAction in [staMoveCaret, staSelect]) then
  with Selected as TTreeNode do
   if Data <> nil then
    with TSyntNodeRange(Data) do
     if StartPos >= 0 then
      begin
       sp := FChanges.OldToCur(StartPos);
       FSyntaxMemo.CaretPos := FSyntaxMemo.StrPosToCaretPos(sp);
       FSyntaxMemo.TopLine := FSyntaxMemo.CaretPos.Y;
       if (EndPos <> -1) and (FClickAction = staSelect) then
         begin
          ep := FChanges.OldToCur(EndPos);
          FSyntaxMemo.SetSelection(sp, ep - sp);
         end;
       Windows.SetFocus(FSyntaxMemo.Handle);
//       FSyntaxMemo.SetFocus;
      end;
end;

procedure TCustomSyntaxTreeView.Finished;
begin
  if Assigned(FSyntaxMemo) and not (csDestroying in ComponentState) then
   begin
    FUpdateTimer.Enabled := False;
    FUpdateTimer.Enabled := True;
   end;
end;

procedure TCustomSyntaxTreeView.FormatChanged;
begin

end;

procedure TCustomSyntaxTreeView.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (AComponent = FSyntaxMemo) and (Operation = opRemove) then
   begin
    FSyntaxMemo := nil;
    if not (csDestroying in ComponentState) then Clear;
   end;
end;

procedure TCustomSyntaxTreeView.SetSyntaxMemo(const Value: TCustomSyntaxMemo);
var SameSource: Boolean;
begin
  if FSyntaxMemo = Value then Exit;
  if Assigned(FSyntaxMemo) then
    FSyntaxMemo.RemoveClient(Self);
  SameSource := Assigned(FSyntaxMemo) and Assigned(Value) and
                (Value.SyntObj = FSyntaxMemo.SyntObj);
  FSyntaxMemo := Value;
  if not SameSource then
    Clear;
  if Assigned(FSyntaxMemo) then
   begin
    FSyntaxMemo.FreeNotification(Self);
    FSyntaxMemo.AddClient(Self);
    if not SameSource and (FSyntaxMemo.SyntObj <> nil) and (FSyntaxMemo.SyntObj.IsFinished) then
      UpdateTree(FSyntaxMemo.SyntObj, True);
   end;
end;

procedure TCustomSyntaxTreeView.TextChanged(Sender: TObject; Pos, Count, LineChange: integer);
begin
  if Pos = -1 then
   begin
    UpdateTree;
   end else
   begin
    FChanges.Add(Pos, Count);
    FBreakUpdate := True;
    FRepeatUpdate := False;
   end;
end;

procedure TCustomSyntaxTreeView.RestoreState;
var i, strt: integer;
begin
  for i := 0 to Items.Count - 1 do
   with Items[i] do
    if Data <> nil then
     begin
      if Data = nil then Exit;
      strt := TSyntNodeRange(Data).StartPos;
      if FExpItems.IndexOf(TObject(strt)) <> -1 then
        Expand(False);
      if (strt = FSelNodeStrt) or (FSelNodeStrt <= 0) and (i = -FSelNodeStrt) then
        Selected := True;
      if (strt = FTopItemStrt) or (FTopItemStrt < 0) and (i = -FTopItemStrt) then
        begin
          TopItem := Items[i];
          if HandleAllocated then
            SendMessage(Handle, WM_HSCROLL, MakeLong(SB_LEFT, 0), 0);
        end;
     end;
  Dec(FSavedStCount);
end;

procedure TCustomSyntaxTreeView.SaveState;
var i: integer;
   function GetNodeLink(Node: TTreeNode): integer;
   begin
     if Node = nil then
       Result := 0
     else
       if Node.Data <> nil then
         Result := FChanges.OldToCur(TSyntNodeRange(Node.Data).StartPos)
       else
         Result := -Node.AbsoluteIndex;
   end;
begin
  if FSavedStCount > 0 then Exit;
  FExpItems.Clear;
  for i := 0 to Items.Count - 1 do
   with Items[i] do
    if Expanded and (Data <> nil)then
     FExpItems.Add(TObject(FChanges.OldToCur(TSyntNodeRange(Data).StartPos)));

  FSelNodeStrt := GetNodeLink(Selected);
  FTopItemStrt := GetNodeLink(TopItem);

  Inc(FSavedStCount);
end;

function TCustomSyntaxTreeView.FindChild(Prn: TTreeNode; const Name: string): TTreeNode;
var i: integer;
begin
  if Prn <> nil then
    Result := TSyntNodeRange(Prn.Data).FindChild(Name)
  else
    begin
      i := FRootNodes.IndexOf(Name);
      if i <> -1 then
        Result := TTreeNode(FRootNodes.Objects[i])
      else
        Result := nil;
    end;
end;

procedure TCustomSyntaxTreeView.UpdateTree(const Synt: TClientSyntAnalyzer; Breakable: Boolean);
var i, GroupCount: integer;
    IdleRes: DWORD;

  procedure DoProgress(N: integer);
  begin
    if Assigned(FOnLoadProgress) then
      FOnLoadProgress(Self, N, Synt.RangeCount);
  end;

  procedure SetStyleRef(Data: TSyntNodeRange; Style: TSyntaxFormat);
  var st: TSyntaxFormat;
  begin
    if Style = nil then Data.FStyleIdx := -1 else
     begin
      Data.FStyleIdx := FStyles.IndexOf(Style);
      if Data.FStyleIdx = -1 then
       begin
        st := TSyntaxFormat.Create(nil);
        st.Assign(Style);
        FStyles.Add(st);
        Data.FStyleIdx := FStyles.Count - 1;
       end;
     end;
  end;

  function ExtractName(var LongPath: string): string;
  var k: integer;
  begin
    Result := '';
    k := 1;
    while True do
      begin
        k := ecPosEx('\', LongPath, k);
        if k = 0 then
          begin
            Result := LongPath;
            LongPath := '';
            Exit;
          end else
          begin
            if (Length(LongPath) > k) and (LongPath[k + 1] = '\') then
              begin
                {$IFDEF EC_DOTNET}
                LongPath.Remove(k, 1);
                {$ELSE}
                System.Delete(LongPath, k, 1);
                {$ENDIF}
                Inc(K);
              end else
              begin
                Result := Copy(LongPath, 1, k - 1);
                {$IFDEF EC_DOTNET}
                LongPath.Remove(k, 1);
                {$ELSE}
                System.Delete(LongPath, 1, k);
                {$ENDIF}
                Exit;
              end;
          end;
      end;
  end;

  // search or create group node;
  function CreateGroup(GroupName: string; Prn: TTreeNode): TTreeNode;
  var S: string;
  begin
    Result := Prn;
    while GroupName <> '' do
      begin
        S := ExtractName(GroupName);
        if S = '' then
          Prn := nil else
        if (S = '..') and Assigned(Prn) then
          Prn := Prn.Parent else
        if not (Assigned(Prn) and (Prn.Text = S) and (GroupName = '') or
                (S = '.')) then
          begin
            Result := FindChild(Prn, S);
            if not Assigned(Result) then
              begin
                dec(GroupCount);
                Result := AddNode(Prn, S, -1);
                AttachNodeData(Result, GroupCount, -1, -1);
              end;
            Prn := Result;
          end else
            Result := Prn;
      end;
{    Result := FindChild(Prn, GroupName);
    if Result = nil then
    if (Prn <> nil) and SameText(Prn.Text, GroupName) then Result := Prn
     else
      begin
       dec(GroupCount);
       Result := AddNode(Prn, GroupName, -1);
       AttachNodeData(Result, GroupCount, -1, -1);
      end;}
  end;

  procedure AddRange(Range: TTextRange; Prn: TTreeNode);
  var tn, ParentNode: TTreeNode;
      gn: string;
      rn: TSyntNodeRange;
      ep: integer;
  begin
    if not CanAcceptRange(Range) then Exit;
    DoSelectParentNode(Prn, Range);
    gn := Synt.GetRangeGroup(Range);
    if gn <> '' then
     begin
       ParentNode := CreateGroup(gn, Prn);
       if (ParentNode <> nil) and (ParentNode.Data <> nil) then
        begin
         if TSyntNodeRange(ParentNode.Data).FStyleIdx = -1 then
           SetStyleRef(TSyntNodeRange(ParentNode.Data), Range.Rule.TreeGroupStyleObj);
         if Range.Rule.TreeGroupImage <> -1 then
          begin
            ParentNode.ImageIndex := Range.Rule.TreeGroupImage;
            ParentNode.SelectedIndex := ParentNode.ImageIndex;
          end;
         DoCreateGroupNode(ParentNode, Range);
        end;
     end else ParentNode := Prn;

    if Assigned(ParentNode) and (FMaxLevels > 0) and (ParentNode.Level >= FMaxLevels - 1) then
      begin
        // Skip children
        Inc(i);
        while (i < Synt.RangeCount) and (Synt.Ranges[i].StartIdx <= Range.EndIdx) do
          inc(i);
        Dec(i);
        Exit;
      end;

    tn := AddNode(ParentNode, Synt.GetRangeName(Range), Range.Rule.TreeItemImage);

    // Index for quick search
    if Range.EndIdx <> -1 then ep := Synt.Tags[Range.EndIdx].EndPos
      else ep := -1;

    rn := AttachNodeData(tn as TTreeNode, Synt.Tags[Range.StartIdx].StartPos, ep, -1);
    SetStyleRef(rn, Range.Rule.TreeItemStyleObj);
    // Node
    DoCreateTreeNode(tn, Range);
    inc(i);
    while (i < Synt.RangeCount) and (Synt.Ranges[i].StartIdx < Range.EndIdx) do
     begin
      if Synt.Ranges[i].IsParent(Range) then
        AddRange(Synt.Ranges[i], tn)
      else
        AddRange(Synt.Ranges[i], Prn);
      inc(i);
     end;
    dec(i);
    DoProgress(i);
    if Breakable then
     begin
       IdleRes := SafeProcessMessages(Self);
       if (IdleRes <> 0) or FBreakUpdate then Abort;
     end;
  end;
begin
  {$IFDEF EC_VCL5}
  if csDesigning in ComponentState then
    Exit;
  {$ENDIF}
  FRepeatUpdate := FUpdating;
  if FRepeatUpdate then Exit;
  FUpdating := True;
  Items.BeginUpdate;
  GroupCount := 0;
  IdleRes := 0;
  try
    SaveState;
    Clear;
    FBreakUpdate := False;
    i := 0;
    DoProgress(0);
    while i < Synt.RangeCount do
     begin
      AddRange(Synt.Ranges[i], nil);
      inc(i);
     end;
    RestoreState;
  except
  end;
  DoProgress(Synt.RangeCount);
  if IdleRes <> 0 then Exit;
  Items.EndUpdate;
  FUpdating := False;
  if FRepeatUpdate then
   begin
    FUpdateTimer.Enabled := False;
    FUpdateTimer.Enabled := True;
//    UpdateTree(Synt, Breakable)
   end
   else
    if SortType in [stText, stBoth] then
      AlphaSort;

  CaretPosChanged;

  DoTreeUpdated;
end;

procedure TCustomSyntaxTreeView.UpdateTree;
begin
   FBreakUpdate := True;
   FRepeatUpdate := False;
   if Assigned(FSyntaxMemo) and not (csDestroying in ComponentState) and
      Assigned(FSyntaxMemo.SyntObj) then
     begin
      if FSyntaxMemo.SyntObj.IsFinished then UpdateTree(FSyntaxMemo.SyntObj, True);
     end
   else
     Clear;
end;

procedure TCustomSyntaxTreeView.WndProc(var Message: TMessage);
begin
  if not (csDesigning in ComponentState) and (Message.Msg = WM_LBUTTONDBLCLK) and
     (FClickAction <> staDefault) and (Selected <> nil) and
     (Selected.Data <> nil) and (TSyntNodeRange(Selected.Data).StartPos >= 0) then
   begin
    DblClick;
   end else inherited WndProc(Message);
end;

procedure TCustomSyntaxTreeView.SetGroupIndex(const Value: integer);
begin
  if FGroupIndex <> Value then
   begin
     FGroupIndex := Value;
     UpdateTree;
   end;
end;

function TCustomSyntaxTreeView.CustomDrawItem(Node: TTreeNode;
  State: TCustomDrawState; Stage: TCustomDrawStage;
  var PaintImages: Boolean): Boolean;
var st_idx: integer;
    sv_col: TColor;
    stl: TSyntaxFormat;
begin
  Result := inherited CustomDrawItem(Node, State, Stage, PaintImages);
  if Result and (Stage = cdPrePaint) and (Node.Data <> nil) then
   begin
     st_idx := TSyntNodeRange(Node.Data).FStyleIdx;
     if (st_idx >= 0) and (st_idx < FStyles.Count) then
       begin
        stl := TSyntaxFormat(FStyles[st_idx]);
        sv_col := Canvas.Font.Color;
        if (stl.BgColor <> clNone) and not (cdsSelected in State) then
          Canvas.Brush.Color := stl.BgColor;
        if stl.FormatType = ftCustomFont then
          begin
           Canvas.Font := stl.Font;
           if (stl.Font.Color = clNone) or (cdsSelected in State) then
             Canvas.Font.Color := sv_col;
          end else
          begin
            if (stl.FormatType <> ftBackGround) and
               (stl.Font.Color <> clNone) and
               not (cdsSelected in State) then
              Canvas.Font.Color := stl.Font.Color;
            if stl.FormatType = ftFontAttr then
              Canvas.Font.Style := stl.Font.Style;
          end;
       end;
   end;
end;

{$IFNDEF EC_VCL5}
function TCustomSyntaxTreeView.IsCustomDrawn(Target: TCustomDrawTarget;
  Stage: TCustomDrawStage): Boolean;
begin
  Result := inherited IsCustomDrawn(Target, Stage) or (Target = dtItem);
end;
{$ELSE}
procedure TCustomSyntaxTreeView.PureCustomDrawn(Sender: TCustomTreeView; Node: TTreeNode;
    State: TCustomDrawState; var DefaultDraw: Boolean);
begin

end;
{$ENDIF}

procedure TCustomSyntaxTreeView.OnUpdateTimer(Sender: TObject);
begin
  FUpdateTimer.Enabled := False;
  UpdateTree;
end;

function TCustomSyntaxTreeView.GetNodeRange(Node: TTreeNode): TTextRange;
begin
  Result := nil;
  if (FSyntaxMemo <> nil) and (FSyntaxMemo.SyntObj <> nil) and
     (Node <> nil) and (Node.Data <> nil) then
   with TSyntNodeRange(Node.Data) do
    if EndPos > 0 then
      Result := FSyntaxMemo.SyntObj.RangeAtPos(StartPos);
end;

function TCustomSyntaxTreeView.GetDragRanges(Source: TObject; X, Y: Integer; var R1, R2: TTextRange): Boolean;
var Node: TTreeNode;
begin
  Result := False;
  if Assigned(FSyntaxMemo) and not FSyntaxMemo.ReadOnly and (Source = Self) then
    begin
      R1 := GetNodeRange(Selected);
      if (R1 <> nil) and (R1.EndIdx >= 0) then
        begin
          Node := GetNodeAt(X, Y);
          R2 := GetNodeRange(Node);
          Result := (R2 <> nil) and (R1 <> R2) and (R1.Parent = R2.Parent);
        end;
    end;
end;

procedure TCustomSyntaxTreeView.DragOver(Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var R1, R2: TTextRange;
begin
  Accept := GetDragRanges(Source, X, Y, R1, R2);
  if not Accept then
    inherited;
end;

procedure TCustomSyntaxTreeView.DragDrop(Source: TObject; X, Y: Integer);
var R1, R2: TTextRange;
    sp, ep, np: integer;
    cp: TPoint;
begin
  if GetDragRanges(Source, X, Y, R1, R2) then
    begin
      sp := FSyntaxMemo.SyntObj[R1.StartIdx].StartPos;
      if R1.EndIdx < FSyntaxMemo.SyntObj.TagCount - 1 then
        ep := FSyntaxMemo.SyntObj[R1.EndIdx + 1].StartPos
      else
        ep := FSyntaxMemo.SyntObj[R1.EndIdx].EndPos;
      FSyntaxMemo.BeginUpdate;
      try
        FSyntaxMemo.SetSelection(sp, ep - sp);
        np := FSyntaxMemo.SyntObj[R2.StartIdx].StartPos;
        with TSyntNodeRange(Selected.Data) do
          begin
            FStartPos := np;
            FEndPos := np + ep - sp;
          end;
        cp := FSyntaxMemo.StrPosToCaretPos(np);
        FSyntaxMemo.MoveText(cp, False);
        FSyntaxMemo.CaretPos := cp;
      finally
        FSyntaxMemo.EndUpdate;
      end;
    end else
  inherited;
end;

procedure TCustomSyntaxTreeView.SetAutoCollapse(const Value: Boolean);
begin
  if FAutoCollapse <> Value then
    begin
      FAutoCollapse := Value;
      if FAutoSynchronize then
        CaretPosChanged;
    end;
end;

procedure TCustomSyntaxTreeView.CMShowingChanged(var Message: TMessage);
begin
  inherited;
  if FAutoSynchronize then
    CaretPosChanged;
end;

procedure TCustomSyntaxTreeView.Resize;
begin
  inherited;
  if FNullWidth and FAutoSynchronize then
    CaretPosChanged;
end;

procedure TCustomSyntaxTreeView.CreateWnd;
var i: integer;
begin
  inherited;
  for i := 0 to Items.Count - 1 do
   with Items[i] do
    if Data <> nil then
     TSyntNodeRange(Data).FNode := Items[i];
end;

procedure TCustomSyntaxTreeView.DestroyWnd;
var i, j: integer;
begin
  for i := 0 to FNodeRanges.LevelCount - 1 do
    with FNodeRanges.Levels[i] do
      for j := 0 to Count - 1 do
        TSyntNodeRange(Items[j]).FNode := nil;
  inherited;
end;

function TCustomSyntaxTreeView.AttachNodeData(Node: TTreeNode; StartPos, EndPos,
  StyleIdx: integer): TSyntNodeRange;
begin
  Result := TSyntNodeRange.Create(StartPos, EndPos, Node, StyleIdx);
  Node.Data := Result;
  if (EndPos = -1) or not FAutoSynchronize then
    FOpenedRanges.Add(Result)
  else
    FNodeRanges.Add(Result);
end;

procedure TCustomSyntaxTreeView.DoTreeUpdated;
begin
  if Assigned(FOnTreeUpdated) then
    FOnTreeUpdated(Self);
end;

procedure TCustomSyntaxTreeView.DoCreateTreeNode(Node: TTreeNode;
  Range: TTextRange);
begin
  if Assigned(FOnCreateTreeNode) then
    FOnCreateTreeNode(Self, Node, Range);
end;

procedure TCustomSyntaxTreeView.SetAutoSynchronize(const Value: Boolean);
begin
  if FAutoSynchronize <> Value then
    begin
      FAutoSynchronize := Value;
      if FAutoSynchronize then
        UpdateTree;
    end;
end;

function TCustomSyntaxTreeView.CanAcceptRange(Range: TTextRange): Boolean;
begin
  Result := Assigned(Range.Rule);
  if Result then
    begin
      Result := Range.Rule.DisplayInTree and
                ((FGroupIndex = 0) or ((Range.Rule.GroupIndex and FGroupIndex) <> 0));
      if Assigned(FOnAcceptRange) then
        FOnAcceptRange(Self, Range, Result);
    end;
end;

procedure TCustomSyntaxTreeView.SetMaxLevels(const Value: integer);
begin
  if FMaxLevels <> Value then
    begin
      FMaxLevels := Value;
      UpdateTree;
    end;
end;

procedure TCustomSyntaxTreeView.DoCreateGroupNode(Node: TTreeNode;
  Range: TTextRange);
begin
  if Assigned(FOnCreateGroupNode) then
    FOnCreateGroupNode(Self, Node, Range);
end;

procedure TCustomSyntaxTreeView.DoSelectParentNode(var Node: TTreeNode;
  Range: TTextRange);
begin
  if Assigned(FOnSelectParentNode) then
    FOnSelectParentNode(Self, Range, Node);
end;

function TCustomSyntaxTreeView.AddNode(Prn: TTreeNode; const Text: string;
  ImageIdx: integer): TTreeNode;
begin
  Result := Items.AddChild(Prn, Text);
  Result.ImageIndex := ImageIdx;
  Result.SelectedIndex := ImageIdx;
  if Prn = nil then
    FRootNodes.AddObject(Text, Result)
  else
    TSyntNodeRange(Prn.Data).AddChild(Result);
end;

end.

