/*------------------------------------------------------------------------------*
 * File Name: App_Utils.C														*
 * Creation: GJL 7/11/2001														*
 * Purpose: Origin C file containing Application Utilities						*
 * Copyright (c) OriginLab Corp.	2002-2007									*
 * All Rights Reserved															*
 * 																				*
 * Modification Log:															*
 *	 CPY 7/20/02 v7.0347 OSTAT_COMPILE_PROBLEM_WITH_TYPE_ERROR_MSG				*
 *------------------------------------------------------------------------------*/
 
////////////////////////////////////////////////////////////////////////////////////
// Included header files
//////////////////////////////////////////////////////////////////////////////////
//
#include <origin.h>      // main Origin C header, has precompiled file
#include "App_Utils.h"   // Function prototypes

// Include definitions of all localized strings. $ causes CodeBuilder to look for 
// correct version of Local.h depending on Windows Regional settings.
#include "$Local.h"

////////////////////////////////////////////////////////////////////////////////////
// Window related functions
////////////////////////////////////////////////////////////////////////////////////
//
/**
		Create new window using specified template, options, and enumerated base name.
	Example:
		// Create hidden matrix window with no GUI access allowed (From Origin C function)
		string strWinName;
		strWinName = CreateWindow( "Origin.OTM", CREATE_HIDDEN | CREATE_NO_GUI_ACCESS );
		// Create hidden matrix window with no GUI access allowed (From LabTalk script)
		iOption=3 + 131072; // CREATE_HIDDEN | CREATE_NO_GUI_ACCESS == 3 + 0x00020000
		%A=CreateWindow("Origin.OTM",iOption)$;
	Parameters:
		strTemplateName=Input path and name of template to use, default template name is "Origin.OTW",
			if template name is specified with no extension default extension is "OTW"
		iCreateOptions=Input create options as specified by bitwise CREATE_ constants in
			OC_const.h, default is CREATE_HIDDEN (3)  
		strBaseWindowName=Input enumerated base name for created window, default "" automatically
			uses Origin enumerated names like Data1, Data2, Matrix1, Matrix2, Graph1, Graph2, etc.
		bAlwaysEnumerate=Input flag specifying whether or not to enumerate strBaseWindowName
			even if a window by that name does not already exist, default is TRUE 
	Return:
		Returns the name of the newly created window on success and returns "" on failure.  
*/
string CreateWindow( string strTemplateName, int iCreateOptions, string strBaseWindowName,
	 BOOL bAlwaysEnumerate ) // strTemplateName = "Origin.OTW", iCreateOptions = CREATE_HIDDEN, strBaseWindowName = "", bAlwaysEnumerate = TRUE
{
	MatrixPage mpWindow;
	WorksheetPage wpWindow;
	GraphPage gpWindow;
	Page pgWindow;
	string strExt, strWindowName;
	int ii;
	
	strWindowName.Empty();                                      // Init to ""
	
	ii = strTemplateName.Find( '.' );                           // Find . in template name
	if( ii < 0 )                                                // If no . then...
	{
		strExt = "OTW";                                         // Assume extension is OTW
		strTemplateName = strTemplateName + "." + strExt;       // Add . and default extension to template name 
	}
	else                                                        // Else get extension
	{
		strExt =  strTemplateName.Mid(ii + 1);                  // All characters to right of . is extension
		strExt.MakeUpper();                                     // Make upper case to simplify comparison
		if( !strExt.Match("OT?") )                              // If first two characters of extension are not OT return ""
			return strWindowName;
	}

	// Must use derived class to create page and then "cast"
	switch( strExt.GetAt(2) )                                   // First two characters are OT, switch on 3rd character
	{
		// Worksheet template
		case 'W':
			wpWindow.Create( strTemplateName, iCreateOptions ); // Create WorksheetPage
			if( !wpWindow.IsValid() )                           // If WorksheetPage is not valid...
				return strWindowName;                           // Return ""
			pgWindow = (Page) Project.Pages( wpWindow.GetName() ); // Get WorksheetPage as Page (casting does not work)
			break;

		// Graph template
		case 'P':
			gpWindow.Create( strTemplateName, iCreateOptions ); // Create GraphPage
			if( !gpWindow.IsValid() )                           // If GraphPage is not valid...
				return strWindowName;                           // Return ""
			pgWindow = (Page) Project.Pages( gpWindow.GetName() ); // Get GraphPage as Page (casting does not work)
			break;

		// Matrix template
		case 'M':
			mpWindow.Create( strTemplateName, iCreateOptions ); // Create MatrixPage
			if( !mpWindow.IsValid() )                           // If MatrixPage is not valid...
				return strWindowName;                           // Return ""
			pgWindow = (Page) Project.Pages( mpWindow.GetName() ); // Get MatrixPage as Page (casting does not work)
			break;

		// All other cases
		default:
			return strWindowName;                               // Return "" 
			break;
	}
	
	if( pgWindow.IsValid() )                                    // If created window is valid...
	{
		strWindowName = pgWindow.GetName();                     // Get name of created window
		if( !strBaseWindowName.IsEmpty() )                      // If a base window name was specified...
		{
			if( strBaseWindowName.CompareNoCase( strWindowName ) != 0 ) // If base name is not same as the created window...
			{
				if( !Project.Pages( strBaseWindowName ).IsValid() && !bAlwaysEnumerate ) // If window having base name does not exist and user does not want to enumerate
					pgWindow.Rename( strBaseWindowName );       // Don't enumerate, rename window to base name
				else                                            // Else enumerate...
				{						
					for( ii = 1; ii > 0; ii++ )                 // Loop until an enumerated window name does not exist...
					{
						strWindowName.Format( "%s%d", strBaseWindowName, ii ); // Make enumerated window name
						if( !Project.Pages( strWindowName ).IsValid() ) // If window having this name does not exist
						{
							pgWindow.Rename( strWindowName );   // Rename window
							break;                              // Break out of until (for) loop
						}                                       // End window having name
					}                                           // End until loop
				}                                               // End enumerate
			}                                                   // End base name is not same
		}                                                       // End base name was specified
		if( !Project.Pages( strWindowName ).IsValid() )         // If created and possibly renamed window does not exist...
			strWindowName = "";                                 // Return with failure
	}                                                           // End window valid
	
	return strWindowName;                                       // Return newly created window name if successful and "" if not
}

////////////////////////////////////////////////////////////////////////////////////
// Output related functions
////////////////////////////////////////////////////////////////////////////////////
// 
int	Type_Separator( int nSeps )
{
	if( nSeps < 1)
		return APP_UTILS_NO_ERROR;
	
	printf( "\t" );
	
	for( int ii = 1; ii < nSeps; ii++)
	{
		printf( "---------------" );
	}

	printf( "---------------\n" );	

	return APP_UTILS_NO_ERROR;
}

int Type_ErrorMsg( string strErrMsg, int iDestination ) // = WRITE_MESSAGE_BOX
{
	strErrMsg.Write( iDestination);
	return APP_UTILS_NO_ERROR;
}

//----- CPY 7/20/02 v7.0347 OSTAT_COMPILE_PROBLEM
int Type_ErrorMsg1( string strErrMsg, string strErrMsgArg, int iDestination ) // = WRITE_MESSAGE_BOX
{
	string strMsg;
	strMsg.Format( strErrMsg, strErrMsgArg );
	strMsg.Write( iDestination );
	return APP_UTILS_NO_ERROR;
}
//-----

int Type_Insert_Blank_Lines_in_Wks( Worksheet &wksOutputWks, int iRow, int nRows ) // = 1
{
	int ii, iErr;
	
	if( iRow < 0 ) iRow = 0;
	if( nRows < 1 ) nRows = 1;
	
	for( ii = 0; ii < nRows; ii++)
		wksOutputWks.InsertRow( iRow );
	
	iErr = Type_Blank_Lines_To_Wks( wksOutputWks, iRow, nRows );
	
	return iErr;
}	

int Type_Blank_Lines_To_Wks( Worksheet &wksOutputWks, int iRow, int nRows ) // = -1, = 1
{
	int iErr;

	if( iRow < 0 ) iRow = 0;
	if( nRows < 1 ) nRows = 1;
	
	iErr = Set_Range_Of_Wks_Cells( wksOutputWks, APP_UTILS_BLANK_SPACE, iRow, iRow + nRows - 1 );
	
	return iErr;
}

int Type_Separator_Line_To_Wks( Worksheet &wksOutputWks, int iRow )
{
	int iErr;
	
	if( iRow < 0 )
		return APP_UTILS_ERROR1;

	wksOutputWks.SetCell( iRow, 0, APP_UTILS_BLANK_SPACE );
	iErr = Set_Range_Of_Wks_Cells( wksOutputWks, APP_UTILS_SEPARATOR, iRow, iRow, 1 );
	
	return iErr;
}

int	Set_Range_Of_Wks_Cells( Worksheet &wksOutputWks, string strOutputString,
	int iRow1, int iRow2, int jCol1, int jCol2 ) // = APP_UTILS_BLANK_SPACE, = -1, = -1, = -1, = -1
{
	if( iRow1 < 0 ) iRow1 = 0;
	if( iRow2 < 0 ) iRow2 = wksOutputWks.GetNumRows() - 1;
	if( iRow2 < 0 ) iRow2 = 0;	
	if( jCol1 < 0 ) jCol1 = 0;
	if( jCol2 < 0 ) jCol2 = wksOutputWks.GetNumCols() - 1;
	if( jCol2 > wksOutputWks.GetNumCols() - 1 ) jCol2 = wksOutputWks.GetNumCols() - 1;
	if( jCol2 < 0 ) jCol2 = 0;

	if( iRow2 < iRow1 || jCol2 < jCol1 )
		return APP_UTILS_ERROR1;
	
	int ii, jj;
	for( ii = iRow1; ii <= iRow2; ii++ )
	{
		for( jj = jCol1; jj <= jCol2; jj++ )
		{
			wksOutputWks.SetCell( ii, jj, strOutputString ); 
		}
	}

	return APP_UTILS_NO_ERROR;
}

string LocalizeDouble( double dValueToLocalize, string strPrintfFormat ) // = "%f"
{
	string strLocalizedDouble;

	strLocalizedDouble.Format( strPrintfFormat, dValueToLocalize ); // Format, always uses "." for decimal 'point' separator
	strLocalizedDouble.Replace( ".", GetDecimalChar() ); // Replace default "." with localized decimal 'point' separator: "," for G or "." for J/E  

	return strLocalizedDouble;
}

////////////////////////////////////////////////////////////////////////////////////
// GUI related functions
////////////////////////////////////////////////////////////////////////////////////
//
/**
		This function writes to the Data Display Window.
*/
void Write_Data_Display_Text( string strDataDisplayText )
{
	SetDataDisplayText( strDataDisplayText );
}

////////////////////////////////////////////////////////////////////////////////////
// Matrix related functions
////////////////////////////////////////////////////////////////////////////////////
//
int OutputMatrix( matrix &InternalMatrix, string strMatrixName )
{
	int ii, jj;
	
	Matrix MatrixOut( strMatrixName );
	
	MatrixOut.SetSize( InternalMatrix.GetNumRows(), InternalMatrix.GetNumCols() );

	for( ii = 0; ii < InternalMatrix.GetNumRows(); ii++ )
	{
		for( jj = 0; jj < InternalMatrix.GetNumCols(); jj++ )
		{
			MatrixOut[ii][jj] = InternalMatrix[ii][jj];
		}
	}
	
	return APP_UTILS_NO_ERROR;
}

////////////////////////////////////////////////////////////////////////////////////
// Dataset related functions 
////////////////////////////////////////////////////////////////////////////////////
//
BOOL DatasetHasNonNumericValues( Dataset &dsTestData )
{
	int iSize;
	BOOL bErr;
	BasicStats bsStat;
	
	bErr = Data_sum( &dsTestData, &bsStat );
	if( bErr == FALSE )
		return TRUE;			// Return TRUE if Data_sum fails 
	
	iSize = dsTestData.GetSize();	
	
	// Return TRUE if GetSize of vector (with missing values removed) does not equal GetSize of Dataset 
	vector vTest( dsTestData, TRUE );
	if( vTest.GetSize() != iSize )
		return TRUE;	
	
	// Return TRUE if number of numeric values not equal to number of data values 
	if( bsStat.N != iSize )
		return TRUE;
	
	// Return TRUE if dataset contains missing values
	if( bsStat.Missing != 0 )
		return TRUE;
	
	return FALSE;				// Return FALSE if no text and no missing values
}

////////////////////////////////////////////////////////////////////////////////////
// Worksheet related functions
////////////////////////////////////////////////////////////////////////////////////
//
/**
		This function looks for duplicate X,Y pairs in the dataset. If duplciates are found,
		they are replaced with one X,Y pair with a Z value equal to that of the average of
		all duplicate points for that X,Y pair. This function sorts x,y,z data and then
		removes/replaces duplicates.
	Example:
	Parameters:
		wksData=name of worksheet with raw data
		colx=position of x column in data worksheet
		coly=pos of y col
		colz=pos of z col
		wksCopy=name of worksheet copy - x,y,z assumed to be in the first three cols
			method: method for replacing duplicates:
			0: replace with mean
			1: replace with max
			2: replace with min
	Return:
*/
int RemoveDuplicates( string wksData, int colx, int coly, int colz, string wksCopy )
{
	waitCursor removewait;
	Dataset xData(wksData, colx);
	Dataset yData(wksData, coly);
	Dataset zData(wksData, colz);

	Dataset xCopy(wksCopy,0);
	Dataset yCopy(wksCopy,1);
	Dataset zCopy(wksCopy,2);

	// first copy over raw data to copy worksheet and sort it
	int isize = xData.GetSize();

	if( yData.GetSize() != isize) return -1;
	if( zData.GetSize() != isize) return -1;
	
	xCopy = xData;
	yCopy = yData;
	zCopy = zData;		

	// LabTalk section that sorts copy worksheet with x, y columns as primary and secondary keys
	// Sorting is done in ascending order	
	_LT_Obj
	{
		sort.wksname$=wksCopy;
		sort.c1=1;
		sort.c2=3;
		sort.r1=1;
		sort.r2=isize;
		sort.cname1$="A: A";
		sort.cname2$="A: B";
		sort.wks();
	}
	
	// Add another row to the end of the copy worksheet, which is just a copy of the first row
	// This is needed for the algorithm that looks for duplicates
	xCopy.SetSize(isize+1);
	yCopy.SetSize(isize+1);
	zCopy.SetSize(isize+1);
	xCopy[isize] = xCopy[0];
	yCopy[isize] = yCopy[0];
	zCopy[isize] = zCopy[0];
		

	// now look for duplicates in the destination worksheet and replace them	

	// define vectors that will hold the data with duplicates removed
	vector xx, yy, zz;
	xx.SetSize(isize);
	yy.SetSize(isize);
	zz.SetSize(isize);
	
	// define a vector to hold duplicate values
	vector dups;
	dups.SetSize(isize/2);

	// define pointers to step thru the copy worksheet and the destination vectors
	int icpy = 0 , idst = 0;

	int idup = 0;

	do
	{
		if ( (xCopy[icpy] != xCopy[icpy + 1]) | (yCopy[icpy] != yCopy[icpy + 1]) )
		{
			if( idup !=0)
			{
				// replace dups with mean
				dups[idup] = zCopy[icpy];
				double sum = 0.0;
				for (int ii = 0; ii <= idup ; ii++)
				{
					sum += dups[ii];
				}
				sum = sum/(idup+1);
				zz[idst] = sum;
				idup = 0; 				
			}
			else zz[idst] = zCopy[icpy];
			xx[idst] = xCopy[icpy];
			yy[idst] = yCopy[icpy];
			idst++;
		}
		else
		{
			dups[idup] = zCopy[icpy];
			idup++;
		}
		icpy++;
	} while ( icpy < isize );
	

	xCopy.SetSize(idst);
	yCopy.SetSize(idst);
	zCopy.SetSize(idst);
	for (int ii = 0; ii < idst; ii++)
	{
		xCopy[ii] = xx[ii];
		yCopy[ii] = yy[ii];
		zCopy[ii] = zz[ii];
	}	

	return isize-ii;
}

////////////////////////////////////////////////////////////////////////////////////
// File related functions
////////////////////////////////////////////////////////////////////////////////////

/**
		Get the file extension from a string containing a filename. 
	Example:
		string strFile("c:\\somefolder\\myfile.txt");
		string strExt = GetFileExtension(strFile);
		ASSERT(0 == strExt.Compare("txt"));
	Parameters:
		lpcszFile=Input string containing a file name
	Return:
		Returns a string containing the file extension found in the input string.
		If the input string does not contain an extension then an empty string is returned.
*/
string GetFileExtension(LPCSTR lpcszFile)
{
	string str(lpcszFile);
	int nPeriod = str.ReverseFind('.');
	int nSlash = str.ReverseFind('\\');
	if( nPeriod > nSlash )
		str.Delete(0, nPeriod + 1);
	else
		str.Empty();
	return str;
}

/**
		Get the path and/or just filename without extension from a string containing a path and/or
		just filename. 
	Example:
		string strFilenameWithoutExt, strFilenameWithExt;
		strFilenameWithExt = "Default.oaf";
		strFilenameWithoutExt = GetFilenameWithoutExt( strFilenameWithExt );
		ASSERT( strFilenameWithoutExt.Compare( "Default" ) == 0 );
	Parameters:
		strFilenameWithExt=Input string containing an optional path and a filename with filetype extension
	Return:
		Returns a filename without filetype extension (but with path if in original string) from an optional
		path and filename with extension.
*/
string GetFilenameWithoutExt( string strPathAndFilename )
{
	return strPathAndFilename.Left( strPathAndFilename.ReverseFind( '.' ) );
}

/**
		Search a windows folder adding any filenames that match a specified file filter to an output string.
		Options specify whether or not subfolders are to be recursively searched and whether or not path
		and file extension are to be included with filename.
	Example:
		string strTemplateFileList;
		GetFilenamesInFolder(strTemplateFileList, "", "*.otw", FALSE, 3);
	Parameters:
		strListOfFilenames=Output list of formatted filenames found in folder 
		lpcstrPath=Input path to start searching, default "" is path to Origin software folder  
		lpcstrFileFilter=Input file filter which may include DOS wild card characters, default is "*.*"
		bRecursive=Input option to recursively search subfolders, default is FALSE
		wDisplayOptions=Input bitwise flag specifying display options, default value APP_UTILS_FILE_WITH_PATH
			displays path, filename and filetype, APP_UTILS_FILE_NO_PATH displays filename and filetype without
			path, APP_UTILS_FILE_NO_EXT displays path and filename with out filetype extension, and
			APP_UTILS_FILE_NO_PATH | APP_UTILS_FILE_NO_EXT displays file name without path or filetype extension
	Return:
		Returns APP_UTILS_NO_ERROR on success or an APP_UTILS warning or error code on failure.
*/
int GetFilenamesInFolder(
	string& strListOfFilenames,
	LPCSTR lpcstrPath,
	LPCSTR lpcstrFileFilter,
	BOOL bRecursive,
	UINT wDisplayOptions) // lpcstrPath = "", lpcstrFileFilter = "", bRecursive = FALSE, wDisplayOptions = APP_UTILS_FILE_WITH_PATH
{
	string strPath = lpcstrPath;
	if( strPath.IsEmpty() )
		strPath = GetAppPath(TRUE);
	string strFileFilter = lpcstrFileFilter;
	if( strFileFilter.IsEmpty() )
		strFileFilter = "*.*";
	string strSearchPathFilter;
	strSearchPathFilter.Format( "%s*.*", strPath );             // Build search start path\file name
		
	WIN32_FIND_DATAA find;

	HANDLE hFile = FindFirstFile(strSearchPathFilter, &find);   // Find first file/folder that matches
	if( hFile != INVALID_HANDLE_VALUE )                         // If valid handle...
	{
		string strFileFound;
		do                                                      // Do while next file/folder is found...
		{
			if( find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )                     // If folder is next...                     
			{
				if( bRecursive )                                                       // If user wants to recurse...
				{
					strFileFound = find.cFileName;
					if( strFileFound.Compare(".") && strFileFound.Compare("..") )      // If folder is not "." or ".." 
					{
						strFileFound.Format("%s%s\\", strPath, find.cFileName);
						GetFilenamesInFolder(strListOfFilenames, strFileFound,
							strFileFilter, TRUE, wDisplayOptions);                     // Recurse into subfolder
					}
				}
			}
			else                                                                       // Else file is next...
			{
				strFileFound = find.cFileName; 
				if( strFileFound.Match(strFileFilter) )                                // If file name matches file filter...
				{
					if( !(wDisplayOptions & APP_UTILS_FILE_NO_PATH) )                  // If user wants path...
						strFileFound.Format("%s%s", strPath, strFileFound);            // Else use path and filename 
					if( wDisplayOptions & APP_UTILS_FILE_NO_EXT )                      // If user does not want file extension...
						strFileFound = GetFilenameWithoutExt(strFileFound);          // Strip off file extension from filename
					if( !strListOfFilenames.IsEmpty() )                                           // If list already has item...
						strListOfFilenames += "|";                                     // Append token separator
					strListOfFilenames += strFileFound;                                // Append newly found filename to list
				}
			}
		} while( FindNextFile( hFile, &find ) );                // While next file is found... 

		FindClose( hFile );                                     // Close find
		
		return APP_UTILS_NO_ERROR;
	}

	return APP_UTILS_NO_FILES_FOUND_ERROR;
}

/**
		Get the file size of a file from an input string containing a path
		and filename. 
	Example:
		string strFileSize, strDataFile = "C:\\Origin80\\Origin.ini";
		strFileSize = GetFileSize(strDataFile);
	Parameters:
		strPathAndFilename=Input string containing a path and filename with filetype extension
	Return:
		Returns a Windows like file size formatted for display. 
*/
string GetFileSize(string strPathAndFilename)
{
	string strFileSize;
	
	WIN32_FILE_ATTRIBUTE_DATA fileInfo;
	if( GetFileAttributesEx(strPathAndFilename, 0, &fileInfo) )
	{
		if( fileInfo.nFileSizeLow < 1024 )
			strFileSize.Format("%d bytes", fileInfo.nFileSizeLow);
		else
		{
			int nSize = fileInfo.nFileSizeLow/1024.0 + 0.5;
			if( nSize < 1024 )
				strFileSize.Format("%d KB", nSize);
			else
			{
				nSize = nSize/1024.0 + 0.5;
				strFileSize.Format("%d MB", nSize);
			}
		}
	}
	
	return strFileSize;
}

////////////////////////////////////////////////////////////////////////////////////
// Graph related functions
////////////////////////////////////////////////////////////////////////////////////
//
/**
		Builds a list of the layer numbers in the active graph. The list is returned
		as a series of tokens separated by the | character. The active layer number
		is also returned.
	Example:
		string str;
		int iActive;
		iActive = GetLayerNumbers(str);
	Return:
		Returns a list of the layer numbers in the active graph as a series of tokens
		separated by the | character. The layer number of the active layer is returned
		on success and -1 is returned on failure.
*/
int GetLayerNumbers(string& strLayerNumbers)
{
	int iActive = -1;
	GraphPage gp;
	gp = (GraphPage) Project.Pages();
	if( gp.IsValid() )
	{
		for(int ii = 1; ii <= gp.Layers.Count(); ii++)
		{
			if( ii == 1 )
				strLayerNumbers.Format("%d", ii);
			else
				strLayerNumbers.Format("%s|%d", strLayerNumbers, ii);		
		}
		GraphLayer glLayer = gp.Layers();
		if( glLayer.IsValid() )
			iActive = glLayer.GetIndex();
	}

	return iActive;
}

////////////////////////////////////////////////////////////////////////////////////
// File related functions
////////////////////////////////////////////////////////////////////////////////////
//
/**
	List all files of a specified file type found in the Origin
	App Data folder and/or in the Origin software folder. 
	
Example:
	int iRetCode;
	StringArray saResult;
	iRetCode = GetSystemFiles(saResult, "otw");
Parameters:
	saResult = the referrence of string array will receive the finding results
	lpcszExt = the file extension name to matach file finding.
Return:
	Returns TRUE for success, otherwise failure.
*/
BOOL GetSystemFiles(StringArray& saResult, LPCSTR lpcszExt)
{	
	// first, the application data path  
	string strAppDataPath = GetAppPath();
	BOOL bRetApp = FindFiles(saResult, strAppDataPath, lpcszExt);

	// Second, the Origin Exe path
	string strOrgExePath = GetAppPath(TRUE);
	
	// if either of tow seach succeed will return true.
	BOOL bRetExe = FindFiles(saResult, strOrgExePath, lpcszExt, true);// 2nd time will need to check already in list or not
	
	return ( bRetApp || bRetExe );
}

/**
	Get the contents of a text file.
Example:
	string strText;
	int iError = LoadTextFile(strText, "C:\\Origin80\\Origin.ini");
Parameters:
	lpcszFile=Input full path and file name to read
	iNumLines=Input number of lines to read not counting skipped lines, default 0 reads all lines (after iFirstLine) 
	iFirstLine=Input first line to read, default 0 reads first line
	iSkipLines=Input number of lines to skip for every one line read, default 0 skips no lines
Return:
	Returns the contents of a text file on success and "" on failure.  
*/
int LoadTextFile(string &strText, LPCSTR lpcszFile, uint iNumLines, uint iFirstLine, uint iSkipLines) // iNumLines = 0, iFirstLine = 0, iSkipLines = 0
{
	StringArray saLines;
	int iError = ReadFileLines(saLines, lpcszFile, iNumLines, iFirstLine, iSkipLines);
	if( 0 == iError )
	{
		strText.SetTokens(saLines, '\n');
		strText += "\n"; // EOL for last line
	}

	return iError;
}

/**
	Read specified lines from a text file into an array of strings.
Example:
	StringArray saLines;
	int iError = ReadFileLines(saLines, "C:\\Origin80\\Origin.ini");
Parameters:
	saLines=Reference to a string array that will hold the lines
	lpcszFile=Name of file to read
	iNumLines=Number of lines to read into the string array. If 0 then all lines are read.
	iFirstLine=Zero based index of the first line to read into the string array.
	iSkipLines=Number of lines to skip after every one line read.
Return:
	Returns zero for success or non-zero to indicate error.  
*/
int ReadFileLines(StringArray &saLines, LPCSTR lpcszFile, uint iNumLines, uint iFirstLine, uint iSkipLines)
{
	if( NULL == lpcszFile )
		return 1; // file name required
	if( 0 == iNumLines )
		return 0; // no lines to read

	stdioFile sf;
	if( sf.Open(lpcszFile, file::modeRead | file::shareDenyRead | file::typeText) )
	{
		string strLine;
               
		BOOL bCont = TRUE;
		while( iFirstLine-- && bCont )
			bCont = sf.ReadString(strLine);

		if( bCont ) // If can continue reading
		{
			int iLine = 0, iSkip;
			
			while( sf.ReadString(strLine) )
			{
				saLines.Add(strLine);
				if( iNumLines && ++iLine == iNumLines )
					break;
				
				iSkip = iSkipLines;
				while( iSkip-- && bCont )
					bCont = sf.ReadString(strLine);
			}
		}

		sf.Close();
		return 0; // success
	}
	return 1; // failed to open file
}

////////////////////////////////////////////////////////////////////////////////////
// Error redirection functions
////////////////////////////////////////////////////////////////////////////////////
//

/**
	Get the LabTalk error redirection variable.
Example:
	See related function OutputErrorMessage in App_Utils.c.
Return:
	Returns the LabTalk error redirection variable which may have any of the following
	values:
		APP_UTILS_NO_ERROR_OUTPUT
		WRITE_SCRIPT_WINDOW
		WRITE_STATUS_BAR
		WRITE_OUTPUT_LOG
		WRITE_MESSAGE_BOX
		WRITE_COMPILER_OUTPUT
*/
int GetErrOut()
{
	int iErrOut;
	double dErrOut;

	LT_get_var("ErrOut", &dErrOut);

	if( NANUM == dErrOut )
		iErrOut = WRITE_SCRIPT_WINDOW;
	else
		iErrOut = (int) dErrOut;

	if( iErrOut <  APP_UTILS_NO_ERROR_OUTPUT || iErrOut > WRITE_COMPILER_OUTPUT )
		iErrOut = WRITE_SCRIPT_WINDOW;

	return iErrOut;
}

/**
	Output an error message redirected by the LabTalk error redirection variable.
Example:
	if(	PopulateTreeNodeWithNonContiguousSelection(trStat.Data) )
	{
		OutputErrorMessage(SOC_WKS_DATA_SEL_ERROR_MSG);
		return false;
	}
*/
void OutputErrorMessage(LPCSTR lpcszMessage)
{
	string strMsg = lpcszMessage;
	int iErrOut = GetErrOut();
		
	if( strMsg.IsEmpty() || APP_UTILS_NO_ERROR_OUTPUT == iErrOut )
		return;
	
	if( WRITE_MESSAGE_BOX == iErrOut )
		strMsg.Write(iErrOut);
	else
		strMsg.WriteLine(iErrOut);

	return;
}

/**
	Set the LabTalk error redirection variable.
Example:
	SetErrOut(WRITE_MESSAGE_BOX);
Parameters:
	iErrOut=Input LabTalk error redirection variable which may have any of the following
	values:
		APP_UTILS_NO_ERROR_OUTPUT
		WRITE_SCRIPT_WINDOW (default)
		WRITE_STATUS_BAR
		WRITE_OUTPUT_LOG
		WRITE_MESSAGE_BOX
		WRITE_COMPILER_OUTPUT
Return:
	Returns the previous value of the LabTalk error redirection variable.
*/
int SetErrOut(int iErrOut)
{
	double dErrOut;
	int iPreviousErrOut;

	iPreviousErrOut = GetErrOut();

	if( iErrOut <  APP_UTILS_NO_ERROR_OUTPUT || iErrOut > WRITE_COMPILER_OUTPUT )
		iErrOut = WRITE_SCRIPT_WINDOW;

	dErrOut = (double) iErrOut;

	LT_set_var("ErrOut", dErrOut);

	return iPreviousErrOut;
}