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

#include <vcl.h>
#pragma hdrstop

#include "MainForm.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
//---------------------------------------------------------------------------

/*
  Constructor
*/
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)
{
}
//---------------------------------------------------------------------------

/*
  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;

    // 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 to the list
    AddFile(file_name);
  }
}

/*
  Given a filename (Drive:/Path/Filename.ext), add it to the list view
*/
void TfrmMain::AddFile(const char *Filename)
{
    // First, extract all of the file's information
  FileInfo finfo = GetFileInfo(Filename);

    // 'Add' creates a new, empty item in the list view
  TListItem *item = lstFiles->Items->Add();

    // Set each "field" in the list view
  item->Caption = finfo.name;
  item->SubItems->Add(finfo.size);
  item->SubItems->Add(finfo.extension);
  item->SubItems->Add(finfo.date);
  item->SubItems->Add(finfo.path);

    // Files and folders have different icons
  if (finfo.isfolder)
    item->ImageIndex = 1;
  else
    item->ImageIndex = 0;
}

/*
  Given the name of a file on the disk, retrieve information from it
  (e.g. date/time, size, etc.)
*/
TfrmMain::FileInfo TfrmMain::GetFileInfo(const AnsiString &Filename)
{
  FileInfo finfo;
  TSearchRec rec; // For FindFirst

    // Separate the full name into parts
  finfo.name = ExtractFileName(Filename);
  finfo.path = ExtractFilePath(Filename);
  finfo.extension = ExtractFileExt(Filename);

    // Returns 0 on success TODO: need handle failures at some point
  if (!FindFirst(Filename, faAnyFile, rec))
  {
      // File size (64-bit integer)
    finfo.size = rec.Size;

      // Convert file date format to a "better" format
    finfo.date = FileDateToDateTime(rec.Time);

      // File or Directory?
    if (rec.Attr & faDirectory)
      finfo.isfolder = true;
    else
      finfo.isfolder = false;
  }
  return finfo;
}

/*
  Callback when comparing the first field in the list view
*/
void __fastcall TfrmMain::CompareTextFields(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  Compare = CompareText(Item1->Caption, Item2->Caption);
}

/*
  Callback when comparing the Size field
*/
void __fastcall TfrmMain::CompareIntegerFields(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  Compare = StrToInt(Item2->SubItems->Strings[0]) < StrToInt(Item1->SubItems->Strings[0]);
}

/*
  Callback when comparing the Date field
*/
void __fastcall TfrmMain::CompareDateFields(TObject *Sender, TListItem* Item1, TListItem* Item2, int Data, int& Compare)
{
  Compare = StrToDateTime(Item2->SubItems->Strings[2]) < StrToDateTime(Item1->SubItems->Strings[2]);
}

void TfrmMain::ListViewDragDrop(TListView *Destination, TListView *Source, int X, int Y)
{
  TListItem* currentItem, *nextItem, *dragItem, *dropItem;

    // This is the item that we are dropping on
  dropItem = Destination->GetItemAt(X, Y);

    // This is the item that is selected. There may be more than one
    // highlighted, but only one is the "selected" or "active" item.
  currentItem = Source->Selected;

    // Loop through all of the highlighted items and drop them
  while (currentItem != NULL)
  {
      // 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.
    nextItem = Source->GetNextItem(currentItem, sdAll, TItemStates() << isSelected) ;

      // If drop item is non-NULL, it's a valid position in the list and we
      // will insert there. If it's NULL, then we are below the last item in
      // the list, so we will Add to the end.
    if (dropItem)
      dragItem = Destination->Items->Insert(dropItem->Index);
    else
      dragItem = Destination->Items->Add();

      // Copy the data from the source to the new item
    dragItem->Assign(currentItem);

      // Free the old one (don't want two of them in the list)
    delete currentItem;

      // Move to the next one that is highlighted (if any)
    currentItem = nextItem;
  }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Event Handlers
///////////////////////////////////////////////////////////////////////////////
/*
  Handler for the form create event.
*/
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
  lstFiles->Align = alClient;
  pnlInstructions->BevelOuter = bvNone;

    // Enable files to be dropped onto the form (a drop target)
  DragAcceptFiles(this->Handle, true);
  Width = 800;
  Height = 600;
}
//---------------------------------------------------------------------------

/*
  Handler for the form destroy event.
*/
void __fastcall TfrmMain::FormDestroy(TObject *Sender)
{
    // Disable the form as a drop target
  DragAcceptFiles(this->Handle, false);
}
//---------------------------------------------------------------------------

/*
  Handles the event of clicking on one of the column headers in the list view.
  We need to sort on that 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 = CompareTextFields;
  else if (Column->Index == 1)
    lstFiles->OnCompare = CompareIntegerFields;
  else if (Column->Index == 3)
    lstFiles->OnCompare = CompareDateFields;
  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
}
//---------------------------------------------------------------------------

/*
  This event handler handles *all* of the buttons on the toolbar. It
  checks their Tag to see which one invoked it, so the proper view
  can be set.
*/
void __fastcall TfrmMain::btnBigIconsClick(TObject *Sender)
{
    // TODO: need to deal with the (albeit, unlikely) case that Sender
    //       is not a TButton*
  TButton *button = (TButton *)Sender;

    // Now set the appropriate view
  if (button->Tag == 0)
    lstFiles->ViewStyle = vsReport;
  else if (button->Tag == 1)
    lstFiles->ViewStyle = vsList;
  else if (button->Tag == 2)
    lstFiles->ViewStyle = vsSmallIcon;
  else if (button->Tag == 3)
    lstFiles->ViewStyle = vsIcon;
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::btnBigIcons2Click(TObject *Sender)
{
    // TODO: need to deal with the (albeit, unlikely) case that Sender
    //       is not a TButton*
  TButton *button = (TButton *)Sender;

    // Now set the appropriate view
  if (button->Tag == 0)
    lstFiles2->ViewStyle = vsReport;
  else if (button->Tag == 1)
    lstFiles2->ViewStyle = vsList;
  else if (button->Tag == 2)
    lstFiles2->ViewStyle = vsSmallIcon;
  else if (button->Tag == 3)
    lstFiles2->ViewStyle = vsIcon;
}
//---------------------------------------------------------------------------

/*
  When the items are dropped, this function handles that event.
  Source is the component where the items are coming from and Sender
  is the component being dropped onto. Currently, we're only handling
  the case when we drag and drop on the same list view.
*/
void __fastcall TfrmMain::lstFilesDragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
  ListViewDragDrop((TListView *)Sender, (TListView *)Source, X, Y);
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::lstFiles2DragDrop(TObject *Sender, TObject *Source, int X, int Y)
{
  ListViewDragDrop((TListView *)Sender, (TListView *)Source, X, Y);
}
//---------------------------------------------------------------------------

/*
  When a drag operation is over the list view, this code decides if the
  contents can be dropped. If this function returns true, then the mouse
  pointer changes to indicate it's a drop target. If it returns false,
  then the mouse pointer changes to indicate you can't drop here.
*/
void __fastcall TfrmMain::lstFilesDragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept)
{
  Accept = false;

  TListView *lv = (TListView *)Sender;

    // Get the item that the mouse is over
  TListItem *item = lv->GetItemAt(X, Y);

    // Not going to allow moving an item onto itself.
  if (item && item->Selected && Sender == Source)
    return;

    // Passed the checks, so we will allow it to be dropped.
  Accept = true;
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::lstFiles2DragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept)
{
  Accept = false;

  TListView *lv = (TListView *)Sender;

    // Get the item that the mouse is over
  TListItem *item = lv->GetItemAt(X, Y);

    // Not going to allow moving an item onto itself.
  if (item && item->Selected && Sender == Source)
    return;

    // Passed the checks, so we will allow it to be dropped.
  Accept = true;

}
//---------------------------------------------------------------------------