//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MainForm.h"
#include <ShlObj.hpp>
#include <ClipBrd.hpp>
#include <System.hpp>
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ActnList.hpp>
#include <ComCtrls.hpp>
#include <ExtCtrls.hpp>
#include <Menus.hpp>
#include <ToolWin.hpp>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
typedef struct _DROPFILES {
DWORD pFiles; // offset of file list
POINT pt; // drop point (client coords)
BOOL fNC; // is it on NonClient area
// and pt is in screen coords
BOOL fWide; // WIDE character switch
} DROPFILES, *LPDROPFILES;
//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)
{
FReverseSortOrder[0] = false;
FReverseSortOrder[1] = false;
FReverseSortOrder[2] = false;
FReverseSortOrder[3] = false;
FReverseSortOrder[4] = false;
FThreadCount[0] = 0;
FThreadCount[1] = 0;
FThreadCount[2] = 0;
FStatePanels[0] = pnlState1;
FStatePanels[1] = pnlState2;
FStatePanels[2] = pnlState3;
FCountLabels[0] = lblCount1;
FCountLabels[1] = lblCount2;
FCountLabels[2] = lblCount3;
FPriorityList[0] = lstPriority1;
FPriorityList[1] = lstPriority2;
FPriorityList[2] = lstPriority3;
FFileList[0] = new TStringList();
FFileList[1] = new TStringList();
FFileList[2] = new TStringList();
}
//---------------------------------------------------------------------------
__fastcall TfrmMain::~TfrmMain(void)
{
delete FFileList[0];
delete FFileList[1];
delete FFileList[2];
}
/*
Called by a thread that has completed (or been terminated)
*/
void __fastcall TfrmMain::ThreadCompleted(long ID)
{
// Sanity check
if (ID >= 0 && ID <= 2)
{
// Update the status
FStatePanels[ID]->Color = clRed;
FStatePanels[ID]->Caption = "Done";
FCountLabels[ID]->Caption = IntToStr(FFileList[ID]->Count);
lblCount->Caption = IntToStr(FFileCount);
Application->ProcessMessages();
// If the threads didn't call back for each file, this is the
// only place that the list of files will be available.
if (!chkCallbackForEachFile->Checked)
{
FFileCount += FFileList[ID]->Count;
lblCount->Caption = IntToStr(FFileCount);
FCountLabels[ID]->Caption = IntToStr(FFileList[ID]->Count);
Application->ProcessMessages();
// Display output?
if (chkShowOutput->Checked)
{
// For each file in the list
for (int i = 0; i < FFileList[ID]->Count; i++)
{
// Add a new item and set the caption only
TListItem *item = lstFiles->Items->Add();
item->Caption = FFileList[ID]->Strings[i];
//TListItem *item = lstFiles->Items->Insert(0);
}
}
}
FThreads[ID] = 0; // thread is invalid now
}
}
/*
Called by each thread as a file is found during the search.
*/
void __fastcall TfrmMain::ProcessFile(const TFileFindInfo& FInfo, long ID)
{
// Update couners
FFileCount++;
FThreadCount[ID]++;
// Update UI
FCountLabels[ID]->Caption = IntToStr(FThreadCount[ID]);
lblCount->Caption = IntToStr(FFileCount);
if (chkShowOutput->Checked)
AddFile(FInfo, ID);
Application->ProcessMessages();
}
/*
Adds a new item to the list view
*/
void TfrmMain::AddFile(const TFileFindInfo& FInfo, long ID)
{
// 'Add' creates a new, empty item in the list view
TListItem *item = lstFiles->Items->Add();
//TListItem *item = lstFiles->Items->Insert(0);
// Set each "field" in the list view (in this order)
item->Caption = FInfo.Name;
item->SubItems->Add(FInfo.Path);
// Type
if (FInfo.IsFolder)
item->SubItems->Add(""); // don't display size for folders
else
item->SubItems->Add(FInfo.Size);
item->SubItems->Add(FInfo.Extension);
item->SubItems->Add(FInfo.DateModified);
// Files and folders have different icons
if (FInfo.IsFolder)
item->ImageIndex = 1;
else
item->ImageIndex = 0;
}
/*
For comparing strings
*/
int __fastcall TfrmMain::CompareTextFields(const AnsiString& Item1, const AnsiString& Item2)
{
return CompareText(Item1, Item2);
}
/*
For comparing integers
*/
int __fastcall TfrmMain::CompareIntegerFields(int Item1, int Item2)
{
return Item1 < Item2;
}
/*
For comparing dates
*/
int __fastcall TfrmMain::CompareDateFields(const TDateTime& Item1, const TDateTime& Item2)
{
return Item1 < Item2;
}
/*
These next 5 functions are callbacks for the sort function of the list view.
Each column is hooked up to the appropriate sort function.
*/
void __fastcall TfrmMain::CompareNameField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
if (FReverseSortOrder[0])
Compare = CompareTextFields(Item2->Caption, Item1->Caption);
else
Compare = CompareTextFields(Item1->Caption, Item2->Caption);
}
void __fastcall TfrmMain::ComparePathField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
if (FReverseSortOrder[1])
Compare = CompareTextFields(Item2->SubItems->Strings[0], Item1->SubItems->Strings[0]);
else
Compare = CompareTextFields(Item1->SubItems->Strings[0], Item2->SubItems->Strings[0]);
}
void __fastcall TfrmMain::CompareSizeField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
int size1 = 0, size2 = 0;
if (Item1->SubItems->Strings[1] != "")
size1 = StrToInt(Item1->SubItems->Strings[1]);
if (Item2->SubItems->Strings[1] != "")
size2 = StrToInt(Item2->SubItems->Strings[1]);
if (FReverseSortOrder[2])
Compare = CompareIntegerFields(size1, size2);
else
Compare = CompareIntegerFields(size2, size1);
}
void __fastcall TfrmMain::CompareTypeField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
if (FReverseSortOrder[3])
Compare = CompareTextFields(Item2->SubItems->Strings[2], Item1->SubItems->Strings[2]);
else
Compare = CompareTextFields(Item1->SubItems->Strings[2], Item2->SubItems->Strings[2]);
}
void __fastcall TfrmMain::CompareDateField(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
if (FReverseSortOrder[4])
Compare = CompareDateFields(StrToDateTime(Item1->SubItems->Strings[3]), StrToDateTime(Item2->SubItems->Strings[3]));
else
Compare = CompareDateFields(StrToDateTime(Item2->SubItems->Strings[3]), StrToDateTime(Item1->SubItems->Strings[3]));
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// Event Handlers
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
lstFiles->Align = alClient;
Width = 725;
Height = 750;
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::FormClose(TObject *Sender, TCloseAction &Action)
{
// Stop the threads before closing the form
btnStopAllClick(Sender);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnStartClick(TObject *Sender)
{
FThreadCount[0] = 0;
FThreadCount[1] = 0;
FThreadCount[2] = 0;
FFileCount = 0;
lblCount1->Caption = "0";
lblCount2->Caption = "0";
lblCount3->Caption = "0";
lblCount->Caption = "0";
AnsiString paths[] = {lstPath1->Text, lstPath2->Text, lstPath3->Text};
FFileList[0]->Clear();
FFileList[1]->Clear();
FFileList[2]->Clear();
TStringList *filespecs = new TStringList();
filespecs->Add(lstFileSpecs->Text);
lstFiles->Clear();
// Create 3 thread objects and set their attributes
for (int i = 0; i < 3; i++)
{
FThreads[i] = new TFileFindThread();
FThreads[i]->ID = i;
FThreads[i]->Directory = paths[i];
FThreads[i]->FileSpecs = filespecs;
FThreads[i]->IncludeSubdirectories = chkRecurseSubdirectories->Checked;
FThreads[i]->FileList = FFileList[i];
FThreads[i]->Priority = TThreadPriority(FPriorityList[i]->ItemIndex);
if (chkCallbackForEachFile->Checked)
FThreads[i]->CallbackProc = &ProcessFile;
FThreads[i]->CompletedProc = &ThreadCompleted;
}
// Start the threads
for (int i = 0; i < 3; i++)
{
FThreads[i]->Resume();
FStatePanels[i]->Color = clLime; // I like it better than clGeen
FStatePanels[i]->Caption = "Running";
}
delete filespecs;
}
//---------------------------------------------------------------------------
/*
Column was clicked in the list view, so sort based on the column
*/
void __fastcall TfrmMain::lstFilesColumnClick(TObject *Sender, TListColumn *Column)
{
// Set comparison callback based on the column that was clicked
if (Column->Index == 0)
lstFiles->OnCompare = CompareNameField;
else if (Column->Index == 1)
lstFiles->OnCompare = ComparePathField;
else if (Column->Index == 2)
lstFiles->OnCompare = CompareSizeField;
else if (Column->Index == 3)
lstFiles->OnCompare = CompareTypeField;
else if (Column->Index == 4)
lstFiles->OnCompare = CompareDateField;
else
return;
// enum TSortType { stNone, stData, stText, stBoth };
lstFiles->SortType = Comctrls::stData; // Set sort type
lstFiles->AlphaSort(); // Perform the sort
lstFiles->SortType = Comctrls::stNone; // Reset sort to None
// Toggle sort order (ascending/descending) after each sort
FReverseSortOrder[Column->Index] = !FReverseSortOrder[Column->Index];
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnResumeClick(TObject *Sender)
{
TButton *button = (TButton *)Sender;
if (FThreads[button->Tag])
{
FThreads[button->Tag]->Resume();
FStatePanels[button->Tag]->Color = clLime;
FStatePanels[button->Tag]->Caption = "Running";
}
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnSuspendClick(TObject *Sender)
{
TButton *button = (TButton *)Sender;
if (FThreads[button->Tag])
{
FThreads[button->Tag]->Suspend();
FStatePanels[button->Tag]->Color = clYellow;
FStatePanels[button->Tag]->Caption = "Suspended";
}
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnStopClick(TObject *Sender)
{
TButton *button = (TButton *)Sender;
if (FThreads[button->Tag])
{
FThreads[button->Tag]->Terminate(); // sets terminated flag
FThreads[button->Tag]->Resume(); // need to let the thread clean up
}
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnSuspendAllClick(TObject *Sender)
{
btnSuspendClick(btnSuspend1);
btnSuspendClick(btnSuspend2);
btnSuspendClick(btnSuspend3);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnStopAllClick(TObject *Sender)
{
for (int i = 0; i < 3; i++)
{
if (FThreads[i])
{
FThreads[i]->Terminate(); // set Terminated flag
FThreads[i]->Resume(); // need to let the thread clean up
}
}
}
//---------------------------------------------------------------------------
/*
When a file is double-clicked in the list view, "launch" it.
*/
void __fastcall TfrmMain::lstFilesDblClick(TObject *Sender)
{
// Get selected item (the one that was clicked on)
TListItem *item = lstFiles->Selected;
if (!item)
return;
// There is different data in the list view depending on the state of
// the callback
AnsiString s;
if (chkCallbackForEachFile->Checked)
s = Format("%s", ARRAYOFCONST((item->SubItems->Strings[0] + item->Caption)));
else
s = Format("%s", ARRAYOFCONST((item->Caption)));
// See the online help for the ShellExecute API function and verbs
AnsiString verb = "open";
if (DirectoryExists(s))
{
if (grpFolderShellExecuteVerb->ItemIndex == 1)
verb = "explore";
else if (grpFolderShellExecuteVerb->ItemIndex == 2)
verb = "find";
}
else
{
if (grpFileShellExecuteVerb->ItemIndex == 1)
verb = "edit";
}
ShellExecute(NULL, verb.c_str(), s.c_str(), NULL, NULL, SW_SHOW);
}
//---------------------------------------------------------------------------
/*
Event handler for when a key is down.
*/
void __fastcall TfrmMain::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
// If the keys Control and C are down, copy the selected files to the
// clipboard
if ( (Key == 'C' || Key == 'c') && Shift.Contains(ssCtrl))
CopySelectedToClipboard();
}
//---------------------------------------------------------------------------
/*
Event handler for when an item is selected. Makes it easy to display the
number of items that are currently selected. (Displayed in the status bar.)
*/
void __fastcall TfrmMain::lstFilesSelectItem(TObject *Sender, TListItem *Item, bool Selected)
{
barStatus->SimpleText = " Files selected: " + IntToStr(lstFiles->SelCount);
}
//---------------------------------------------------------------------------
/*
Iterates through the items in the list view. If the item is
highlighted, add it to a list. This list is then passed to
a function that will copy the filenames to the clipboard so
they can be pasted in Explorer.
*/
void TfrmMain::CopySelectedToClipboard(void)
{
// Get the selected item
TListItem* item = lstFiles->Selected;
if (!item)
return;
// Filenames will be put into this list
TStringList *files = new TStringList();
// Loop through all of the highlighted items
while (item != NULL)
{
// Construct full path name
AnsiString file = item->SubItems->Strings[0] + item->Caption;
files->Add(file);
// From the help:
// TListItem *GetNextItem(StartItem, Direction, States)
// Call GetNextItem to find the next list item after StartItem in the
// direction given by the Direction parameter. Only items in the state
// indicated by the States parameter are considered.
item = lstFiles->GetNextItem(item, sdAll, TItemStates() << isSelected) ;
}
// This function formats the filenames for the clipboard copy/paste
PutFilesOnClipboard(files);
delete files;
}
/*
Take a list of filenames and format them so that they can be placed
on the clipboard. The format is simply a long string of characters.
Each filename in the "string" is separated by a NULL character. The
last filename has two NULL characters. As you can see, this is not
a typical C-style NULL-terminated string, so you must be careful
not to treat it as such.
*/
void TfrmMain::PutFilesOnClipboard(TStringList* Filelist)
{
// No files in the list
if (!Filelist->Count)
return;
TDropFiles *DropFiles;
HGLOBAL globalMem;
AnsiString files;
int len = 0;
// Calculate how much space is needed to hold all of the filenames
// and the extra NULL characters.
for (int i = 0; i < Filelist->Count; i++)
{
len += Filelist->Strings[i].Length();
len++; // additional NULL to be included
}
len++; // include the second NULL terminator at the end
// Create the buffer to hold all of the characters
char *buffer = new char[len];
std::memset(buffer, 0, len);
char *next = buffer;
// Copy each filename into the buffer and add an extra NULL character
for (int i = 0; i < Filelist->Count; i++)
{
// Copy the filename starting at 'next' in the buffer
strcpy(next, Filelist->Strings[i].t_str());
// Point at the NULL immediately following this string
next += Filelist->Strings[i].Length();
// Point one further (we need to keep the NULL between filenames)
next++;
}
// Use GlobalAlloc when dealing with the clipboard
globalMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(TDropFiles) + len);
if (!globalMem)
{
ShowMessage("Can't alloc memory.");
delete [] buffer;
return;
}
// Lock the memory (it's moveable)
DropFiles = (TDropFiles *)GlobalLock(globalMem);
if (!DropFiles)
{
ShowMessage("Can't lock memory.");
delete [] buffer;
return;
}
// The size of the struct
DropFiles->pFiles = sizeof(TDropFiles);
void *source = (void*)buffer;
void *dest = (char *)(DropFiles) + sizeof(TDropFiles);
// Copy the data from the buffer to the allocated memory
// Move handles overlaps between the source and destination
Move(source, dest, len);
// Unlock the memory
GlobalUnlock(globalMem);
// Assign the memory to the clipboard (it will free it)
// and tell the clipboard it has drag/drop data
Clipboard()->SetAsHandle(CF_HDROP, (THandle)globalMem);
delete [] buffer;
}