{*******************************************************} { } { Tocsg.VTUtil } { } { Copyright (C) 2022 kku } { } {*******************************************************} unit Tocsg.VTUtil; interface uses WinAPi.Windows, VirtualTrees, VirtualTrees.Types, System.Classes, System.SysUtils; type TTgVirtualStringTreeHelper = class helper for TBaseVirtualTree public procedure SortEx(pBeginNode: PVirtualNode; nColumn: TColumnIndex; Direction: TSortDirection; nSortCount: Integer = -1); end; function VT_CountTotalNode(vt: TVirtualStringTree): DWORD; function VT_CountVisibleNode(vt: TVirtualStringTree): DWORD; function VT_CountVisibleChildNode(pNode: PVirtualNode): DWORD; function VT_CountVisibleCheckedNode(vt: TVirtualStringTree): DWORD; procedure VT_ReverseSelected(vt: TVirtualStringTree); procedure VT_CheckAll(var vt: TVirtualStringTree); procedure VT_UnCheckAll(var vt: TVirtualStringTree); procedure VT_ForceCheckNode(pNode: PVirtualNode); procedure VT_ForceUnCheckNode(pNode: PVirtualNode); procedure VT_ReverseCheckAll(vt: TVirtualStringTree); procedure VT_SetEnalbedNode(vt: TVirtualStringTree; pNode: PVirtualNode; bVal: Boolean); function VT_HasDisableNode(vt: TVirtualStringTree; pNode: PVirtualNode): Boolean; procedure VT_ExpandAll(vt: TVirtualStringTree; bFlag: Boolean); procedure VT_ExpandNodeAll(vt: TVirtualStringTree; pNode: PVirtualNode; bFlag: Boolean); function VT_CopyToClipboardSelectedInfo(vt: TVirtualStringTree; nStartColumnIdx: Integer = 1): Integer; function VT_AddChild(vt: TVirtualStringTree; pParent: PVirtualNode = nil): PVirtualNode; inline; function VT_AddChildDataN(vt: TVirtualStringTree; out pAddNode: PVirtualNode; pParent: PVirtualNode = nil): Pointer; overload; inline; function VT_AddChildData(vt: TVirtualStringTree; pParent: PVirtualNode = nil): Pointer; inline; overload; function VT_HasChildNode(vt: TVirtualStringTree; pParent, pFindNode: PVirtualNode): Boolean; function VT_Get1SelNodeData(vt: TVirtualStringTree): Pointer; procedure VT_SortAll(vt: TVirtualStringTree; nColumn: Integer; aDirection: TSortDirection); procedure VT_SortNodeChilds(vt: TVirtualStringTree; pNode: PVirtualNode; nColumn: Integer; aDirection: TSortDirection); function VT_FindNodeFromPath(vt: TVirtualStringTree; sPath: String; sDelimiter: String = '\'; nColumn: Integer = 0): PVirtualNode; procedure VT_SetCheckTypeAllNode(vt: TVirtualStringTree; aCheckType: TCheckType); procedure VT_SetFocuceNode(vt: TVirtualStringTree; pNode: PVirtualNode); procedure VT_Clear(aVT: TVirtualStringTree); implementation uses Vcl.Clipbrd, Tocsg.Safe, Tocsg.Strings; // pBeginNode 부터 nSortCount 수만큼 정렬을 시도한다. // 기본 골격은 TBaseVirtualTree >> procedure Sort(Node: PVirtualNode; Column: TColumnIndex; Direction: TSortDirection; DoInit: Boolean = True); virtual; procedure TTgVirtualStringTreeHelper.SortEx(pBeginNode: PVirtualNode; nColumn: TColumnIndex; Direction: TSortDirection; nSortCount: Integer = -1); function MergeAscending(A, B: PVirtualNode): PVirtualNode; var Dummy: TVirtualNode; CompareResult: Integer; begin // This avoids checking for Result = nil in the loops. Result := @Dummy; while Assigned(A) and Assigned(B) do begin if OperationCanceled then CompareResult := 0 else CompareResult := DoCompare(A, B, nColumn); if CompareResult <= 0 then begin Result.NextSibling := A; Result := A; A := A.NextSibling; end else begin Result.NextSibling := B; Result := B; B := B.NextSibling; end; end; // Just append the list which is not nil (or set end of result list to nil if both lists are nil). if Assigned(A) then Result.NextSibling := A else Result.NextSibling := B; // return start of the new merged list Result := Dummy.NextSibling; end; //--------------------------------------------------------------------------- function MergeDescending(A, B: PVirtualNode): PVirtualNode; var Dummy: TVirtualNode; CompareResult: Integer; begin // this avoids checking for Result = nil in the loops Result := @Dummy; while Assigned(A) and Assigned(B) do begin if OperationCanceled then CompareResult := 0 else CompareResult := DoCompare(A, B, nColumn); if CompareResult >= 0 then begin Result.NextSibling := A; Result := A; A := A.NextSibling; end else begin Result.NextSibling := B; Result := B; B := B.NextSibling; end; end; // Just append the list which is not nil (or set end of result list to nil if both lists are nil). if Assigned(A) then Result.NextSibling := A else Result.NextSibling := B; // Return start of the newly merged list. Result := Dummy.NextSibling; end; //--------------------------------------------------------------------------- function MergeSortAscending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; var A, B: PVirtualNode; begin if N > 1 then begin A := MergeSortAscending(Node, N div 2); B := MergeSortAscending(Node, (N + 1) div 2); Result := MergeAscending(A, B); end else begin Result := Node; Node := Node.NextSibling; Result.NextSibling := nil; end; end; //--------------------------------------------------------------------------- function MergeSortDescending(var Node: PVirtualNode; N: Cardinal): PVirtualNode; // Sorts the list of nodes given by Node (which must not be nil). var A, B: PVirtualNode; begin if N > 1 then begin A := MergeSortDescending(Node, N div 2); B := MergeSortDescending(Node, (N + 1) div 2); Result := MergeDescending(A, B); end else begin Result := Node; Node := Node.NextSibling; Result.NextSibling := nil; end; end; //--------------- end local functions --------------------------------------- var Run: PVirtualNode; Index: Cardinal; i: Integer; bBeginFirstChild: Boolean; pTempNode, pSortCountNextNode, pBeginPrevNode: PVirtualNode; vtState: TVirtualTreeStates; begin with Self do begin InterruptValidation; if tsEditPending in FStates then begin StopTimer(EditTimer); DoStateChange([], [tsEditPending]); end; vtState := FStates; end; if not (tsEditing in vtState) or DoEndEdit then begin if pBeginNode = nil then exit; if nSortCount = -1 then begin nSortCount := 0; pTempNode := pBeginNode; while pTempNode <> nil do begin Inc(nSortCount); pTempNode := pTempNode.NextSibling; end; pSortCountNextNode := nil; end else begin pTempNode := pBeginNode; for i := 1 to nSortCount do begin pTempNode := pTempNode.NextSibling; if pTempNode = nil then begin nSortCount := i; break; end; end; pSortCountNextNode := pTempNode; end; if nSortCount > 1 then begin bBeginFirstChild := false; if pBeginNode.Parent <> nil then begin if pBeginNode.Parent.FirstChild = pBeginNode then bBeginFirstChild := true; end; pBeginPrevNode := pBeginNode.PrevSibling; Index := pBeginNode.Index; StartOperation(okSortNode); try // Sort the linked list, check direction flag only once. if Direction = sdAscending then pTempNode := MergeSortAscending(pBeginNode, nSortCount) else pTempNode := MergeSortDescending(pBeginNode, nSortCount); if pTempNode.Parent <> nil then begin if bBeginFirstChild then pTempNode.Parent.FirstChild := pTempNode; end; finally EndOperation(okSortNode); end; // Consolidate the child list finally. Run := pTempNode; if pBeginPrevNode <> nil then pBeginPrevNode.NextSibling := Run; Run.PrevSibling := pBeginPrevNode; repeat Run.Index := Index; Inc(Index); if Run.NextSibling = nil then Break; Run.NextSibling.PrevSibling := Run; Run := Run.NextSibling; until False; if pSortCountNextNode <> nil then begin Run.NextSibling := pSortCountNextNode; pSortCountNextNode.PrevSibling := Run; end else if (Run.Parent <> nil) and (Run.NextSibling = nil) then Run.Parent.LastChild := Run; InvalidateCache; end; with Self do begin if FUpdateCount = 0 then begin ValidateCache; Invalidate; end; end; end; end; { Other } function VT_CountTotalNode(vt: TVirtualStringTree): DWORD; var pNode: PVirtualNode; begin Result := 0; pNode := vt.GetFirst; while pNode <> nil do begin Inc(Result); pNode := vt.GetNext(pNode); end; end; function VT_CountVisibleNode(vt: TVirtualStringTree): DWORD; var pNode: PVirtualNode; begin Result := 0; pNode := vt.GetFirst; while pNode <> nil do begin if vt.IsVisible[pNode] then Inc(Result); pNode := vt.GetNext(pNode); end; end; function VT_CountVisibleChildNode(pNode: PVirtualNode): DWORD; begin Result := 0; pNode := pNode.FirstChild; while pNode <> nil do begin if vsVisible in pNode.States then Inc(Result); pNode := pNode.NextSibling; end; end; function VT_CountVisibleCheckedNode(vt: TVirtualStringTree): DWORD; var pNode: PVirtualNode; begin Result := 0; pNode := vt.GetFirst; while pNode <> nil do begin if vt.IsVisible[pNode] and (pNode.CheckState = csCheckedNormal) then Inc(Result); pNode := vt.GetNext(pNode); end; end; procedure VT_ReverseSelected(vt: TVirtualStringTree); var pNode: PVirtualNode; begin vt.BeginUpdate; try pNode := vt.GetFirst; while pNode <> nil do begin vt.Selected[pNode] := not vt.Selected[pNode]; pNode := vt.GetNext(pNode); end; finally vt.EndUpdate; end; end; procedure VT_CheckAll(var vt: TVirtualStringTree); var pNode: PVirtualNode; begin vt.BeginUpdate; try pNode := vt.GetFirst; while pNode <> nil do begin if vt.IsVisible[pNode] and not vt.IsDisabled[pNode] then pNode.CheckState := csCheckedNormal; pNode := vt.GetNext(pNode, true); end; finally vt.EndUpdate; end; end; procedure VT_UnCheckAll(var vt: TVirtualStringTree); var pNode: PVirtualNode; begin vt.BeginUpdate; try pNode := vt.GetFirst; while pNode <> nil do begin if vt.IsVisible[pNode] and not vt.IsDisabled[pNode] then // vt.CheckState[pNode] := csUncheckedNormal; pNode.CheckState := csUncheckedNormal; pNode := vt.GetNext(pNode, true); end; finally vt.EndUpdate; end; end; procedure VT_ForceCheckNode(pNode: PVirtualNode); var pChildN: PVirtualNode; begin pNode.CheckState := csCheckedNormal; pChildN := pNode.FirstChild; while pChildN <> nil do begin pChildN.CheckState := csCheckedNormal; if pChildN.ChildCount > 0 then VT_ForceCheckNode(pChildN); pChildN := pChildN.NextSibling; end; end; procedure VT_ForceUnCheckNode(pNode: PVirtualNode); var pChildN: PVirtualNode; begin pNode.CheckState := csUncheckedNormal; pChildN := pNode.FirstChild; while pChildN <> nil do begin pChildN.CheckState := csUncheckedNormal; if pChildN.ChildCount > 0 then VT_ForceCheckNode(pChildN); pChildN := pChildN.NextSibling; end; end; procedure VT_ReverseCheckAll(vt: TVirtualStringTree); var pNode: PVirtualNode; begin vt.BeginUpdate; try pNode := vt.GetFirst; while pNode <> nil do begin if vt.IsVisible[pNode] and not vt.IsDisabled[pNode] then begin if pNode.CheckState = csCheckedNormal then pNode.CheckState := csUncheckedNormal else if pNode.CheckState = csUncheckedNormal then pNode.CheckState := csCheckedNormal; end; pNode := vt.GetNext(pNode); end; finally vt.EndUpdate; end; end; procedure VT_SetEnalbedNode(vt: TVirtualStringTree; pNode: PVirtualNode; bVal: Boolean); begin vt.BeginUpdate; try vt.IsDisabled[pNode] := not bVal; if pNode.ChildCount > 0 then begin pNode := pNode.FirstChild; while pNode <> nil do begin VT_SetEnalbedNode(vt, pNode, bVal); pNode := pNode.NextSibling; end; end; finally vt.EndUpdate; end; end; function VT_HasDisableNode(vt: TVirtualStringTree; pNode: PVirtualNode): Boolean; function CheckDisableNode(aNode: PVirtualNode): Boolean; begin Result := false; if vt.IsDisabled[aNode] then begin Result := true; exit; end; if aNode.ChildCount > 0 then begin aNode := aNode.FirstChild; while aNode <> nil do begin Result := CheckDisableNode(aNode); if Result then exit; aNode := aNode.NextSibling; end; end; end; begin vt.BeginUpdate; try Result := CheckDisableNode(pNode); finally vt.EndUpdate; end; end; procedure VT_ExpandAll(vt: TVirtualStringTree; bFlag: Boolean); var pNode: PVirtualNode; begin vt.BeginUpdate; try pNode := vt.GetFirst; while pNode <> nil do begin vt.Expanded[pNode] := bFlag; pNode := vt.GetNext(pNode); end; finally vt.EndUpdate; end; end; procedure VT_ExpandNodeAll(vt: TVirtualStringTree; pNode: PVirtualNode; bFlag: Boolean); begin vt.BeginUpdate; try while pNode <> nil do begin vt.Expanded[pNode] := bFlag; pNode := vt.GetNext(pNode); end; finally vt.EndUpdate; end; end; function VT_CopyToClipboardSelectedInfo(vt: TVirtualStringTree; nStartColumnIdx: Integer = 1): Integer; var pNode: PVirtualNode; sData: String; i: Integer; cbd: TClipboard; begin Result := 0; pNode := vt.GetFirstSelected; if pNode = nil then begin Result := 1; exit; end; if vt.SelectedCount > 1000 then begin Result := 2; exit; end; if nStartColumnIdx >= vt.Header.Columns.Count then nStartColumnIdx := 0; sData := ''; while pNode <> nil do begin for i := nStartColumnIdx to vt.Header.Columns.Count - 1 do if coVisible in vt.Header.Columns[i].Options then begin sData := sData + vt.Text[pNode, i] + #9; end; sData := sData + #13#10; pNode := vt.GetNextSelected(pNode); end; if sData <> '' then begin Guard(cbd, TClipboard.Create); cbd.AsText := sData; end else Result := 3; end; function VT_AddChild(vt: TVirtualStringTree; pParent: PVirtualNode = nil): PVirtualNode; inline; begin Result := vt.AddChild(pParent); Include(Result.States, vsInitialized); end; function VT_AddChildDataN(vt: TVirtualStringTree; out pAddNode: PVirtualNode; pParent: PVirtualNode = nil): Pointer; inline; begin pAddNode := vt.AddChild(pParent); Include(pAddNode.States, vsInitialized); Result := vt.GetNodeData(pAddNode); end; function VT_AddChildData(vt: TVirtualStringTree; pParent: PVirtualNode = nil): Pointer; inline; overload; var pNode: PVirtualNode; begin Result := VT_AddChildDataN(vt, pNode, pParent); end; function VT_HasChildNode(vt: TVirtualStringTree; pParent, pFindNode: PVirtualNode): Boolean; var pNode: PVirtualNode; begin Result := false; if pParent = nil then exit; if pParent = pFindNode then begin Result := true; exit; end; pNode := pParent.FirstChild; while pNode <> nil do begin Result := VT_HasChildNode(vt, pNode, pFindNode); if Result then exit; pNode := pNode.NextSibling; end; end; function VT_Get1SelNodeData(vt: TVirtualStringTree): Pointer; var pNode: PVirtualNode; begin pNode := vt.GetFirstSelected; if pNode <> nil then Result := vt.GetNodeData(pNode) else Result := nil; end; procedure VT_SortAll(vt: TVirtualStringTree; nColumn: Integer; aDirection: TSortDirection); var pNode: PVirtualNode; begin vt.Sort(nil, nColumn, aDirection); pNode := vt.GetFirst; while pNode <> nil do begin if pNode.ChildCount > 0 then vt.Sort(pNode, nColumn, aDirection); pNode := vt.GetNext(pNode); end; end; procedure VT_SortNodeChilds(vt: TVirtualStringTree; pNode: PVirtualNode; nColumn: Integer; aDirection: TSortDirection); begin vt.Sort(pNode, nColumn, aDirection); if pNode = nil then pNode := vt.RootNode; pNode := pNode.FirstChild; while pNode <> nil do begin if pNode.ChildCount > 0 then VT_SortNodeChilds(vt, pNode, nColumn, aDirection); pNode := pNode.NextSibling; end; end; // 잘 동작하는지 테스트 안됨 22_0119 21:34:10 kku // 확인 되면 이거 지워 ㅇㅇ function VT_FindNodeFromPath(vt: TVirtualStringTree; sPath: String; sDelimiter: String = '\'; nColumn: Integer = 0): PVirtualNode; function GetMatchTextNode(pNode: PVirtualNode; sMatchText: String): PVirtualNode; begin Result := nil; while pNode <> nil do begin if CompareText(vt.Text[pNode, nColumn], sMatchText) = 0 then begin Result := pNode; exit; end; pNode := pNode.NextSibling; end; end; var PathList: TStringList; pNode: PVirtualNode; i: Integer; begin Result := nil; Guard(PathList, TStringList.Create); if SplitString2(sDelimiter, sPath, PathList) = 0 then exit; vt.BeginUpdate; try pNode := vt.RootNode; if pNode = nil then exit; for i := 0 to PathList.Count - 1 do begin Result := GetMatchTextNode(pNode.FirstChild, PathList[i]); if Result = nil then break; end; finally vt.EndUpdate; end; end; procedure VT_SetCheckTypeAllNode(vt: TVirtualStringTree; aCheckType: TCheckType); var pNode: PVirtualNode; begin pNode := vt.GetFirst; while pNode <> nil do begin pNode.CheckType := aCheckType; pNode := vt.GetNext(pNode); end; end; procedure VT_SetFocuceNode(vt: TVirtualStringTree; pNode: PVirtualNode); begin vt.FocusedNode := nil; vt.ClearSelection; vt.FocusedNode := pNode; vt.Selected[pNode] := true; vt.ScrollIntoView(pNode, true); end; procedure VT_Clear(aVT: TVirtualStringTree); begin with aVT.Header do begin if SortColumn <> -1 then begin Columns[SortColumn].Options := Columns[SortColumn].Options + [coParentColor]; SortColumn := -1; end; end; aVT.Clear; end; end.