|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Payments Humor Music |
File Array: Borland C++ class to build and store a list of disk filesFileArray is a C++ class that holds a list of disk files as an array of strings. It can build the list from wildcards and provides functions for retrieving filenames from the list in sequential or random order and optionally deleting the file from the array (not the disk) upon retrieval. It can build its list from the contents of a Borland ObjectWindows Library (OWL) TOpenSaveDialog::TData.FileName, and it can output its contents in a way that will display properly in a TEdit control, with one filename to a line. Thus, you can build the file list, from wildcards if desired, allow a user to edit the list in a TEdit, and read the revised list back into the array. It is useful for managing lists of files given as command-line arguments, or that result from drag-and-drop operations, or anywhere else you need to manage a list of files. Many of my programs use it. The FileArray class contains no OWL or Windows-specific code, and I use it in both DOS and Windows programs. It requires my.h, to which there is a link in the code below. An associated utility class provides easy parsing of partial or full file names. There is also a conversion of this class to Microsoft Visual C++ .NET. |
/* filearay.h 1/27/02
Copyright (C)1999-2000, 2002 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
An array for holding a list of files, with handling functions,
including ability to parse the FileList member of a TOpenSaveDialog::TData.
Useful for holding files from TDropInfo lists.
------
TO DO:
I think you can now eliminate TEditControlOutput if you just use LFtoCRLF()
on the output string wherever you use it. Must search all current uses of it first.
------
NOTES:
--This class uses the string >> operator, whose Borland C++ 4.0 library version has a bug.
MYLIB.CPP, containing a fix, must be a part of any project where this is used.
--YOU CAN USE THE LIST OF FILES AS A:
1. Queue from which filenames are deleted as they're retrieved
2. Fixed list through which you cycle.
You should NOT use it both ways in the same program.
--REMEMBER TO SET THE DATA MEMBERS AFTER CONSTRUCTION, IF NECESSARY.
For an ostrstream (for use by a TEdit), setting the stream to ios::binary does
not have the same effect as using TEditControlOutput=TRUE. Apparently,
binary mode only applies to file output. So TEditControlOutput is still necessary.
TEditControlOutput could be eliminated if you could determine within operator << whether the
ostream had been opened in binary mode, but you can't. I only found 1 ostream-derived
class (don't recall which) that even recorded its opening mode, and it is protected.
--the return of empty strings when the list is empty is only for protection. you should always:
if(FileList.GetItemsInContainer())
filename = FileList.GetNext();
*/
#ifndef __FILEARAY_H
#define __FILEARAY_H
#include <dir.h>
#include <classlib\arrays.h>
#include "c:\bcs\my.h"
#pragma hdrstop
//////////////////////////////////////////////////////////////////////////////
// A utility class that provides easy parsing of partial or full file names
// and disk/directory management.
class FilePathParser
{
public:
FilePathParser(const char* path = 0);
FilePathParser(const string& path);
// functions
Parse(const char* path); // reuse the parser on a new filename
BOOL ChDir(); // change default directory to the Drive+Dir it now holds
// useful combinations you can retrieve. they will reflect
// any changes you've made to the basic elements
string FileExt(); // File + Ext
string DriveDir(); // Drive + Dir
string DriveDirFile(); // DriveDir() + File
string DriveDirFileExt(); // DriveDirFile() + Ext
string Entire(); // DriveDirFileExt()
// variables
string Drive; // basic elements. The contents follow the rules of fnsplit().
string Dir; // you can change these directly, but be sure to do it right
string File;
string Ext;
};
//----------------------------------------------------------------------------
// end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
// the TArray is protected to prevent outside use of the TArray functions,
// forcing use of the provided manipulation functions instead.
// Might be better to just make the TArray a member. Haven't been able to decide.
class FileArray : protected TArrayAsVector<string>
{
public:
FileArray(BOOL editcontroloutput = TRUE); // constructor
~FileArray(); // destructor
friend ostream& operator << (ostream& os, const FileArray&); // write
friend istream& operator >> (istream& is, FileArray&); // read
// If you create a newed FileArray f, there is a potential typing conflict
// in the case of: if(f)... Which is used, if(the pointer f != 0) or if((int)f != 0) ?
// Does this problem exist for ios-based classes, which use a similar method?
// Use GetItemsInContainer() in new code, in case I phase out operator int().
// handy for: if(FileArray) or while(FileArray)
operator int() const { return(GetItemsInContainer()); }
// functions
int AddFile(const string& s); // add 1 file OR all matching files if s contains wildcards
int AddDialogList(char *s); // from TOpenFileDialog.TData.FileName
void Flush() { TArrayAsVector<string>::Flush(); current = -1; }
uint GetItemsInContainer() const
{ return(TArrayAsVector<string>::GetItemsInContainer()); }
// FOR SEQUENTIAL USE OF THE FILE NAMES:
string GetNext(BOOL DeleteEntry = TRUE);
// FOR CYCLING THROUGH THE FILE NAMES:
string Next();
string Prev();
string Current();
string Random(); // selects one at random
// write only if they're ever needed
// int Load(const string& filename);
// int Write(const string& filename);
// variables
BOOL TEditControlOutput; // if it's TRUE, operator << uses \r\n instead of endl
protected:
int current; // index into the array, for cycling
};
//----------------------------------------------------------------------------
// end class FileArray
//////////////////////////////////////////////////////////////////////////////
#endif // __FILEARAY_H
/* filearay.cpp 1/27/02
Copyright (C)1999-2000, 2002 Steven Whitney.
Initially published by http://25yearsofprogramming.com.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "c:\bcs\library\filearay.h"
//////////////////////////////////////////////////////////////////////////////
// class FilePathParser code
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const char* path)
{
string s;
if(path)
s = path;
else // if NOTHING provided, get and use currently logged disk & dir
{
char temp[MAXPATH] = {0};
if(getcwd(temp,MAXPATH) == 0) // if error,
temp[0] = 0; // make it a null string
s = temp;
if(s.length()) // must add terminal \ to identify it as a path
s += "\\";
}
Parse(s.c_str());
} //constructor
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const string& path) { Parse(path.c_str()); }
//----------------------------------------------------------------------------
// the various non-inline functions.
string FilePathParser::FileExt() { return File + Ext; }
string FilePathParser::DriveDir() { return Drive + Dir; }
string FilePathParser::DriveDirFile() { return DriveDir() + File; }
string FilePathParser::DriveDirFileExt() { return DriveDirFile() + Ext; }
string FilePathParser::Entire() { return DriveDirFileExt(); }
//----------------------------------------------------------------------------
// call this to reuse the parser on a new filename. (it resets everything).
// if path is ONLY a path, it must end with a "\", or the last dir will be parsed
// as a file name.
BOOL FilePathParser::Parse(const char* path)
{
Drive.remove(0); Dir.remove(0); File.remove(0); Ext.remove(0);
if(!path || !strlen(path))
return FALSE;
char drive[MAXDRIVE] = {0}, dir[MAXDIR] = {0}, file[MAXFILE] = {0}, ext[MAXEXT] = {0};
int flags = fnsplit(path,drive,dir,file,ext);
if(flags & DRIVE) // drive was specified
Drive = drive; // Drive has the :
else // drive NOT specified: use current default
Drive = string((uchar)(getdisk() + 'A')) + ":";
if(flags & DIRECTORY) // dir was successfully parsed
Dir = dir; // dir has leading and trailing \
else // else get default dir for the drive we've just determined
{
int newdrive = toupper(Drive[0]) - '@'; // (default=0), A=1, B=2, etc.
// if specified drive was illegal, an empty Dir is your flag of the error.
if(getcurdir(newdrive,dir) == 0)
Dir = string("\\") + string(dir) + string("\\");
}
if(flags & FILENAME)
File = file;
if(flags & EXTENSION)
Ext = ext;
return TRUE;
} //Parse
//----------------------------------------------------------------------------
// change the current default dir to the one it currently holds, changing disk if necessary.
BOOL FilePathParser::ChDir()
{
if(Drive.length() && Dir.length())
{
setdisk(toupper(Drive[0]) - 'A');
string s(Dir);
int i = s.length() - 1;
if(s[i] == '\\') // remove trailing backslash
s.remove(i);
if(chdir(s.c_str()) == 0)
return TRUE;
}
return FALSE;
} //ChDir
//----------------------------------------------------------------------------
// end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
// class FileArray code
//----------------------------------------------------------------------------
// constructor
FileArray::FileArray(BOOL editcontroloutput) : TArrayAsVector<string>(10,0,10)
{
current = -1; // -1 when array is empty so ++ makes it 0
TEditControlOutput = editcontroloutput; // see operator <<
} // constructor
//----------------------------------------------------------------------------
// destructor
FileArray::~FileArray()
{
Flush();
} // destructor
//----------------------------------------------------------------------------
// output the current list of files, one to a line
// for output to a string, remember to add the << ends after returning.
// A TEdit control is good for editing the list (see wanimate.cpp),
// and requires \r\n instead of endl.
ostream& operator << (ostream& os, const FileArray& a)
{
int N = a.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
os << a[i]; // output the string
if(a.TEditControlOutput) // choose a terminator
os << "\r\n";
else
os << endl;
}
return(os);
}
//----------------------------------------------------------------------------
// add to file list from a stream
istream& operator >> (istream& is, FileArray& a)
{
string s;
while(is >> s)
a.AddFile(s);
return(is);
}
//----------------------------------------------------------------------------
// add 1 file name, OR
// if the given name contains wildcards, find FILES that match the pattern, and add them all.
// could test for legality, but complicated, and a bad name will simply fail on open.
// returns number of filenames added.
int FileArray::AddFile(const string& filename)
{
int count = 0;
// builds a fully qualified name from scratch using either provided values or defaults
FilePathParser f(filename);
if(!f.Dir.length()) // if an explicit or default Disk+Dir couldn't be determined,
return(0); // quit to avoid lots of problems
// you don't HAVE to test for wildcards and avoid searching if there aren't any:
// an unambiguous filename would just be found once by findfirst.
// But the original purpose of this function was to create a list of STRINGS.
// findfirst is for dealing with EXISTING FILES. If user specified a SINGLE file,
// and it doesn't exist, findfirst will fail, and it won't go into the list.
// That's probably the effect you'd want, but there's no provision HERE for warning the user.
// It is better to unconditionally add the STRING now and let the open fail later,
// where user can be informed that named file doesn't exist.
// If there ARE wildcards, you MUST expand them now to whatever files DO exist.
string s = f.Entire();
string path = f.DriveDir();
if(s.contains("?") || s.contains("*"))
{
struct ffblk fileblock;
int done = findfirst(s.c_str(), &fileblock, 0);
while(!done)
{
// retrieved name never has path, so must prepend to each file.
// if the caller wants upper or lower case upon use, it can do it itself.
count += Add(path + fileblock.ff_name);
done = findnext(&fileblock);
}
}
else
count += Add(s); // just 1 file, and Add returns 1 if successful
current = -1; // so that either Current() or Next() will restart at 0
return(count);
} // AddFile
//----------------------------------------------------------------------------
// add files from a TOpenSaveDialog::TData.FileName,
// which contains either 1 legal filename OR multiple entries separated by spaces,
// where the first is a pathname, followed by all the filenames.
// Help|WinAPI says list might contain relative pathnames, but OWL seems to prohibit this.
// remember that multi-selected files are automatically alphabetized.
// If you want a particular order, you must select and add them one at a time.
// returns number of filenames ADDED.
int FileArray::AddDialogList(char* FileName)
{
int filecount = 0; // number of files selected
string pathname; // path to the files (or a single filename)
string filename; // for copying file names into
istrstream is(FileName); // input stream
is >> pathname; // the first entry is a path OR a full file name
is >> filename; // try to read next filename, if any
if(filename.length()) // If there are multiple files,
{
do // prepend pathname to each filename
{
filecount += AddFile(pathname + "\\" + filename); // add them to FileList
}
while(is >> filename); // while there are more filenames to copy
}
else // else just add the one file,
{
if(pathname.length()) // if there was one.
{
filecount += AddFile(pathname);
}
}
current = -1; // so either Current() or Next() will restart at 0
return(filecount);
} // AddDialogList
//----------------------------------------------------------------------------
// for SEQUENTIAL use of the filenames:
//----------------------------------------------------------------------------
// return name of the next file (at index 0), and optionally (usually) delete it.
//
// this could also be BOOL GetNext(string& destination, BOOL delete), that
// fills the string and returns TRUE if there was anything to get; otherwise
// leaves the string unchanged and returns FALSE. Unsure which is more useful.
//
string FileArray::GetNext(BOOL DeleteEntry)
{
string s;
if(GetItemsInContainer())
{
s = (*this)[0];
if(DeleteEntry)
Destroy(0);
}
current = -1; // EITHER Current() or Next() will start at 0
return(s);
} // GetNext
//----------------------------------------------------------------------------
// for CYCLING use of the filenames:
//----------------------------------------------------------------------------
// advances to the next filename, then returns it.
string FileArray::Next()
{
string s;
int N = GetItemsInContainer();
if(N)
{
if(++current >= N)
current = 0;
s = (*this)[current];
}
return(s);
} // Next
//----------------------------------------------------------------------------
// goes back to previous filename, then returns it.
// maybe useful for cycling backwards or back and forth
string FileArray::Prev()
{
string s;
int N = GetItemsInContainer();
if(N)
{
if(--current < 0)
current = N - 1;
s = (*this)[current];
}
return(s);
} // Prev
//----------------------------------------------------------------------------
// returns name of the filename currently pointed at.
string FileArray::Current()
{
string s;
int N = GetItemsInContainer();
if(N)
{
current = range(0, N - 1, current);
s = (*this)[current];
}
return(s);
} // Current
//----------------------------------------------------------------------------
// returns a filename randomly selected from the list. (used sometimes for .MAP color files)
string FileArray::Random()
{
string s;
int N = GetItemsInContainer();
if(N)
s = (*this)[random(N)];
return(s);
} // Current
//----------------------------------------------------------------------------
// end class FileArray
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
Copyright ©2012 Steven Whitney. Last modified Sun 07/29/2012 11:09:19 -0700. |
||