unit DebuggerTreeView; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Types, Math, laz.VirtualTrees, SpinEx, GraphType, LMessages, Controls, ImgList, LCLIntf, Graphics; type TDbgTreeView = class; PVTVirtualItemNodeEnumeration = ^TVTVirtualItemNodeEnumeration; { TVTVirtualItemNodeEnumerator } TVTVirtualItemNodeEnumerator = class private FNode: PVirtualNode; FCanModeNext: Boolean; FEnumeration: PVTVirtualItemNodeEnumeration; function GetCurrent: PVirtualNode; {$ifdef COMPILER_10_UP}inline;{$endif} public function MoveNext: Boolean; {$ifdef COMPILER_10_UP}inline;{$endif} property Current: PVirtualNode read GetCurrent; end; { TVTVirtualItemNodeEnumeration } TVTVirtualItemNodeEnumeration = object private FTree: TDbgTreeView; FMode: TVZVirtualNodeEnumerationMode; FConsiderChildrenAbove, FIncludeNonItemNodes: Boolean; FControlNodes: Boolean; FNextNode: PVirtualNode; function GetNext(Node: PVirtualNode): PVirtualNode; function DoGetNext(Node: PVirtualNode): PVirtualNode; public function GetEnumerator: TVTVirtualItemNodeEnumerator; end; TDbgTreeNodeData = record Item: TObject; // Must be the first field. Node.AddChild will write the new "Item" at UserData^ (aka the memory at the start of UserData) ImageIndex: Array of Integer; RightSideButton: record ImageIndex: integer; Rect: TRect; end; CachedText: Array of String; CachedColumnData: array of record ShortenWidth1, ShortenWidth2: integer; ShortenResText: string; ColWidth: integer; end; CachedFirstCellLeft: integer; Control: TControl; ControlTop, ControlWidth, ControlHeight: integer; // ControlWidth -1 = full ControlHidden, ControlOutside: Boolean; // below current node, overlaps next PrevControlNode, NextControlNode: PVirtualNode; end; PDbgTreeNodeData = ^TDbgTreeNodeData; TItemRemovedEvent = procedure (Sender: TDbgTreeView; AnItem: TObject; ANode: PVirtualNode) of object; TDetermineDropModeEvent = procedure (const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect; var DropMode: TDropMode) of object; TDbgTreeRightButtonClickEvent = procedure (Sender: TDbgTreeView; ANode: PVirtualNode) of object; { TDbgTreeView } TDbgTreeView = class(TLazVirtualStringTree) private FCheckControlsVisibleLock: integer; FCheckControlsVisibleRunning, FCheckControlsVisibleAgain: Boolean; FInToggle: boolean; FFirstControlNode: PVirtualNode; // not ordered FLazImages: TCustomImageList; FOnDetermineDropMode: TDetermineDropModeEvent; FOnItemRemoved: TItemRemovedEvent; FOnNodeRightButtonClick: TDbgTreeRightButtonClickEvent; // Text/Image function GetNodeImageIndex(Node: PVirtualNode; AColumn: integer): Integer; function GetNodeText(Node: PVirtualNode; AColumn: integer): String; function GetNodeRightButtonImgIdx(Node: PVirtualNode): Integer; function GetNodeRightButtonRect(Node: PVirtualNode): TRect; procedure SetNodeImageIndex(Node: PVirtualNode; AColumn: integer; AValue: Integer); procedure SetNodeText(Node: PVirtualNode; AColumn: integer; AValue: String); procedure SetNodeRightButtonImgIdx(Node: PVirtualNode; AValue: Integer); // Item procedure SetNodeItem(Node: PVirtualNode; AValue: TObject); function GetNodeItem(Node: PVirtualNode): TObject; // Control function GetNodeControl(Node: PVirtualNode): TControl; function GetNodeControlVisible(Node: PVirtualNode): Boolean; function GetNodeControlOutside(Node: PVirtualNode): Boolean; function GetNodeControlWidth(Node: PVirtualNode): Integer; function GetNodeControlHeight(Node: PVirtualNode): Integer; procedure SetNodeControl(Node: PVirtualNode; AValue: TControl); procedure SetNodeControlVisible(Node: PVirtualNode; AValue: Boolean); procedure SetNodeControlOutside(Node: PVirtualNode; AValue: Boolean); procedure SetNodeControlWidth(Node: PVirtualNode; AValue: Integer); procedure SetNodeControlHeight(Node: PVirtualNode; AValue: Integer); procedure ChangeControl(Node: PVirtualNode; NData: PDbgTreeNodeData; AControl: TControl); function GetIsVisible(Node: PVirtualNode): Boolean; reintroduce; procedure SetIsVisible(Node: PVirtualNode; AValue: Boolean); reintroduce; protected procedure DoAllAutoSize; override; procedure CheckControlsVisible; procedure VisibleChanged; override; // Paint / Invalidate procedure EndUpdate; override; function DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect = nil): Boolean; override; function DoCollapsing(Node: PVirtualNode): Boolean; override; procedure DoExpanded(Node: PVirtualNode); override; procedure DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates = []); override; function DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; override; procedure DoBeforeItemErase(ACanvas: TCanvas; ANode: PVirtualNode; const ItemRect: TRect; var AColor: TColor; var EraseAction: TItemEraseAction); override; procedure PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); override; // the background procedure PaintTreeLines(const PaintInfo: TVTPaintInfo; VAlignment, IndentSize: Integer; LineImage: TLineImage); override; procedure PaintNodeButton(ACanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; ButtonX, ButtonY: Integer; ABidiMode: TBiDiMode); override; procedure DoPaintNode(var PaintInfo: TVTPaintInfo); override; // Mouse function DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; override; procedure HandleMouseDown(var Message: TLMMouse; var HitInfo: THitInfo); override; procedure HandleMouseDblClick(var Message: TLMMouse; const HitInfo: THitInfo); override; // Data procedure DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var AText: String); override; function DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var Index: Integer ): TCustomImageList; override; function DoGetNodeWidth(ANode: PVirtualNode; AColumn: TColumnIndex; ACanvas: TCanvas = nil): Integer; override; function DoShortenString(ACanvas: TCanvas; ANode: PVirtualNode; AColumn: TColumnIndex; const S: String; AWidth: Integer; AnEllipsisWidth: Integer = 0): String; override; procedure DoNodeMoved(ANode: PVirtualNode); override; procedure ValidateNodeDataSize(var Size: Integer); override; procedure DoFreeNode(Node: PVirtualNode); override; public function GetFocusedNode(OnlySelected: Boolean = True; AnIncludeControlNodes: Boolean = False): PVirtualNode; function FocusedData(OnlySelected: Boolean = True): PDbgTreeNodeData; function FocusedItem(OnlySelected: Boolean = True): TObject; procedure SelectNode(Node: PVirtualNode; ASetFocus: boolean = True); function FindNodeForItem(AnItem: TObject): PVirtualNode; function FindNodeForControl(AControl: TObject): PVirtualNode; function FindNodeForText(AText: String; AColumn: integer; ATopLvlOnly: Boolean = False): PVirtualNode; procedure DeleteNodeEx(Node: PVirtualNode; FreeItem: Boolean; Reindex: Boolean = True); // LazMoveTo: Don't mess with children procedure LazMoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode); (* NoInitItemNodes, SelectedItemNodes - By default iterate only nodes WITHOUT control - Allow the current node to be deleted, or moved (if moved, it can be seen twice) *) function NoInitItemNodes(ConsiderChildrenAbove: Boolean = False; IncludeNonItemNodes: Boolean = False): TVTVirtualItemNodeEnumeration; function SelectedItemNodes(ConsiderChildrenAbove: Boolean = False; IncludeNonItemNodes: Boolean = False): TVTVirtualItemNodeEnumeration; function ControlNodes: TVTVirtualItemNodeEnumeration; function GetNodeData(Node: PVirtualNode): PDbgTreeNodeData; reintroduce; property NodeItem[Node: PVirtualNode]: TObject read GetNodeItem write SetNodeItem; property NodeText[Node: PVirtualNode; AColumn: integer]: String read GetNodeText write SetNodeText; property NodeImageIndex[Node: PVirtualNode; AColumn: integer]: Integer read GetNodeImageIndex write SetNodeImageIndex; property IsVisible[Node: PVirtualNode]: Boolean read GetIsVisible write SetIsVisible; // May need reintroduction //property NodeHeight[Node: PVirtualNode]: Cardinal read GetNodeHeight write SetNodeHeight; // Control property NodeControl[Node: PVirtualNode]: TControl read GetNodeControl write SetNodeControl; property NodeControlVisible[Node: PVirtualNode]: Boolean read GetNodeControlVisible write SetNodeControlVisible; property NodeControlWidth[Node: PVirtualNode]: Integer read GetNodeControlWidth write SetNodeControlWidth; property NodeControlHeight[Node: PVirtualNode]: Integer read GetNodeControlHeight write SetNodeControlHeight; property NodeControlOutside[Node: PVirtualNode]: Boolean read GetNodeControlOutside write SetNodeControlOutside; property OnItemRemoved: TItemRemovedEvent read FOnItemRemoved write FOnItemRemoved; property OnDetermineDropMode: TDetermineDropModeEvent read FOnDetermineDropMode write FOnDetermineDropMode; property LastDropMode; property LazImages: TCustomImageList read FLazImages write FLazImages; property NodeRightButtonImgIdx[Node: PVirtualNode]: Integer read GetNodeRightButtonImgIdx write SetNodeRightButtonImgIdx; property NodeRightButtonRect[Node: PVirtualNode]: TRect read GetNodeRightButtonRect; property OnNodeRightButtonClick: TDbgTreeRightButtonClickEvent read FOnNodeRightButtonClick write FOnNodeRightButtonClick; end; implementation { TVTVirtualItemNodeEnumerator } function TVTVirtualItemNodeEnumerator.GetCurrent: PVirtualNode; begin Result := FNode; end; function TVTVirtualItemNodeEnumerator.MoveNext: Boolean; begin Result := FCanModeNext; if Result then begin FNode := FEnumeration^.GetNext(FNode); Result := FNode <> nil; FCanModeNext := Result; end; end; { TVTVirtualItemNodeEnumeration } function TVTVirtualItemNodeEnumeration.GetEnumerator: TVTVirtualItemNodeEnumerator; begin Result := TVTVirtualItemNodeEnumerator.Create; Result.FNode := nil; Result.FCanModeNext := True; Result.FEnumeration := @Self; FNextNode := DoGetNext(nil); end; function TVTVirtualItemNodeEnumeration.GetNext(Node: PVirtualNode ): PVirtualNode; begin Result := FNextNode; if FNextNode <> nil then FNextNode := DoGetNext(FNextNode); // if the current node gets deleted or moved, continue from the original pos end; function TVTVirtualItemNodeEnumeration.DoGetNext(Node: PVirtualNode ): PVirtualNode; begin if FControlNodes then begin if Node = nil then Result := FTree.FFirstControlNode else Result := FTree.GetNodeData(Node)^.NextControlNode; exit; end; repeat case FMode of vneNoInit: if Node = nil then Result := FTree.GetFirstNoInit(FConsiderChildrenAbove) else Result := FTree.GetNextNoInit(Node, FConsiderChildrenAbove); vneSelected: if Node = nil then Result := FTree.GetFirstSelected(FConsiderChildrenAbove) else Result := FTree.GetNextSelected(Node, FConsiderChildrenAbove); else Result := nil; end; Node := Result; until (Result=nil) or FIncludeNonItemNodes or (FTree.NodeControl[Result] = nil); end; { TDbgTreeView } function TDbgTreeView.GetNodeImageIndex(Node: PVirtualNode; AColumn: integer ): Integer; var Data: PDbgTreeNodeData; begin Result := -1; Data := GetNodeData(Node); if (Data <> nil) and (AColumn < Length(Data^.ImageIndex)) then Result := Data^.ImageIndex[AColumn] - 1; end; function TDbgTreeView.GetNodeText(Node: PVirtualNode; AColumn: integer): String; var Data: PDbgTreeNodeData; begin Result := ''; Data := GetNodeData(Node); if (Data <> nil) and (AColumn < Length(Data^.CachedText)) then Result := Data^.CachedText[AColumn]; end; function TDbgTreeView.GetNodeRightButtonImgIdx(Node: PVirtualNode): Integer; var Data: PDbgTreeNodeData; begin Result := -1; Data := GetNodeData(Node); if (Data <> nil) then Result := Data^.RightSideButton.ImageIndex - 1; end; function TDbgTreeView.GetNodeRightButtonRect(Node: PVirtualNode): TRect; var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if (Data <> nil) then Result := Data^.RightSideButton.Rect else Result := Rect(-1,-1,-1,-1); end; procedure TDbgTreeView.SetNodeImageIndex(Node: PVirtualNode; AColumn: integer; AValue: Integer); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data <> nil then begin if AColumn >= Length(Data^.ImageIndex) then SetLength(Data^.ImageIndex, AColumn + 1); Data^.ImageIndex[AColumn] := AValue + 1; end; end; procedure TDbgTreeView.SetNodeText(Node: PVirtualNode; AColumn: integer; AValue: String); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data <> nil then begin if AColumn >= Length(Data^.CachedText) then begin SetLength(Data^.CachedText, AColumn + 1); SetLength(Data^.CachedColumnData, AColumn + 1); end; Data^.CachedText[AColumn] := AValue; Data^.CachedColumnData[AColumn].ColWidth := -1; Data^.CachedColumnData[AColumn].ShortenWidth1 := -1; Data^.CachedColumnData[AColumn].ShortenResText := ''; end; end; procedure TDbgTreeView.SetNodeRightButtonImgIdx(Node: PVirtualNode; AValue: Integer); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data <> nil then begin if Data^.RightSideButton.ImageIndex <> AValue then begin InvalidateNode(Node); Data^.RightSideButton.ImageIndex := AValue + 1; end; end; end; procedure TDbgTreeView.SetNodeItem(Node: PVirtualNode; AValue: TObject); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data <> nil then begin if (FOnItemRemoved <> nil) and (Data^.Item <> nil) and (Data^.Item <> AValue) then FOnItemRemoved(Self, Data^.Item, Node); Data^.Item := AValue; end; end; function TDbgTreeView.GetNodeItem(Node: PVirtualNode): TObject; var Data: PDbgTreeNodeData; begin Result := nil; Data := GetNodeData(Node); if Data <> nil then Result := Data^.Item; end; function TDbgTreeView.GetNodeControl(Node: PVirtualNode): TControl; var Data: PDbgTreeNodeData; begin Result := nil; Data := GetNodeData(Node); if Data <> nil then Result := Data^.Control; end; function TDbgTreeView.GetNodeControlVisible(Node: PVirtualNode): Boolean; var Data: PDbgTreeNodeData; begin Result := False; Data := GetNodeData(Node); if Data <> nil then Result := not Data^.ControlHidden; end; function TDbgTreeView.GetNodeControlOutside(Node: PVirtualNode): Boolean; var Data: PDbgTreeNodeData; begin Result := False; Data := GetNodeData(Node); if Data <> nil then Result := Data^.ControlOutside; end; function TDbgTreeView.GetNodeControlWidth(Node: PVirtualNode): Integer; var Data: PDbgTreeNodeData; begin Result := 0; Data := GetNodeData(Node); if Data <> nil then Result := Data^.ControlWidth; end; function TDbgTreeView.GetNodeControlHeight(Node: PVirtualNode): Integer; var Data: PDbgTreeNodeData; begin Result := 0; Data := GetNodeData(Node); if Data <> nil then Result := Data^.ControlHeight; end; procedure TDbgTreeView.SetNodeControl(Node: PVirtualNode; AValue: TControl); var Data: PDbgTreeNodeData; h: LongWord; begin Data := GetNodeData(Node); if Data = nil then exit; if Data^.Control = AValue then exit; Data^.Control.Free; ChangeControl(Node, Data, AValue); if AValue <> nil then begin AValue.Visible := False; AValue.Parent := Self; AValue.AutoSize := False; end; if (Data^.Item <> nil) and (Data^.ControlTop = 0) then begin h := NodeHeight[Node]; Data^.ControlTop := h; Data^.ControlHeight := DefaultNodeHeight; if (Data^.Control <> nil) and (not Data^.ControlHidden) and not Data^.ControlOutside then NodeHeight[Node] := h + Data^.ControlHeight else NodeHeight[Node] := h; end else if AValue = nil then begin if Data^.ControlTop > 0 then NodeHeight[Node] := Data^.ControlTop; Data^.ControlTop := 0; end; end; procedure TDbgTreeView.SetNodeControlVisible(Node: PVirtualNode; AValue: Boolean); var Data: PDbgTreeNodeData; h: LongWord; begin Data := GetNodeData(Node); if Data = nil then exit; if not Data^.ControlHidden = AValue then exit; Data^.ControlHidden := not AValue; if (Data^.ControlTop = 0) then begin h := NodeHeight[Node]; if Data^.Item <> nil then Data^.ControlTop := h; end else h := Data^.ControlTop; if (Data^.Control <> nil) and Data^.ControlHidden then Data^.Control.Visible := False; if (Data^.Item <> nil) then begin if (Data^.Control <> nil) and (not Data^.ControlHidden) and not Data^.ControlOutside then NodeHeight[Node] := h + Data^.ControlHeight else NodeHeight[Node] := h; end; InvalidateToBottom(Node); end; procedure TDbgTreeView.SetNodeControlOutside(Node: PVirtualNode; AValue: Boolean); var Data: PDbgTreeNodeData; h: LongWord; begin Data := GetNodeData(Node); if Data = nil then exit; if Data^.ControlOutside = AValue then exit; Data^.ControlOutside := AValue; if (Data^.ControlTop = 0) then begin h := NodeHeight[Node]; if Data^.Item <> nil then Data^.ControlTop := h; end else h := Data^.ControlTop; if (Data^.Item <> nil) then begin if (Data^.Control <> nil) and (not Data^.ControlHidden) and not Data^.ControlOutside then NodeHeight[Node] := h + Data^.ControlHeight else NodeHeight[Node] := h; end; InvalidateToBottom(Node); end; procedure TDbgTreeView.SetNodeControlWidth(Node: PVirtualNode; AValue: Integer); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data = nil then exit; if Data^.ControlWidth = AValue then exit; Data^.ControlWidth := AValue; InvalidateToBottom(Node); end; procedure TDbgTreeView.SetNodeControlHeight(Node: PVirtualNode; AValue: Integer); var Data: PDbgTreeNodeData; begin Data := GetNodeData(Node); if Data = nil then exit; if AValue < 0 then AValue := 0; if Data^.ControlHeight = AValue then exit; if (Data^.Item = nil) then // control takes full node NodeHeight[Node] := AValue else if (Data^.Control <> nil) and (not Data^.ControlHidden) and not Data^.ControlOutside then NodeHeight[Node] := NodeHeight[Node] + AValue - Data^.ControlHeight; Data^.ControlHeight := AValue; InvalidateToBottom(Node); end; procedure TDbgTreeView.ChangeControl(Node: PVirtualNode; NData: PDbgTreeNodeData; AControl: TControl); var NData2: PDbgTreeNodeData; begin if (NData^.Control = nil) = (AControl = nil) then begin NData^.Control := AControl; exit; end; NData^.Control := AControl; if AControl = nil then begin // node must have had a control if NData^.PrevControlNode = nil then begin assert(FFirstControlNode = Node, 'TDbgTreeView.DoFreeNode: FFirstControlNode = Node'); FFirstControlNode := NData^.NextControlNode; end else begin NData2 := GetNodeData(NData^.PrevControlNode); assert((NData2 <> nil) and (NData2^.NextControlNode = Node), 'TDbgTreeView.DoFreeNode: (NData2 <> nil) and (NData2^.NextControlNode = Node)'); NData2^.NextControlNode := NData^.NextControlNode; end; if NData^.NextControlNode <> nil then begin NData2 := GetNodeData(NData^.NextControlNode); assert((NData2 <> nil) and (NData2^.PrevControlNode = Node), 'TDbgTreeView.DoFreeNode: (NData2 <> nil) and (NData2^.PrevControlNode = Node)'); NData2^.PrevControlNode := NData^.PrevControlNode; end; NData^.PrevControlNode := nil; NData^.NextControlNode := nil; end else begin assert((NData^.NextControlNode = nil) and (NData^.PrevControlNode = nil), 'TDbgTreeView.ChangeControl: (NData^.NextControlNode = nil) and (NData^.PrevControlNode = nil)'); assert(FFirstControlNode <> Node, 'TDbgTreeView.ChangeControl: FFirstControlNode <> Node'); if FFirstControlNode <> nil then begin NData2 := GetNodeData(FFirstControlNode); NData2^.PrevControlNode := Node; NData^.NextControlNode := FFirstControlNode; end; FFirstControlNode := Node; end; end; function TDbgTreeView.GetIsVisible(Node: PVirtualNode): Boolean; begin Result := inherited IsVisible[Node]; end; procedure TDbgTreeView.SetIsVisible(Node: PVirtualNode; AValue: Boolean); var Data: PDbgTreeNodeData; begin inherited IsVisible[Node] := AValue; Data := GetNodeData(Node); if (Data <> nil) and (Data^.Control <> nil) and (not AValue) then Data^.Control.Visible := False; end; procedure TDbgTreeView.DoAllAutoSize; begin FCheckControlsVisibleRunning := True; inherited DoAllAutoSize; FCheckControlsVisibleRunning := False; if FCheckControlsVisibleAgain then CheckControlsVisible; end; procedure TDbgTreeView.CheckControlsVisible; var VNode: PVirtualNode; NData: PDbgTreeNodeData; Y, Y2, H, H2, T, B, offs, Limit: Integer; Ctrl: TControl; Chg: Boolean; r2: TRect; begin FCheckControlsVisibleAgain := FCheckControlsVisibleRunning; if (FCheckControlsVisibleLock > 0) or FCheckControlsVisibleRunning or (FFirstControlNode = nil) or (UpdateCount > 0) or (not TControl(self).IsVisible) or (not HandleAllocated) then exit; DisableAutoSizing; FCheckControlsVisibleRunning := True; try Limit := 5 + ClientHeight div (DefaultNodeHeight * 2); repeat dec(Limit); T := Header.Height; B := ClientHeight - 1 + T; // subtracted by laz.VirtualTree Y := OffsetY + T; Chg := False; for VNode in VisibleNoInitNodes do begin NData := GetNodeData(VNode); offs := 0; H := NodeHeight[VNode]; if NData <> nil then begin Ctrl := NData^.Control; if NData^.ControlHidden then begin if (Ctrl <> nil) then Ctrl.Visible := False; end else if (Ctrl <> nil) then begin offs := NData^.ControlTop; Y2 := Y + offs; if NData^.ControlOutside then H2 := NData^.ControlHeight else H2 := H - offs; if (Y2 < T) or (Y2 >= B) then begin Chg := Chg or Ctrl.Visible; Ctrl.Visible := False; end else begin Chg := Chg or (Ctrl.Top <> Y2) or (Ctrl.Height <> H2) or (Ctrl.Visible <> True); if NData^.ControlOutside then begin Ctrl.SetBounds(OffsetX, Y2, Ctrl.Width, H2); end else begin if (NData^.CachedFirstCellLeft <= 0) then begin r2 := GetDisplayRect(vNode, 0, True, False); NData^.CachedFirstCellLeft := r2.Left; end; Ctrl.SetBounds(NData^.CachedFirstCellLeft + OffsetX, Y2, Ctrl.Width, H2); end; ctrl.Visible := True; H2 := NodeHeight[VNode]; if H <> H2 then Ctrl.Height := H2 - offs; end; end; end; Y := Y + NodeHeight[VNode]; end; until (not FCheckControlsVisibleAgain) or (Limit <= 0); finally FCheckControlsVisibleAgain := False; FCheckControlsVisibleRunning := False; // EnableAutoSizing: Some controls may change their node's height EnableAutoSizing; end; end; procedure TDbgTreeView.VisibleChanged; begin inc(FCheckControlsVisibleLock); inherited VisibleChanged; dec(FCheckControlsVisibleLock); CheckControlsVisible; end; procedure TDbgTreeView.EndUpdate; begin inc(FCheckControlsVisibleLock); inherited EndUpdate; dec(FCheckControlsVisibleLock); CheckControlsVisible; end; function TDbgTreeView.DoSetOffsetXY(Value: TPoint; Options: TScrollUpdateOptions; ClipRect: PRect): Boolean; begin inc(FCheckControlsVisibleLock); DisableAutoSizing; Result := inherited DoSetOffsetXY(Value, Options, ClipRect); dec(FCheckControlsVisibleLock); CheckControlsVisible; EnableAutoSizing; end; function TDbgTreeView.DoCollapsing(Node: PVirtualNode): Boolean; procedure RecursivelyHideControls(N: PVirtualNode); var N2: PVirtualNode; NData: PDbgTreeNodeData; begin while N <> nil do begin NData := GetNodeData(N); if NData^.Control <> nil then NData^.Control.Visible := False; N2 := GetFirstChildNoInit(N); if N2 <> nil then RecursivelyHideControls(N2); N := GetNextSiblingNoInit(N); end; end; var n: PVirtualNode; begin n := GetFirstChildNoInit(Node); if n <> nil then RecursivelyHideControls(n); Result := inherited DoCollapsing(Node); end; procedure TDbgTreeView.DoExpanded(Node: PVirtualNode); begin inc(FCheckControlsVisibleLock); inherited DoExpanded(Node); dec(FCheckControlsVisibleLock); CheckControlsVisible; end; procedure TDbgTreeView.DoStateChange(Enter: TVirtualTreeStates; Leave: TVirtualTreeStates); begin if tsToggling in Enter then begin if not FInToggle then inc(FCheckControlsVisibleLock); FInToggle := True; end; inherited DoStateChange(Enter, Leave); if tsToggling in Leave then begin if FInToggle then dec(FCheckControlsVisibleLock); FInToggle := False; CheckControlsVisible; if not AutoSizeDelayed then // e.g. called by mouseclick / update before syncronizing with dbg-results Update; end; end; function TDbgTreeView.DetermineLineImageAndSelectLevel(Node: PVirtualNode; var LineImage: TLineImage): Integer; var NData: PDbgTreeNodeData; i: Integer; begin Result := inherited DetermineLineImageAndSelectLevel(Node, LineImage); if Length(LineImage) > 1 then LineImage[0] := ltNone else if (Length(LineImage) > 0) and (LineImage[0] <> ltNone) then LineImage[0] := ltRight; NData := GetNodeData(Node); if (NData^.Control <> nil) and (NData^.Item <> nil) then for i := 1 to Length(LineImage) - 1 do if LineImage[i] = ltNone then LineImage[i] := ltTopDown; end; procedure TDbgTreeView.DoPaintNode(var PaintInfo: TVTPaintInfo); var NData: PDbgTreeNodeData; b: LongInt; begin NData := GetNodeData(PaintInfo.Node); b := PaintInfo.ContentRect.Bottom; if NData^.Item = nil then exit; if (NData^.Control <> nil) and (not NData^.ControlHidden) and (not NData^.ControlOutside) then PaintInfo.ContentRect.Bottom := NData^.ControlTop; inherited DoPaintNode(PaintInfo); if (PaintInfo.Column=0) and (FLazImages <> nil) and (NData^.RightSideButton.ImageIndex > 0) then begin NData^.RightSideButton.Rect := PaintInfo.ContentRect; NData^.RightSideButton.Rect.Left := Max( PaintInfo.ContentRect.Right - FLazImages.Width, PaintInfo.ContentRect.Left + FLazImages.Width ); //if PaintInfo.; //DrawEffect := gdeNormal //DrawEffect := gdeShadowed; FLazImages.Draw(PaintInfo.Canvas, NData^.RightSideButton.Rect.Left, (NData^.RightSideButton.Rect.Bottom - NData^.RightSideButton.Rect.Top - FLazImages.Height) div 2, NData^.RightSideButton.ImageIndex - 1, gdeNormal); end; PaintInfo.ContentRect.Bottom := b; end; procedure TDbgTreeView.PaintNodeButton(ACanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; const R: TRect; ButtonX, ButtonY: Integer; ABidiMode: TBiDiMode); var NData: PDbgTreeNodeData; offs: Integer; begin NData := GetNodeData(Node); offs := NData^.ControlTop; if offs > 0 then ButtonY := ButtonY - (r.Bottom - offs) div 2; inherited PaintNodeButton(ACanvas, Node, Column, R, ButtonX, ButtonY, ABidiMode); end; procedure TDbgTreeView.PaintTreeLines(const PaintInfo: TVTPaintInfo; VAlignment, IndentSize: Integer; LineImage: TLineImage); var NData: PDbgTreeNodeData; offs: Integer; PaintI2: TVTPaintInfo; VAlignment2: LongInt; begin NData := GetNodeData(PaintInfo.Node); offs := NData^.ControlTop; PaintI2 := PaintInfo; if offs > 0 then begin PaintI2.CellRect.Top := min(PaintInfo.CellRect.Top + offs, PaintInfo.CellRect.Bottom); VAlignment2 := (PaintI2.CellRect.Bottom - PaintI2.CellRect.Top) div 2 - 1; inherited PaintTreeLines(PaintI2, VAlignment2, IndentSize, LineImage); end; inherited PaintTreeLines(PaintInfo, VAlignment, IndentSize -1, LineImage); end; procedure TDbgTreeView.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); var NData: PDbgTreeNodeData; offs: Integer; b, b2: LongInt; begin NData := GetNodeData(PaintInfo.Node); offs := NData^.ControlTop; if offs = 0 then begin inherited PrepareCell(PaintInfo, WindowOrgX, MaxWidth); exit; end; b := PaintInfo.CellRect.Bottom; b2 := PaintInfo.ContentRect.Bottom; PaintInfo.CellRect.Bottom := PaintInfo.CellRect.Top + offs; PaintInfo.ContentRect.Bottom := PaintInfo.ContentRect.Top + offs; inherited PrepareCell(PaintInfo, WindowOrgX, MaxWidth); PaintInfo.CellRect.Bottom := b; PaintInfo.ContentRect.Bottom := b2; end; procedure TDbgTreeView.DoBeforeItemErase(ACanvas: TCanvas; ANode: PVirtualNode; const ItemRect: TRect; var AColor: TColor; var EraseAction: TItemEraseAction); var NData: PDbgTreeNodeData; offs: Integer; r, r2: TRect; Temp: PVirtualNode; begin inherited DoBeforeItemErase(ACanvas, ANode, ItemRect, AColor, EraseAction); EraseAction := eaColor; NData := GetNodeData(ANode); if NData^.Control <> nil then begin if not NData^.ControlHidden then begin r := ItemRect; if NData^.CachedFirstCellLeft <= 0 then begin r2 := GetDisplayRect(ANode, 0, True, False); NData^.CachedFirstCellLeft := r2.Left; r.Top := r2.Top; end else begin r.Top := OffsetY; Temp := ANode; repeat Temp := GetPreviousVisibleNoInit(Temp, True); if Temp = nil then Break; Inc(r.Top, NodeHeight[Temp]); until False; if hoVisible in Header.Options then inc(r.Top, Header.Height); end; offs := NData^.ControlTop; r.Bottom := r.Top + ItemRect.Bottom - ItemRect.Top; r.Top := r.Top + offs; if r.Top >= Header.Height then begin if NData^.ControlOutside then begin NData^.Control.SetBounds(OffsetX, r.Top, NData^.ControlWidth, NData^.ControlHeight); end else begin r.Left := NData^.CachedFirstCellLeft + OffsetX; if NData^.ControlWidth <= 0 then r.Right := RangeX else r.Right := r.Left+NData^.ControlWidth; NData^.Control.BoundsRect := r; end; NData^.Control.Visible := True; end; end else NData^.Control.Visible := False; end; end; function TDbgTreeView.DetermineDropMode(const P: TPoint; var HitInfo: THitInfo; var NodeRect: TRect): TDropMode; begin if Assigned(HitInfo.HitNode) then begin if ((NodeRect.Top + NodeRect.Bottom) div 2) > P.Y then Result := dmAbove else Result := dmBelow; end else Result := dmNowhere; if FOnDetermineDropMode <> nil then FOnDetermineDropMode(P, HitInfo, NodeRect, Result); end; procedure TDbgTreeView.HandleMouseDown(var Message: TLMMouse; var HitInfo: THitInfo); var NData: PDbgTreeNodeData; begin if Message.Msg = LM_LBUTTONDOWN then DragMode := dmAutomatic else DragMode := dmManual; if (HitInfo.HitColumn = 0) then begin NData := GetNodeData(HitInfo.HitNode); if (NData^.RightSideButton.ImageIndex > 0) and (NData^.RightSideButton.Rect.Top >= 0) and (HitInfo.HitPoint.X >= NData^.RightSideButton.Rect.Left) and (HitInfo.HitPoint.X < NData^.RightSideButton.Rect.Right) then begin if FOnNodeRightButtonClick <> nil then FOnNodeRightButtonClick(Self, HitInfo.HitNode); exit; end; end; inherited HandleMouseDown(Message, HitInfo); end; procedure TDbgTreeView.HandleMouseDblClick(var Message: TLMMouse; const HitInfo: THitInfo); begin if hiOnItemButton in HitInfo.HitPositions then exit; inherited HandleMouseDblClick(Message, HitInfo); if (HitInfo.HitNode = nil) and (HitInfo.HitColumn < 0) then DoNodeDblClick(HitInfo); end; procedure TDbgTreeView.DoGetText(Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var AText: String); begin if Assigned(OnGetText) then begin inherited DoGetText(Node, Column, TextType, AText); end else begin AText := NodeText[Node, Column]; end; end; function TDbgTreeView.DoGetImageIndex(Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var Index: Integer ): TCustomImageList; begin Result := nil; Ghosted := False; Index := GetNodeImageIndex(Node, Column); end; function TDbgTreeView.DoGetNodeWidth(ANode: PVirtualNode; AColumn: TColumnIndex; ACanvas: TCanvas ): Integer; var NData: PDbgTreeNodeData; begin NData := GetNodeData(ANode); if (AColumn > 0) and (AColumn < Length(NData^.CachedText)) then begin Result := NData^.CachedColumnData[AColumn].ColWidth; if Result > 0 then exit; Result := inherited DoGetNodeWidth(ANode, AColumn, ACanvas); NData^.CachedColumnData[AColumn].ColWidth := Result; exit; end; Result := inherited DoGetNodeWidth(ANode, AColumn, ACanvas); end; function TDbgTreeView.DoShortenString(ACanvas: TCanvas; ANode: PVirtualNode; AColumn: TColumnIndex; const S: String; AWidth: Integer; AnEllipsisWidth: Integer): String; var NData: PDbgTreeNodeData; begin NData := GetNodeData(ANode); if (AColumn < Length(NData^.CachedText)) and (s = NData^.CachedText[AColumn]) then begin if (AWidth = NData^.CachedColumnData[AColumn].ShortenWidth1) and (AnEllipsisWidth = NData^.CachedColumnData[AColumn].ShortenWidth2) then begin Result := NData^.CachedColumnData[AColumn].ShortenResText; exit; end; Result := inherited DoShortenString(ACanvas, ANode, AColumn, S, AWidth, AnEllipsisWidth); NData^.CachedColumnData[AColumn].ShortenWidth1 := AWidth; NData^.CachedColumnData[AColumn].ShortenWidth2 := AnEllipsisWidth; NData^.CachedColumnData[AColumn].ShortenResText := Result; exit; end; Result := inherited DoShortenString(ACanvas, ANode, AColumn, S, AWidth, AnEllipsisWidth); end; procedure TDbgTreeView.DoNodeMoved(ANode: PVirtualNode); var NData: PDbgTreeNodeData; i: Integer; N: PVirtualNode; begin inherited DoNodeMoved(ANode); NData := GetNodeData(ANode); for i := 0 to Length(NData^.CachedColumnData) - 1 do begin NData^.CachedColumnData[i].ShortenResText := ''; NData^.CachedColumnData[i].ShortenWidth1 := -1; NData^.CachedColumnData[i].ColWidth := -1; NData^.CachedFirstCellLeft := -1; end; N := GetFirstChildNoInit(ANode); if N <> nil then DoNodeMoved(N); N := GetNextSiblingNoInit(ANode); if N <> nil then DoNodeMoved(N); end; procedure TDbgTreeView.ValidateNodeDataSize(var Size: Integer); begin Size := SizeOf(TDbgTreeNodeData); end; procedure TDbgTreeView.DoFreeNode(Node: PVirtualNode); var NData: PDbgTreeNodeData; begin NData := GetNodeData(Node); if NData <> nil then begin if (FOnItemRemoved <> nil) and (NData^.Item <> nil) then FOnItemRemoved(Self, NData^.Item, Node); NData^.Control.Free; ChangeControl(Node, NData, nil); NData^ := Default(TDbgTreeNodeData); end; inherited DoFreeNode(Node); end; function TDbgTreeView.GetNodeData(Node: PVirtualNode): PDbgTreeNodeData; begin Result := PDbgTreeNodeData(inherited GetNodeData(Node)); end; function TDbgTreeView.GetFocusedNode(OnlySelected: Boolean; AnIncludeControlNodes: Boolean): PVirtualNode; begin Result := FocusedNode; if (not AnIncludeControlNodes) and (Result <> nil) and (NodeControl[Result] <> nil) and (NodeItem[Result] = nil) then Result := nil; if (Result = nil) or (OnlySelected and not Selected[Result]) then Result := nil; end; function TDbgTreeView.FocusedData(OnlySelected: Boolean): PDbgTreeNodeData; var VNode: PVirtualNode; begin Result := nil; VNode := FocusedNode; if (VNode = nil) or (OnlySelected and not Selected[VNode]) then exit; Result := PDbgTreeNodeData(GetNodeData(VNode)); end; function TDbgTreeView.FocusedItem(OnlySelected: Boolean): TObject; var Data: PDbgTreeNodeData; begin Result := nil; Data := FocusedData(OnlySelected); if Data <> nil then Result := Data^.Item; end; procedure TDbgTreeView.SelectNode(Node: PVirtualNode; ASetFocus: boolean); begin ClearSelection; FocusedNode := Node; Selected[Node] := True; end; function TDbgTreeView.FindNodeForItem(AnItem: TObject): PVirtualNode; var VNode: PVirtualNode; begin for VNode in NoInitNodes do begin if GetNodeItem(VNode) = AnItem then exit(VNode); end; Result := nil; end; function TDbgTreeView.FindNodeForControl(AControl: TObject): PVirtualNode; var VNode: PVirtualNode; begin for VNode in NoInitNodes do begin if GetNodeControl(VNode) = AControl then exit(VNode); end; Result := nil; end; function TDbgTreeView.FindNodeForText(AText: String; AColumn: integer; ATopLvlOnly: Boolean): PVirtualNode; var VNode: PVirtualNode; begin VNode := GetFirstNoInit; while VNode <> nil do begin if GetNodeText(VNode, AColumn) = AText then exit(VNode); if ATopLvlOnly then VNode := GetNextSiblingNoInit(VNode) else VNode := GetNextNoInit(VNode); end; Result := nil; end; procedure TDbgTreeView.DeleteNodeEx(Node: PVirtualNode; FreeItem: Boolean; Reindex: Boolean); var Item: TObject; begin if Node = nil then exit; Item := GetNodeItem(Node); DeleteNode(Node, Reindex); if FreeItem then Item.Free; end; procedure TDbgTreeView.LazMoveTo(Source, Target: PVirtualNode; Mode: TVTNodeAttachMode); begin if Target = nil then begin Target := RootNode; Mode := amAddChildFirst; end; if Target = RootNode then begin case Mode of amInsertBefore: Mode := amAddChildFirst; amInsertAfter: Mode := amAddChildLast; end; end; if (Source <> Target) and HasAsParent(Target, Source) then exit; // Disconnect from old location. InternalDisconnectNode(Source, True); // Connect to new location. InternalConnectNode(Source, Target, Self, Mode); DoNodeMoved(Source); InvalidateCache; if (UpdateCount = 0) then begin ValidateCache; UpdateScrollBars(True); Invalidate; end; StructureChange(Source, crNodeMoved); end; function TDbgTreeView.NoInitItemNodes(ConsiderChildrenAbove: Boolean; IncludeNonItemNodes: Boolean): TVTVirtualItemNodeEnumeration; begin Result.FMode := vneNoInit; Result.FTree := Self; Result.FConsiderChildrenAbove := ConsiderChildrenAbove; Result.FControlNodes := False; Result.FIncludeNonItemNodes := IncludeNonItemNodes; end; function TDbgTreeView.SelectedItemNodes(ConsiderChildrenAbove: Boolean; IncludeNonItemNodes: Boolean): TVTVirtualItemNodeEnumeration; begin Result.FMode := vneSelected; Result.FTree := Self; Result.FConsiderChildrenAbove := ConsiderChildrenAbove; Result.FControlNodes := False; Result.FIncludeNonItemNodes := IncludeNonItemNodes; end; function TDbgTreeView.ControlNodes: TVTVirtualItemNodeEnumeration; begin Result.FMode := vneNoInit; Result.FTree := Self; Result.FControlNodes := True; end; initialization RegisterClass(TDbgTreeView); end.