unit VirtualTrees.EditLink;

// Base class for inplace node editors implementing IVTEditLink interface
// and default node editor.

interface

uses
  WinApi.Messages,
  System.Types,
  System.Classes,
  Vcl.Controls,
  Vcl.StdCtrls,
  VirtualTrees,
  VirtualTrees.Types;

type
  //Edit support Classes.
  TStringEditLink = class;

  TVTEdit = class(TCustomEdit)
  private
    procedure CMAutoAdjust(var Message : TMessage); message CM_AUTOADJUST;
    procedure CMExit(var Message : TMessage); message CM_EXIT;
    procedure CMRelease(var Message : TMessage); message CM_RELEASE;
    procedure CNCommand(var Message : TWMCommand); message CN_COMMAND;
    procedure WMChar(var Message : TWMChar); message WM_CHAR;
    procedure WMDestroy(var Message : TWMDestroy); message WM_DESTROY;
    procedure WMGetDlgCode(var Message : TWMGetDlgCode); message WM_GETDLGCODE;
    procedure WMKeyDown(var Message : TWMKeyDown); message WM_KEYDOWN;
  protected
    FRefLink : IVTEditLink;
    FLink : TStringEditLink;
    procedure AutoAdjustSize; virtual;
    function CalcMinHeight : Integer; virtual;
    procedure CreateParams(var Params : TCreateParams); override;
    function GetTextSize : TSize; virtual;
    procedure KeyPress(var Key : Char); override;
  public
    constructor Create(Link : TStringEditLink); reintroduce;
    procedure ClearLink;
    procedure ClearRefLink;
    procedure Release; virtual;

    property AutoSelect;
    property AutoSize;
    property BorderStyle;
    property CharCase;
    property HideSelection;
    property MaxLength;
    property OEMConvert;
    property PasswordChar;
  end;

  TBaseEditLink = class;

  TEditLinkEditEvent = procedure (Sender: TBaseEditLink; var Result: Boolean) of object;
  TEditLinkPrepareEditEvent = procedure (Sender: TBaseEditLink; var Edit: TControl; var Result: Boolean) of object;

  // Most abstract base class for implementing IVTEditLink.
  // Knows almost nothing about associated Edit control and doesn't perform any
  // actions on it. Contains some properties that are not used directly but could
  // be useful in descendant classes. Follows general extension approach - all
  // IVTEditLink methods are virtual and most of them call DoXXX virtual methods
  // which in turn call event handlers so these extension options possible:
  //   - overriding main API methods to run additional actions before, after or
  //     instead of basic class code.
  //     (+) Lesser modification of existing classes
  //     (-) Event handlers are already launched after calling parent method
  //     (-) It's critical to check Result of parent method and exit immediately
  //       on False - this value means no action is done.
  //     (-) Returning Result is necessary
  //   - overriding DoXXX methods to run additional actions inside basic class code
  //     (+) No need in returning - lesser boilerplate code
  //     (-) Should call inherited to launch event handlers (OK if not using them)
  //   - assign event handlers in end-user code
  //     (+) Access to external classes with data to copy to EditLink editor.
  //     (-) Lesser encapsulation
  TBaseEditLink = class(TInterfacedObject, IVTEditLink)
  strict protected
    FEdit: TControl;                          // One of the property editor classes.
    FTree : TCustomVirtualStringTree;         //A back reference to the tree calling.
    FNode : PVirtualNode;                     //The node to be edited.
    FColumn : TColumnIndex;                   //The column of the node.
    FStopping : Boolean;                      //Set to True when the edit link requests stopping the edit action.
    FAlignment : TAlignment;
    FBiDiMode: TBiDiMode;

    // custom event handlers
    FOnPrepareEdit: TEditLinkPrepareEditEvent;
    FOnBeginEdit,
    FOnEndEdit,
    FOnCancelEdit: TEditLinkEditEvent;

    procedure SetEdit(const Value : TControl); //Setter for the FEdit member;
  public
    // IVTEditLink API
    function BeginEdit : Boolean; virtual; stdcall;
    function CancelEdit : Boolean; virtual; stdcall;
    function EndEdit : Boolean; virtual; stdcall;
    function GetBounds : TRect; virtual; stdcall; abstract;
    function PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; virtual; stdcall;
    procedure ProcessMessage(var Message : TMessage); virtual; stdcall; abstract;
    procedure SetBounds(R : TRect); virtual; stdcall; abstract;

    // Methods to plug custom actions into main ones. In base class only call event handlers.
    // Descendants may modify Result to cancel further flow.
    procedure DoBeginEdit(var Result: Boolean); virtual;
    procedure DoCancelEdit(var Result: Boolean); virtual;
    procedure DoEndEdit(var Result: Boolean); virtual;
    procedure DoPrepareEdit(var Result: Boolean); virtual;

    property Alignment : TAlignment read FAlignment;
    property BiDiMode: TBiDiMode read FBiDiMode;
    property Column : TColumnIndex read FColumn; //[IPK] Make Column(Index) accessible
    property Node : PVirtualNode read FNode;     //[IPK] Make FNode accessible
    property Tree : TCustomVirtualStringTree read FTree;
    property Stopping : Boolean read FStopping;

    property OnBeginEdit: TEditLinkEditEvent read FOnBeginEdit write FOnBeginEdit;
    property OnCancelEdit: TEditLinkEditEvent read FOnCancelEdit write FOnCancelEdit;
    property OnEndEdit: TEditLinkEditEvent read FOnEndEdit write FOnEndEdit;
    property OnPrepareEdit: TEditLinkPrepareEditEvent read FOnPrepareEdit write FOnPrepareEdit;
  end;

  // Edit link that has TWinControl-based Edit. Performs visibility and focus actions,
  // transfers window messages to Edit control.
  TWinControlEditLink = class(TBaseEditLink)
  protected
    function GetEdit: TWinControl;                //Getter for the FEdit member;
    procedure SetEdit(const Value : TWinControl); //Setter for the FEdit member;
  public
    destructor Destroy; override;

    function BeginEdit : Boolean; override; stdcall;
    function CancelEdit : Boolean; override; stdcall;
    function EndEdit : Boolean; override; stdcall;
    function GetBounds : TRect; override; stdcall;
    procedure ProcessMessage(var Message : TMessage); override; stdcall;

    property Edit : TWinControl read GetEdit write SetEdit;
  end;

  // Edit link that implements default node text editor.
  TStringEditLink = class(TWinControlEditLink)
  protected
    FTextBounds : TRect;                      //Smallest rectangle around the text.
    function GetEdit: TVTEdit;                //Getter for the FEdit member;
    procedure SetEdit(const Value : TVTEdit); //Setter for the FEdit member;
  public
    constructor Create;

    function BeginEdit : Boolean; override; stdcall;
    function CancelEdit : Boolean; override; stdcall;
    function EndEdit : Boolean; override; stdcall;
    function PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean; override; stdcall;
    procedure SetBounds(R : TRect); override; stdcall;

    property Edit : TVTEdit read GetEdit write SetEdit;
  end;

implementation

uses
  WinApi.Windows,
  System.SysUtils,
  System.Math,
  Vcl.Graphics,
  Vcl.Forms;

type
  TCustomVirtualStringTreeCracker = class(TCustomVirtualStringTree);

//----------------- TVTEdit --------------------------------------------------------------------------------------------

//Implementation of a generic node caption editor.

constructor TVTEdit.Create(Link : TStringEditLink);
begin
  inherited Create(nil);
  if not Assigned(Link) then
    raise EArgumentException.Create('Parameter Link must not be nil.');
  ShowHint := False;
  ParentShowHint := False;
  //This assignment increases the reference count for the interface.
  FRefLink := Link;
  //This reference is used to access the link.
  FLink := Link;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.ClearLink;
begin
  FLink := nil
end;

//----------------------------------------------------------------------------------------------------------------------
procedure TVTEdit.ClearRefLink;
begin
  FRefLink := nil
end;

//----------------------------------------------------------------------------------------------------------------------

function TVTEdit.CalcMinHeight : Integer;
var
  textHeight : Integer;
begin
  //Get the actual text height.
  textHeight := GetTextSize.cy;
  //The minimal height is the actual text height in pixels plus the the non client area.
  Result := textHeight + (Height - ClientHeight);
  //Also, proportionally to the text size, additional pixel(s) needs to be added for the caret.
  Result := Result + Trunc(textHeight * 0.05);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.CMAutoAdjust(var Message : TMessage);
begin
  AutoAdjustSize;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.CMExit(var Message : TMessage);
begin
  if Assigned(FLink) and not FLink.Stopping then
    with TCustomVirtualStringTreeCracker(FLink.Tree) do
    begin
      if (toAutoAcceptEditChange in TreeOptions.StringOptions) then
        DoEndEdit
      else
        DoCancelEdit;
    end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.CMRelease(var Message : TMessage);
begin
  Free;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.CNCommand(var Message : TWMCommand);
begin
  if Assigned(FLink) and Assigned(FLink.Tree) and (Message.NotifyCode = EN_UPDATE) and not (vsMultiline in FLink.Node.States) then
    //Instead directly calling AutoAdjustSize it is necessary on Win9x/Me to decouple this notification message
    //and eventual resizing. Hence we use a message to accomplish that.
    AutoAdjustSize()
  else
    inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.WMChar(var Message : TWMChar);
begin
  if not (Message.CharCode in [VK_ESCAPE, VK_TAB]) then
    inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.WMDestroy(var Message : TWMDestroy);
begin
  //If editing stopped by other means than accept or cancel then we have to do default processing for
  //pending changes.
  if Assigned(FLink) and not FLink.Stopping and not (csRecreating in Self.ControlState) then
  begin
    with TCustomVirtualStringTreeCracker(FLink.Tree) do
    begin
      if (toAutoAcceptEditChange in TreeOptions.StringOptions) and Modified then
        Text[FLink.Node, FLink.Column] := FLink.Edit.Text;
    end;
    FLink := nil;
    FRefLink := nil;
  end;

  inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.WMGetDlgCode(var Message : TWMGetDlgCode);
begin
  inherited;

  Message.Result := Message.Result or DLGC_WANTALLKEYS or DLGC_WANTTAB or DLGC_WANTARROWS;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.WMKeyDown(var Message : TWMKeyDown);
//Handles some control keys.

var
  Shift : TShiftState;
  EndEdit : Boolean;
  Tree : TBaseVirtualTree;
  NextNode : PVirtualNode;
  ColumnCandidate : Integer;
  EditOptions : TVTEditOptions;
  Column : TVirtualTreeColumn;
begin
  Tree := FLink.Tree;
  case Message.CharCode of
    VK_ESCAPE :
      begin
        TCustomVirtualStringTreeCracker(Tree).DoCancelEdit;
      end;
    VK_RETURN :
      begin
        EndEdit := not (vsMultiline in FLink.Node.States);
        if not EndEdit then
        begin
          //If a multiline node is being edited the finish editing only if Ctrl+Enter was pressed,
          //otherwise allow to insert line breaks into the text.
          Shift := KeyDataToShiftState(Message.KeyData);
          EndEdit := ssCtrl in Shift;
        end;
        if EndEdit then
        begin
          Tree := FLink.Tree;
          FLink.Tree.InvalidateNode(FLink.Node);
          NextNode := Tree.GetNextVisible(FLink.Node, True);
          TCustomVirtualStringTreeCracker(FLink.Tree).DoEndEdit;

          //get edit options for column as priority. If column has toDefaultEdit
          //use global edit options for tree
          EditOptions := TCustomVirtualStringTreeCracker(Tree).TreeOptions.EditOptions; //default
          ColumnCandidate := - 1;
          if Tree.Header.Columns.Count > 0 then //are there any columns?
          begin
            Column := Tree.Header.Columns[Tree.FocusedColumn];
            if Column.EditOptions <> toDefaultEdit then
              EditOptions := Column.EditOptions;

            //next column candidate for toVerticalEdit and toHorizontalEdit
            if Column.EditNextColumn <> - 1 then
              ColumnCandidate := Column.EditNextColumn;
          end;

          case EditOptions of
            toDefaultEdit :
              TCustomVirtualStringTreeCracker(Tree).TrySetFocus;
            toVerticalEdit :
              if NextNode <> nil then
              begin
                Tree.FocusedNode := NextNode;

                //for toVerticalEdit ColumnCandidate is also proper,
                //select ColumnCandidate column in row below
                if ColumnCandidate <> - 1 then
                begin
                  Tree.FocusedColumn := ColumnCandidate;
                  TCustomVirtualStringTreeCracker(Tree).EditColumn := ColumnCandidate;
                end;

                if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then
                  TCustomVirtualStringTreeCracker(Tree).DoEdit;
              end;
            toHorizontalEdit :
              begin
                if ColumnCandidate = - 1 then
                begin
                  //for toHorizontalEdit if property EditNextColumn is not used
                  //try to use just next column
                  ColumnCandidate := Tree.FocusedColumn + 1;
                  while (ColumnCandidate < Tree.Header.Columns.Count) and not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) do
                    Inc(ColumnCandidate);
                end
                else if not Tree.CanEdit(Tree.FocusedNode, ColumnCandidate) then
                  ColumnCandidate := Tree.Header.Columns.Count; //omit "focus/edit column" (see below)

                if ColumnCandidate < Tree.Header.Columns.Count then
                begin
                  Tree.FocusedColumn := ColumnCandidate;
                  TCustomVirtualStringTreeCracker(Tree).EditColumn := ColumnCandidate;
                  TCustomVirtualStringTreeCracker(Tree).DoEdit;
                end;
              end;
          end;
        end;
      end;
    VK_UP :
      begin
        if not (vsMultiline in FLink.Node.States) then
          Message.CharCode := VK_LEFT;
        inherited;
      end;
    VK_DOWN :
      begin
        if not (vsMultiline in FLink.Node.States) then
          Message.CharCode := VK_RIGHT;
        inherited;
      end;
    VK_TAB :
      begin
        if Tree.IsEditing then
        begin
          Tree.InvalidateNode(FLink.Node);
          if ssShift in KeyDataToShiftState(Message.KeyData) then
            NextNode := Tree.GetPreviousVisible(FLink.Node, True)//Shift+Tab goes to previous mode
          else
            NextNode := Tree.GetNextVisible(FLink.Node, True);
          Tree.EndEditNode;
          //check NextNode, otherwise we got AV
          if NextNode <> nil then
          begin
            //Continue editing next node
            Tree.ClearSelection();
            Tree.Selected[NextNode] := True;
            if Tree.CanEdit(Tree.FocusedNode, Tree.FocusedColumn) then
              TCustomVirtualStringTreeCracker(Tree).DoEdit;
          end;
        end;
      end;
    Ord('A') :
      begin
        if Tree.IsEditing and ([ssCtrl] = KeyboardStateToShiftState) then
        begin
          Self.SelectAll();
          Message.CharCode := 0;
        end;
      end;
    else
      inherited;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.AutoAdjustSize;
//Changes the size of the edit to accomodate as much as possible of its text within its container window.
//NewChar describes the next character which will be added to the edit's text.

var
  Size : TSize;
begin
  if not (vsMultiline in FLink.Node.States) and not (toGridExtensions in TCustomVirtualStringTreeCracker(FLink.Tree).TreeOptions.MiscOptions { see issue #252 } ) then
  begin
    //avoid flicker
    SendMessage(Handle, WM_SETREDRAW, 0, 0);
    try
      Size := GetTextSize;
      Inc(Size.cx, 2 * TCustomVirtualStringTreeCracker(FLink.Tree).TextMargin);
      //Repaint associated node if the edit becomes smaller.
      if Size.cx < Width then
        FLink.Tree.Invalidate();

      if FLink.Alignment = taRightJustify then
        FLink.SetBounds(Rect(Left + Width - Size.cx, Top, Left + Width, Top + Max(Size.cy, Height)))
      else
        FLink.SetBounds(Rect(Left, Top, Left + Size.cx, Top + Max(Size.cy, Height)));
    finally
      SendMessage(Handle, WM_SETREDRAW, 1, 0);
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.CreateParams(var Params : TCreateParams);
begin
  inherited;
  if not Assigned(FLink.Node) then
    exit; //Prevent AV exceptions occasionally seen in code below

  //Only with multiline style we can use the text formatting rectangle.
  //This does not harm formatting as single line control, if we don't use word wrapping.
  with Params do
  begin
    Style := Style or ES_MULTILINE;
    if vsMultiline in FLink.Node.States then
      Style := Style and not (ES_AUTOHSCROLL or WS_HSCROLL) or WS_VSCROLL or ES_AUTOVSCROLL;
    if tsUseThemes in FLink.Tree.TreeStates then
    begin
      Style := Style and not WS_BORDER;
      ExStyle := ExStyle or WS_EX_CLIENTEDGE;
    end
    else
    begin
      Style := Style or WS_BORDER;
      ExStyle := ExStyle and not WS_EX_CLIENTEDGE;
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TVTEdit.GetTextSize : TSize;
var
  DC : HDC;
  LastFont : THandle;
begin
  DC := GetDC(Handle);
  LastFont := SelectObject(DC, Font.Handle);
  try
    //Read needed space for the current text.
    GetTextExtentPoint32(DC, PChar(Text + 'yG'), Length(Text) + 2, Result);
  finally
    SelectObject(DC, LastFont);
    ReleaseDC(Handle, DC);
  end;
end;

procedure TVTEdit.KeyPress(var Key : Char);
begin
  if (Key = #13) and Assigned(FLink) and not (vsMultiline in FLink.Node.States) then
    Key := #0; //Filter out return keys as they will be added to the text, avoids #895
  inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TVTEdit.Release;
begin
  if HandleAllocated then
    PostMessage(Handle, CM_RELEASE, 0, 0);
end;

//----------------- TBaseEditLink ------------------------------------------------------------------------------------

procedure TBaseEditLink.SetEdit(const Value : TControl);
begin
  if Assigned(FEdit) then
    FEdit.Free;
  FEdit := Value;
end;

//----------------------------------------------------------------------------------------------------------------------

function TBaseEditLink.BeginEdit : Boolean;
//Notifies the edit link that editing can start now. descendants may cancel node edit
//by returning False.

begin
  Result := not FStopping;
  if Result then
    DoBeginEdit(Result);
end;

//----------------------------------------------------------------------------------------------------------------------

function TBaseEditLink.CancelEdit : Boolean;

// Performs edit cancelling.

begin
  Result := not FStopping;
  if Result then
  begin
    // Let descendants cancel the cancel
    DoCancelEdit(Result);
    if not Result then
      Exit;
    FStopping := True;
    FTree.CancelEditNode;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TBaseEditLink.EndEdit : Boolean;

// Performs edit ending.

begin
  Result := not FStopping;
  if Result then
  begin
    // Let descendants cancel the end
    DoEndEdit(Result);
    if not Result then
      Exit;
    FStopping := True;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TBaseEditLink.PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean;

// Performs general init: assign Tree, Node, Column, other properties; destroys previous
// edit instance.

begin
  Result := Tree is TCustomVirtualStringTree;
  if not Result then Exit; // should not happen

  FTree := Tree as TCustomVirtualStringTree;
  FNode := Node;
  FColumn := Column;
  if Column <= NoColumn then
  begin
    FBidiMode := FTree.BidiMode;
    FAlignment := TCustomVirtualStringTreeCracker(FTree).Alignment;
  end
  else
  begin
    FBidiMode := FTree.Header.Columns[Column].BidiMode;
    FAlignment := FTree.Header.Columns[Column].Alignment;
  end;
  SetEdit(nil); // always dispose edit

  DoPrepareEdit(Result);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TBaseEditLink.DoBeginEdit(var Result: Boolean);
begin
  if Assigned(OnBeginEdit) then
    OnBeginEdit(Self, Result);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TBaseEditLink.DoCancelEdit(var Result: Boolean);
begin
  if Assigned(OnCancelEdit) then
    OnCancelEdit(Self, Result);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TBaseEditLink.DoEndEdit(var Result: Boolean);
begin
  if Assigned(OnEndEdit) then
    OnEndEdit(Self, Result);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TBaseEditLink.DoPrepareEdit(var Result: Boolean);
begin
  if Assigned(OnPrepareEdit) then
    OnPrepareEdit(Self, FEdit, Result);
end;

//----------------- TWinControlEditLink ------------------------------------------------------------------------------------

destructor TWinControlEditLink.Destroy;
begin
  //FEdit.Free; casues issue #357. Fix:
  if Assigned(FEdit) and Edit.HandleAllocated then
    PostMessage(Edit.Handle, CM_RELEASE, 0, 0);
  inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

function TWinControlEditLink.GetEdit: TWinControl;
begin
  Result := TWinControl(FEdit);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TWinControlEditLink.SetEdit(const Value: TWinControl);
begin
  inherited SetEdit(Value);
end;

//----------------------------------------------------------------------------------------------------------------------

function TWinControlEditLink.BeginEdit: Boolean;
begin
  Result := inherited;
  if Result then
  begin
    Edit.Show;
    Edit.SetFocus;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TWinControlEditLink.CancelEdit: Boolean;
begin
  Result := inherited;
  if Result then
  begin
    Edit.Hide;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TWinControlEditLink.GetBounds : TRect;
begin
  Result := FEdit.BoundsRect;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TWinControlEditLink.ProcessMessage(var Message : TMessage);
begin
  FEdit.WindowProc(Message);
end;

//----------------------------------------------------------------------------------------------------------------------

function TWinControlEditLink.EndEdit: Boolean;
begin
  Result := inherited;
  if Result then
  begin
    Edit.Hide;
  end;
end;

//----------------- TStringEditLink ------------------------------------------------------------------------------------

constructor TStringEditLink.Create;
begin
  inherited;
  FEdit := TVTEdit.Create(Self);
  with Edit do
  begin
    Visible := False;
    BorderStyle := bsSingle;
    AutoSize := False;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TStringEditLink.GetEdit: TVTEdit;
begin
  Result := TVTEdit(FEdit);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TStringEditLink.SetEdit(const Value : TVTEdit);
begin
  inherited SetEdit(Value);
end;

//----------------------------------------------------------------------------------------------------------------------

function TStringEditLink.BeginEdit : Boolean;
begin
  Result := inherited;
  if Result then
  begin
    Edit.SelectAll;
    Edit.AutoAdjustSize;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TStringEditLink.CancelEdit : Boolean;
begin
  Result := inherited;
  if Result then
  begin
    Edit.ClearLink;
    Edit.ClearRefLink;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TStringEditLink.EndEdit : Boolean;
begin
  Result := inherited;
  if Result then
  try
    if Edit.Modified then
      FTree.Text[FNode, FColumn] := Edit.Text;
    Edit.ClearLink;
    Edit.ClearRefLink;
  except
    FStopping := False;
    raise;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TStringEditLink.PrepareEdit(Tree : TBaseVirtualTree; Node : PVirtualNode; Column : TColumnIndex) : Boolean;
var
  Text : string;
begin
  Result := inherited;
  if Result then
  begin
    Edit := TVTEdit.Create(Self);
    Edit.Visible := False;
    Edit.BorderStyle := bsSingle;
    Edit.AutoSize := True;
    Edit.Parent := Tree;
    //Initial size, font and text of the node.
    FTree.GetTextInfo(Node, Column, Edit.Font, FTextBounds, Text);
    Edit.Font.Color := clWindowText;
    Edit.RecreateWnd;
    Edit.AutoSize := False;
    Edit.Text := Text;
    Edit.BidiMode := FBidiMode;
    if Edit.BidiMode <> bdLeftToRight then
      ChangeBidiModeAlignment(FAlignment);
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TStringEditLink.SetBounds(R : TRect);
//Sets the outer bounds of the edit control and the actual edit area in the control.

var
  lOffset, tOffset, Height : Integer;
  offsets : TVTOffsets;
begin
  if not FStopping then
  begin
    //Check if the provided rect height is smaller than the edit control height.
    Height := R.Bottom - R.Top;
    if Height < Edit.ClientHeight then
    begin
      //If the height is smaller than the minimal height we must correct it, otherwise the caret will be invisible.
      tOffset := Edit.CalcMinHeight - Height;
      if tOffset > 0 then
        Inc(R.Bottom, tOffset);
    end;

    //Set the edit's bounds but make sure there's a minimum width and the right border does not
    //extend beyond the parent's left/right border.
    if R.Left < 0 then
      R.Left := 0;
    if R.Right - R.Left < 30 then
    begin
      if FAlignment = taRightJustify then
        R.Left := R.Right - 30
      else
        R.Right := R.Left + 30;
    end;
    if R.Right > FTree.ClientWidth then
      R.Right := FTree.ClientWidth;
    Edit.BoundsRect := R;

    //The selected text shall exclude the text margins and be centered vertically.
    //We have to take out the two pixel border of the edit control as well as a one pixel "edit border" the
    //control leaves around the (selected) text.
    R := Edit.ClientRect;

    //If toGridExtensions are turned on, we can fine tune the left margin (or the right margin if RTL is on)
    //of the text to exactly match the text in the tree cell.
    if (toGridExtensions in TCustomVirtualStringTreeCracker(FTree).TreeOptions.MiscOptions) and
      ((FAlignment = taLeftJustify) and (Edit.BidiMode = bdLeftToRight) or (FAlignment = taRightJustify) and (Edit.BidiMode <> bdLeftToRight)) then
    begin
      //Calculate needed text area offset.
      FTree.GetOffsets(FNode, offsets, ofsText, FColumn);
      if FColumn = FTree.Header.MainColumn then
      begin
        if offsets[ofsToggleButton] < 0 then
          lOffset := - (offsets[ofsToggleButton] + 2)
        else
          lOffset := 0;
      end
      else
        lOffset := offsets[ofsText] - offsets[ofsMargin] + 1;
      //Apply the offset.
      if Edit.BidiMode = bdLeftToRight then
        Inc(R.Left, lOffset)
      else
        Dec(R.Right, lOffset);
    end;

    lOffset := IfThen(vsMultiline in FNode.States, 0, 2);
    if tsUseThemes in FTree.TreeStates then
      Inc(lOffset);
    InflateRect(R, - TCustomVirtualStringTreeCracker(FTree).TextMargin + lOffset, lOffset);
    if not (vsMultiline in FNode.States) then
    begin
      tOffset := FTextBounds.Top - Edit.Top;
      //Do not apply a negative offset, the cursor will disappear.
      if tOffset > 0 then
        OffsetRect(R, 0, tOffset);
    end;
    R.Top := Max( - 1, R.Top); //A value smaller than -1 will prevent the edit cursor from being shown by Windows, see issue #159
    R.Left := Max( - 1, R.Left);
    SendMessage(Edit.Handle, EM_SETRECTNP, 0, LPARAM(@R));
  end;
end;

end.
