//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MainForm.h"
#include <algorithm>
#include <ctime>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
//---------------------------------------------------------------------------
/*
Constructor
*/
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)
{
AbortProcess = false;
Randomize();
}
/*
Updates the display of the number of nodes in the tree.
*/
void TfrmMain::UpdateCount(void)
{
lblNodeCount->Caption = IntToStr(treeWords->Items->Count);
}
/*
Update the node information display
*/
void TfrmMain::UpdateNodeInfo(TTreeNode *node)
{
if (!node)
{
lblText->Caption = "";
lblIndex->Caption = "";
lblLevel->Caption = "";
lblParent->Caption = "";
}
else
{
lblText->Caption = node->Text;
lblIndex->Caption = IntToStr(node->Index);
lblLevel->Caption = IntToStr(node->Level);
if (node->Parent)
lblParent->Caption = node->Parent->Text;
else
lblParent->Caption = "";
}
}
/*
Checks to see if Ancestor is an ancestor of Child.
*/
bool TfrmMain::IsAncestorNode(TTreeNode *Ancestor, TTreeNode *Child) const
{
if (!Child || !Ancestor)
return false;
TTreeNode *p = Child->Parent;
// Walk up the tree to see if Parent is an ancestor of Child
while (p)
{
if (p == Ancestor)
return true;
p = p->Parent;
}
return false;
}
/*
Moves a node (subtree) to be a child of another node
*/
void TfrmMain::MoveTreeNode(TTreeView *tv, TTreeNode *SourceNode, TTreeNode *DestNode)
{
// Copy the subtree to the destination
CopyTreeNodesRec(tv, SourceNode, SourceNode, DestNode);
// Delete the original sub-tree
SourceNode->Delete();
}
/*
Recursive function that makes a copy of a subtree.
*/
void TfrmMain::CopyTreeNodesRec(TTreeView *tv, TTreeNode *OriginalNode, TTreeNode *SourceNode, TTreeNode *DestNode)
{
// If either is NULL, nothing to do
if (!DestNode || !SourceNode)
return;
// Add a new child and copy the properties and data
TTreeNode *NewNode = tv->Items->AddChild(DestNode, SourceNode->Text);
NewNode->ImageIndex = SourceNode->ImageIndex;
NewNode->StateIndex = SourceNode->StateIndex;
NewNode->SelectedIndex = SourceNode->SelectedIndex;
NewNode->Data = SourceNode->Data; // if there is any data being used
// If there are children, copy them first, recursively (depth-first)
if (SourceNode->HasChildren)
CopyTreeNodesRec(tv, OriginalNode, SourceNode->getFirstChild(), NewNode);
// Copy all siblings, unless at original level (breadth-first)
if (OriginalNode != SourceNode)
CopyTreeNodesRec(tv, OriginalNode, SourceNode->getNextSibling(), DestNode);
}
//******************************************************************************
//******************************************************************************
// Event handlers
//******************************************************************************
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
treeWords->Align = alClient;
pnlRight->DoubleBuffered = true;
pnlRight->BevelOuter = bvNone;
UpdateNodeInfo(0);
// Enable files to be dropped onto the form (a drop target)
DragAcceptFiles(this->Handle, true);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::FormClose(TObject *Sender, TCloseAction &Action)
{
ClearNodes(); // Need to delete the Data
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::FormDestroy(TObject *Sender)
{
// Enable files to be dropped onto the form (a drop target)
DragAcceptFiles(this->Handle, false);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnAddTopLevelNodeClick(TObject *Sender)
{
treeWords->Items->Add(NULL, edtWordToAdd->Text);
UpdateCount();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnAddChildNodeClick(TObject *Sender)
{
TTreeNode *tn = treeWords->Selected;
treeWords->Items->AddChild(tn, edtWordToAdd->Text);
UpdateCount();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnExpandAllClick(TObject *Sender)
{
treeWords->Items->BeginUpdate();
treeWords->FullExpand(); // Expand entire tree
treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnCollapseAllClick(TObject *Sender)
{
treeWords->Items->BeginUpdate();
treeWords->FullCollapse(); // Collapse entire tree
treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------
/*
Simple function to add random nodes to a tree.
*/
void __fastcall TfrmMain::btnPopulateClick(TObject *Sender)
{
treeWords->Items->BeginUpdate();
if (treeWords->Items->Count == 0)
{
for (int i = 0; i < 3; i++)
{
TTreeNode *tn = treeWords->Items->Add(NULL, edtWordToAdd->Text);
tn->ImageIndex = 0;
tn->StateIndex = 5;
}
}
int count = treeWords->Items->Count;
for (int i = count - 1; i >= 0; i--)
{
TTreeNode *tn = treeWords->Items->Item[i];
for (int j = 0; j <= Random(3) + 1; j++)
{
TTreeNode *node = treeWords->Items->AddChild(tn, edtWordToAdd->Text);
node->ImageIndex = std::min(node->Level, lstImages->Count - 1);
node->SelectedIndex = node->ImageIndex;
node->StateIndex = 5;
}
}
UpdateCount();
btnExpandAllClick(Sender);
treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------
/*
Empty the tree.
*/
void __fastcall TfrmMain::btnClearClick(TObject *Sender)
{
ClearNodes();
}
//---------------------------------------------------------------------------
/*
Handle the KeyUp event on the TreeView.
*/
void __fastcall TfrmMain::treeWordsKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
// F2 key is pressed (edit)
if (Key == VK_F2)
{
// Edit the selected node
if (treeWords->Selected)
treeWords->Selected->EditText();
}
else if (Key == VK_DELETE) // Delete key is pressed
{
// Delete the selected node
if (treeWords->Selected)
// If the node is being edited, *don't* delete it, otherwise do.
if (!treeWords->IsEditing())
{
DeleteData(treeWords->Selected); // delete the node Data (recursively)
treeWords->Selected->Delete(); // delete the nodes
UpdateCount();
}
}
}
//---------------------------------------------------------------------------
/*
Checks to see if the items can be dropped on the target.
*/
void __fastcall TfrmMain::treeWordsDragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept)
{
Accept = false;
TTreeView *tv = dynamic_cast<TTreeView *>(Source);
if (!tv)
return;
// Only interested in dropping on our own tree
if (Source != Sender)
return;
Accept = true;
}
//---------------------------------------------------------------------------
/*
Handles nodes being dropped.
*/
void __fastcall TfrmMain::treeWordsDragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
TTreeView *source_tv = (TTreeView*)Source; // The "from" TreeView
TTreeView *destination_tv = (TTreeView *)Sender; // The "To" TreeView
// The node that is being dragged
TTreeNode *sourceNode = source_tv->Selected;
// The node that is being dropped onto
TTreeNode *targetNode = destination_tv->GetNodeAt(X, Y);
// Dropped onto nothing
if (!targetNode)
return;
// Dropped onto the node that is being moved
if (targetNode == sourceNode)
return;
// Dropping onto our parent is no good
if (targetNode == destination_tv->Selected->Parent)
return;
// Can't drop "within" our own subtree
if (IsAncestorNode(sourceNode, targetNode))
return;
// Do the copy
MoveTreeNode(destination_tv, sourceNode, targetNode);
// Show the nodes that were dropped
targetNode->Expand(true);
}
//---------------------------------------------------------------------------
/*
Simple function to populate the tree with a regular pattern. Invaluable for
testing drag and drop behavior. 3 levels with 3 nodes at each level.
*/
void __fastcall TfrmMain::btnSimpleDemoClick(TObject *Sender)
{
treeWords->Items->BeginUpdate();
treeWords->Items->Clear();
treeWords->Items->Add(NULL, "1")->ImageIndex = 0;
treeWords->Items->Add(NULL, "2")->ImageIndex = 0;
treeWords->Items->Add(NULL, "3")->ImageIndex = 0;
int count = treeWords->Items->Count;
for (int i = count - 1; i >= 0; i--)
{
// Add 3 subitems to each node
TTreeNode *tn = treeWords->Items->Item[i];
for (int j = 0; j < 3; j++)
{
AnsiString s = Format("%d.%d", ARRAYOFCONST((i + 1, j + 1)));
TTreeNode *node = treeWords->Items->AddChild(tn, s);
node->ImageIndex = std::min(node->Level, lstImages->Count - 1);
node->SelectedIndex = node->ImageIndex;
// Add 3 subitems to each node
for (int k = 0; k < 3; k++)
{
AnsiString s = Format("%d.%d.%d", ARRAYOFCONST((i + 1, j + 1, k + 1)));
TTreeNode *node2 = treeWords->Items->AddChild(node, s);
node2->ImageIndex = std::min(node2->Level, lstImages->Count - 1);
node2->SelectedIndex = node2->ImageIndex;
}
}
}
UpdateCount();
btnExpandAllClick(Sender);
treeWords->Items->EndUpdate();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::treeWordsChange(TObject *Sender, TTreeNode *Node)
{
UpdateNodeInfo(Node);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::treeWordsMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
TTreeNode *node = treeWords->GetNodeAt(X, Y);
UpdateNodeInfo(node);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::treeWordsMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
TTreeNode *node = treeWords->GetNodeAt(X, Y);
if (!node)
return;
// Expand the entire subtree when on a middle mouse click
// If the Ctrl key is down, collapse the entire subtree
if (Button == mbMiddle)
if (Shift.Contains(ssCtrl))
{
treeWords->Items->BeginUpdate();
node->Collapse(true); // recursively collapse
treeWords->Items->EndUpdate();
}
else
{
treeWords->Items->BeginUpdate();
node->Expand(true); // recursively expand
treeWords->Items->EndUpdate();
}
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::treeWordsMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
TTreeNode *node = treeWords->GetNodeAt(X, Y);
if (!node)
return;
// If the user clicks the left mouse button with the
// Control key down, toggle the state image
if (Button == mbLeft)
if (Shift.Contains(ssCtrl))
node->StateIndex = (node->StateIndex == 5) ? 6 : 5;
}
//---------------------------------------------------------------------------
/*
When files are dropped onto the form, this handles the event.
*/
void __fastcall TfrmMain::WMDropFiles(TWMDropFiles &Message)
{
int num_files, filename_length;
char file_name[MAX_PATH + 1];
// Refer to the Win SDK documentation about the details
// of DragQueryFile and the HDROP structure
// Get the number of files that are being dropped on the app
num_files = DragQueryFile((HDROP)Message.Drop, -1, NULL, 0);
if (num_files < 1)
return;
treeWords->Items->BeginUpdate();
std::clock_t start = std::clock();
lblTime->Caption = "Working...";
// Retrieve each filename, one at a time
for(int i = 0; i < num_files; i++)
{
// Get the length of the i'th filename
filename_length = DragQueryFile((HDROP)Message.Drop, i, NULL, 0);
// Retrieve the i'th filename from the HDROP structure
DragQueryFile((HDROP)Message.Drop, i, file_name, filename_length + 1);
// Add the file/folder to the list (and children, if folder)
AddFiles(file_name);
}
std::clock_t end = std::clock();
lblTime->Caption = Format("%d ms", ARRAYOFCONST((end - start)));
treeWords->Items->EndUpdate();
}
void __fastcall TfrmMain::btnStopClick(TObject *Sender)
{
AbortProcess = true;
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnSortClick(TObject *Sender)
{
treeWords->AlphaSort(true);
}
//---------------------------------------------------------------------------
/*
When an item in the tree is double-clicked, open it with the appropriate
application. See MSDN for more information on ShellExecute and associated
functions, e.g. ShellExecuteEx, and the ShellAPI in shellapi.h.
*/
void __fastcall TfrmMain::treeWordsDblClick(TObject *Sender)
{
// If something is double-clicked, it will be the only one selected
TTreeNode *selnode = treeWords->Selected;
if (!selnode)
return; // should never happen
// The Data stores the entire path (the text is only the filename portion)
// We've stored it as a NULL-terminated string there.
char *str = (char *)selnode->Data;
if (str)
ShellExecute(Handle, "open", str, NULL, NULL, SW_SHOWNORMAL);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::Timer1Timer(TObject *Sender)
{
UpdateCount();
}
//---------------------------------------------------------------------------
//******************************************************************************
//******************************************************************************
// Support functions
//******************************************************************************
//******************************************************************************
/*
Given a filename, add it to the top level of the tree. If Filename is a
directory, recursively add its descendants to the tree at the proper
location.
*/
void TfrmMain::AddFiles(const AnsiString &Filename)
{
// Add at the top level (only the filename portion)
TTreeNode *tn = treeWords->Items->Add(NULL, ExtractFileName(Filename));
// Store the entire name (i.e. including the absolute path) in the Data
AddData(tn, Filename);
// If it's not a directory (it's a file), just add the filename
if (!DirectoryExists(Filename))
{
tn->ImageIndex = 4; // file icon (magic number!)
}
else // otherwise it's a directory, recurse
{
tn->ImageIndex = 1; // folder icon (magic number!)
// Add any children to this node (files/folders)
AddSubFiles(tn, Filename);
}
UpdateCount(); // Update UI counter
}
/*
Adds all files/folders to parent
*/
void TfrmMain::AddSubFiles(TTreeNode *parent, const AnsiString &Directory)
{
TStringList *filespecs = new TStringList();
filespecs->Add("*.*"); // Currently, we want all files
AbortProcess = false;
// Recursively finds and adds all files/folders to the tree
FindFiles(Directory, *filespecs, true, treeWords, parent);
delete filespecs;
}
/*
Finds all files in the directory "Directory" of the type "FileSpec" and
adds them to Tree at parent. Example:
CatalogFiles("C:\Windows\System32", "*.exe", SomeTreeView, SomeNode);
*/
int TfrmMain::CatalogFiles(const AnsiString& Directory, const AnsiString& FileSpec,
TTreeView* Tree, TTreeNode *parent)
{
TSearchRec finfo;
int done, count = 0;
AnsiString dir;
//Make sure there is a trailing "/"
dir = IncludeTrailingPathDelimiter(Directory);
// Prime the search (See help on FindFirst/FindNext)
done = FindFirst(dir + FileSpec, faAnyFile, finfo);
// Until no more files match (or the user stopped it)
while ((done == 0) && (!AbortProcess))
{
// Ignore current directory and parent directory
if ((finfo.Name != ".") && (finfo.Name != ".."))
{
//AnsiString name = Format("[%p] %s", ARRAYOFCONST((parent, finfo.Name)));
AnsiString name = finfo.Name;
// Add the filename to the list
TTreeNode *tn = Tree->Items->AddChild(parent, name /* finfo.Name*/);
// Store the entire name (i.e. including the absolute path) in the Data
AddData(tn, dir + name);
tn->ImageIndex = 4; // default to file icon (magic number!)
count++;
}
// Another match?
done = FindNext(finfo);
// Yield to the application
Application->ProcessMessages();
}
// Done searching
FindClose(finfo);
return count;
}
/*
Finds all files in the directory "Directory" of the type "FileSpec" and
adds them to "List". If "IncludeSubdirectories" is true, it will call
itself recursively. Example:
FindFiles("C:\Windows\System32", "*.exe", true, SomeTreeView, SomeNode);
The real work is done by the CatalogFiles above.
*/
int TfrmMain::FindFiles(const AnsiString& Directory, TStringList& FileSpecs, bool IncludeSubdirectories,
TTreeView *Tree, TTreeNode *parent)
{
TSearchRec finfo;
int done, count = 0;
AnsiString filespec, dir, dirspec;
//Make sure there is a trailing "/"
dir = IncludeTrailingPathDelimiter(Directory);
// Always search for all when looking for directories
dirspec = dir + "*.*";
// Get all files for each spec
for (int i = 0; i < FileSpecs.Count; i++)
{
// Get the next filespec
filespec = FileSpecs[i];
// Find matching files
count += CatalogFiles(dir, filespec, Tree, parent);
}
// Traverse directories?
if (IncludeSubdirectories)
{
// Find the first file that matches
done = FindFirst(dirspec, faAnyFile, finfo);
// While there are more files that match (and the user doesn't abort)
while ((done == 0) && (!AbortProcess))
{
// If it's a directory (and not current nor parent), traverse it
if (((finfo.Attr & faDirectory) != 0) && (finfo.Name != ".") && (finfo.Name != ".."))
{
AnsiString subdir = Format("%s%s", ARRAYOFCONST((dir, finfo.Name)));
// Find this node in the tree and use it as the parent during
// the recursion
TTreeNode *newparent = FindChild(parent, finfo.Name);
if (newparent)
{
newparent->ImageIndex = 1; // folder icon (magic number!)
count += FindFiles(subdir, FileSpecs, IncludeSubdirectories, Tree, newparent);
}
}
// Yield to update the UI
Application->ProcessMessages();
// Another match?
done = FindNext(finfo);
}
FindClose(finfo);
}
return count;
}
/*
Given a TTreeNode and text, find the child containing the text.
*/
TTreeNode * TfrmMain::FindChild(TTreeNode *parent, const AnsiString &str)
{
// Iterate through the children looking for str
TTreeNode *tn = parent->getFirstChild();
while (tn)
{
if (tn->Text == str) // found
return tn;
tn = parent->GetNextChild(tn);
}
return 0; // not found
}
/*
Allocates space for the string and assigns it to the tree node.
*/
void TfrmMain::AddData(TTreeNode *node, const AnsiString &path)
{
if (node)
{
int size = path.Length();
node->Data = new char[size + 1];
strcpy((char *)node->Data, (char *)path.c_str());
}
}
/*
Recursively delete the Data portion of the node
*/
void TfrmMain::DeleteData(TTreeNode *node)
{
if (!node)
return;
delete [] node->Data;
// How many children for this node? TTreeNode::Count is O(N)!
int num_children = node->Count;
for (int i = 0; i < num_children; i++)
DeleteData(node->Item[i]);
}
/*
Iterates through the tree deleting all of the Data and all of the nodes.
*/
void TfrmMain::ClearNodes(void)
{
int count = treeWords->Items->Count;
// Delete the Data stroed in the nodes
for (int i = 0; i < count; i++)
{
//if (!treeWords->Items->Item[i]->Parent)
if (treeWords->Items->Item[i]->Level == 0)
DeleteData(treeWords->Items->Item[i]);
}
treeWords->Items->Clear(); // Delete the nodes
UpdateCount();
UpdateNodeInfo(0);
}