//------------------------------------------------------------------------------
// OWL Extensions (OWLEXT) Class Library
// TDirDialog v. 1.0 - (C) Copyright 1996 by Kent Reisdorph, All Rights Reserved
// Copyright � 1998 by Yura Bidus. All Rights Reserved.
//
// Original code by Kent Reisdorph, 75522,1174
//
//  OVERVIEW
//  ~~~~~~~~
// Source for class TDirDialog - A dialog class that allows
// directory selection similar to Win95's Explorer
//
//----------------------------------------------------------------------------
#include <owlext\pch.h>
#pragma hdrstop
 
#include <owl/treeviewctrl.h>
#include <owl/imagelst.h>
#include <owl/static.h>
#include <owl/filename.h>
 
//#include <stdlib.h>
 
#include <owlext/dirdialg.h>
#include <owlext/dirdialg.rh>
 
namespace OwlExt {
 
using namespace owl;
 
// private function
TModule* FindResourceModule(TWindow* parent, TModule* module, TResId resId, LPCTSTR type);
 
// constants
const int EXPANDED   = 1;
const int QUICK     = 0;
const int DETAIL     = 1;
const int DRIVES     = 2;
const int CDROM     = 3;
 
// Constructor for TData. I decided to structure things using a
// TData class in keeping with the conventions used by the OWL
// common dialog classes. This class is not completely necessary
// at this point but allows for additional functionality later.
 
// Allocates storage to hold the directory path selected.
TDirDialog::TData::TData()
{
  DirPath = new _TCHAR[MAX_PATH];
}
 
// delete the DirPath member
TDirDialog::TData::~TData()
{
  delete[] DirPath;
}
 
// Response table. TTreeView notifications for the selection of
// an item and for the expanding of a node.
DEFINE_RESPONSE_TABLE1(TDirDialog, TDialog)
EV_BN_CLICKED(IDOK, CmOk),
EV_TVN_SELCHANGED(IDC_DIRTREE,   TvnSelChanged),
EV_TVN_ITEMEXPANDED(IDC_DIRTREE,TvnItemExpanded),
END_RESPONSE_TABLE;
 
// constructor
TDirDialog::TDirDialog(TWindow* parent, TData& data, TModule* module)
:
TDialog(parent, IDD_DIRDLG,
    FindResourceModule(parent,module,IDD_DIRDLG,RT_DIALOG)),
    Data(data)
{
  // pointers for the TTreeViewCtrl and the static control where
  // the current directory is displayed
  DirTree    = new TTreeViewCtrl(this, IDC_DIRTREE);
  CurrentDir = new TStatic(this, IDC_CURRENTDIR);
 
  // a TImageList object which holds our bitmaps
  Images = new TImageList(*GetModule(), IDB_DIRDLG_TREEIMAGE, 20, 2,
    TColor(255, 255, 255), IMAGE_BITMAP, 0);
  // set level to 0 initially
  level = 0;
}
 
// Destructor. Deletes the image list object
TDirDialog::~TDirDialog()
{
  delete Images;
}
 
// SetupWindow(). Sets the image list of the tree window and then
// calls LoadTree() to add the initial directory nodes to the
// tree.
void
TDirDialog::SetupWindow()
{
  TDialog::SetupWindow();
  DirTree->SetImageList(*Images);
  LoadTree();
}
 
// CmOk(). Gets the path from the CurrentDir static control
// and places it in TData::DirPath.
void
TDirDialog::CmOk()
{
  CurrentDir->GetText(Data.DirPath, MAX_PATH);
  TDialog::CmOk();
}
 
// TvnSelChanged(). This is the selection change notification
// handler for the tree window.
void
TDirDialog::TvnSelChanged(TTvNotify &)
{
  // get the selected node
  TTreeNode sel = DirTree->GetSelection();
  owl::tstring s;
  // build the path name based on the selection
  BuildPath(s, sel);
  // set the static control's text to the complete path
  CurrentDir->SetText(s.c_str());
}
 
// TvnItemExpanded(). This is the item expanded notification
// handler for the tree window.
void
TDirDialog::TvnItemExpanded(TTvNotify & twn)
{
  // get the node that was expanded or contracted
  TTreeNode node(*DirTree, twn.itemNew.hItem);
 
  // get the TTvItem for the node
  TTvItem item;
  // must do this so that TTvItem knows we will be using the
  // item data
  item.SetItemData(0);
  node.GetItem(&item);
 
  // if the node was contracted then set the item data so that
  // later we know not to rebuild the node.
  if (twn.action == TVE_COLLAPSE) {
    item.SetItemData(EXPANDED);
    node.SetItem(&item);
    return;
  }
 
  // If the node was already built once then we don't need to do
  // it again. If we do, we'll have duplicate entries
  if (item.GetItemData() == EXPANDED)
    return;
 
  // remove the first child because we're going to add it again
  // remember that we have to have already added one child per
  // node so that the plus sign gets added to the node
  TTreeNode child = node.GetChild();
  child.Delete();
 
  // add this node and all subnodes
  AddNode(0, node, DETAIL);
 
  return;
}
// BuildPath(). This function builds the path name by recursing
// backwards through the tree. Use the string class to make
// manipulation of the string easy.
void
TDirDialog::BuildPath(owl::tstring& path, TTreeNode& node)
{
  TAPointer<_TCHAR> buff(new _TCHAR [MAX_PATH]);
 
  // get the parent of the current node
  TTreeNode parent = node.GetParent();
 
  // create a TTvItem with the buffer and get the item
  // from the node. 'buff' will now contain the directory name.
  buff[0] = _T('\0');
  TTvItem item(buff, MAX_PATH);
  node.GetItem(&item);
 
  // add the backslash to the node
  _tcscat(buff, _T("\\"));
 
  // add (prepend) the current directory name to the front of
  // the path. Remember we are stepping backwards through the
  // directory tree.
  path.insert(0, buff);
 
  // if this node has a parent then we have to recurse until
  // no parent is found. This is how we will know that we are
  // done building the directory path string.
  if (parent)
    BuildPath(path, parent);
 
  // If the parent is found then we need to strip the
  // "(" and ")" from the string. We added the parens
  // to distinguish the drive nodes.
  else
  {
    auto pos = path.find(_T("("));
    if (pos != path.npos)
      path.erase(pos, 1);
    pos = path.find(_T(")"));
    if (pos != path.npos)
      path.erase(pos, 1);
  }
}
 
// LoadTree(). Adds the drive nodes. We are only using fixed
// disks and CDROMS.
void
TDirDialog::LoadTree()
{
  TAPointer<_TCHAR> drives(new _TCHAR[MAX_PATH]);
 
  // Get the drive string.
  if (::GetLogicalDriveStrings(MAX_PATH, drives)) {
    _TCHAR* aDrive = drives;
 
    // parse the string
    do {
      int length = static_cast<int>(_tcslen(aDrive)) + 1;
      uint type = ::GetDriveType(aDrive);
 
      // only HDs and CDROMS   //??????????????
      if (type == DRIVE_FIXED || type == DRIVE_CDROM) {
 
        // we'll need to have the type in the AddNode() function
        // so we can use the proper bitmap next to the node name
        if (type == DRIVE_FIXED)
          type = DRIVES;
        else
          type = CDROM;
 
        // get a drive
        if (aDrive[_tcslen(aDrive) - 1] == _T('\\'))
          aDrive[_tcslen(aDrive) - 1] = 0;
 
        // add the node
        AddNode(_tcsupr(aDrive), DirTree->GetRoot(), type);
      }
      aDrive += length;
    } while(*aDrive);
  }
}
 
// AddNode(). This function adds the nodes to the tree. The first
// time we display a node we don't want to add each and every
// sub-directory to the tree. We only want to add nodes when
// necessary or it will take forever for the dialog box to come
// up initially. But, we have to add at least one node so that
// the + sign shows up indicating that sub-directories exist.
void
TDirDialog::AddNode(LPCTSTR path, const TTreeNode& node, int mode)
{
  // add one to the level we are on
  level++;
 
  // temporary storage
  TAPointer<_TCHAR> buff(new _TCHAR [MAX_PATH]);
 
  // by default we will be using image 0 for the node and
  // image 1 for the selected node
  int image1 = 0, image2 = 1;
 
  // need a string object to build the directory path
  owl::tstring dirPath;
  TTreeNode newNode = node;
 
  // if a node name was passed to us then we will add the node
  // if no node name was passed then we need to get a directory
  // name first and then recurse
  if (path) {
 
    // if the mode indicates that we are adding a drive node
    // then we need to:
    // add the parens around the drive name...
    // set the image to correspond to the type of drive...
    // reset the mode to QUICK
    if (mode >= DRIVES) {
      wsprintf(buff, _T("(%s)"), path);
      image1 = image2 = mode;
      mode = QUICK;
    }
 
    // otherwise just use the node name passed
    else
      _tcscpy(buff, path);
 
    // Add the node by creating a TTvItem based on the string,
    // the image, and the select image. Use InsertChild so that
    // the list is sorted as we go.
    newNode = node.InsertChild( TTvItem(buff, image1, image2),
      TTreeNode::Sort );
  }
 
  // first, build a path name based on the node and add wildcards
  BuildPath(dirPath, newNode);
  dirPath += _T("*.*");
#if 1
  TFileNameIterator itr(dirPath);
  while(itr){
    if((*itr).attribute & TFile::Directory){
      if (_tcscmp(itr, _T(".")) == 0)
        ; // don't want it
      else if (_tcscmp(itr, _T("..")) == 0)
        ; // don't want this either
      else {
        // add the node by recursion
        AddNode(itr, newNode, mode);
 
        // update the tree so that we can see the nodes being added
        DirTree->UpdateWindow();
 
        // only want one sub-node per node for now in the QUICK mode
        if (mode == QUICK)
          break;
 
        // In the detail mode we need to fill this branch completely
        // but only one sub-node per node under this branch. Again,
        // we have to do this so the + sign shows up next to the node.
        if (mode == DETAIL && level > 1)
          break;
      }
    }
    ++itr;
  }
#else
  // use findfirst & findnext to get the directories
  ffblk fb;
  bool done = findfirst(dirPath.c_str(), &fb, FA_DIREC);
  while (!done) {
    if (fb.ff_attrib & FA_DIREC) {
      if (strcmp(fb.ff_name, ".") == 0)
        ; // don't want it
      else if (strcmp(fb.ff_name, "..") == 0)
        ; // don't want this either
      else {
 
        // add the node by recursion
        AddNode(fb.ff_name, newNode, mode);
 
        // update the tree so that we can see the nodes being added
        DirTree->UpdateWindow();
 
        // only want one sub-node per node for now in the QUICK mode
        if (mode == QUICK)
          break;
 
        // In the detail mode we need to fill this branch completely
        // but only one sub-node per node under this branch. Again,
        // we have to do this so the + sign shows up next to the node.
        if (mode == DETAIL && level > 1)
          break;
      }
    }
 
    // do it again
    done = findnext(&fb);
  }
#endif
 
  // decrement the level counter
  level--;
}
 
} // OwlExt namespace
 

V817 It is more efficient to seek '(' character rather than a string.

V817 It is more efficient to seek ')' character rather than a string.

V821 Decreased performance. The 'buff' variable can be constructed in a lower level scope.