You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1832 lines
57 KiB
1832 lines
57 KiB
#include "stdafx.h"
|
|
#include "category.h"
|
|
#include "MSInfo5Category.h"
|
|
#include "FileIO.h"
|
|
#include "filestuff.h"
|
|
#include <afxtempl.h>
|
|
#include "fdi.h"
|
|
#include "cabfunc.h"
|
|
#include "dataset.h"
|
|
#include "resource.h"
|
|
#include "msxml.h"
|
|
#include "wbemcli.h"
|
|
|
|
///////////////
|
|
//CMSInfo7Category
|
|
|
|
CMSInfo7Category::CMSInfo7Category()
|
|
{
|
|
this->m_iColCount = 0;
|
|
this->m_iRowCount = 0;
|
|
this->m_pFirstChild = NULL;
|
|
this->m_pNextSibling = NULL;
|
|
this->m_pParent = NULL;
|
|
this->m_pPrevSibling = NULL;
|
|
}
|
|
|
|
CMSInfo7Category::~CMSInfo7Category()
|
|
{
|
|
this->DeleteAllContent();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Static member of CMSInfo7Category
|
|
// Starts the process of reading a file, creating a new CMSInfo7Category object
|
|
// for each category found, and returning a pointer to the root node
|
|
//-----------------------------------------------------------------------------
|
|
|
|
HRESULT CMSInfo7Category::ReadMSI7NFO(CMSInfo7Category** ppRootCat, LPCTSTR szFilename)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
CMSInfo7Category* pRootCat = new CMSInfo7Category();
|
|
|
|
do
|
|
{
|
|
if (!pRootCat)
|
|
break;
|
|
|
|
if (!pRootCat->LoadFromXML(szFilename))
|
|
{
|
|
delete pRootCat;
|
|
pRootCat = NULL;
|
|
break;
|
|
}
|
|
|
|
if (szFilename)
|
|
{
|
|
CString strAppend;
|
|
strAppend.Format(_T(" (%s)"), szFilename);
|
|
pRootCat->m_strCaption += strAppend;
|
|
}
|
|
|
|
hr = S_OK;
|
|
}
|
|
while (false);
|
|
|
|
*ppRootCat = pRootCat;
|
|
return hr;
|
|
}
|
|
|
|
BOOL CMSInfo7Category::LoadFromXML(LPCTSTR szFilename)
|
|
{
|
|
CComPtr<IXMLDOMDocument> pDoc;
|
|
CComPtr<IXMLDOMNode> pNode;
|
|
HRESULT hr;
|
|
VARIANT_BOOL vb;
|
|
BOOL retVal = FALSE;
|
|
|
|
CoInitialize(NULL);
|
|
|
|
do
|
|
{
|
|
hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument, (void**)&pDoc);
|
|
if (FAILED(hr) || !pDoc)
|
|
break;
|
|
|
|
pDoc->put_async(VARIANT_FALSE);
|
|
|
|
hr = pDoc->load(CComVariant(szFilename), &vb);
|
|
if (FAILED(hr) || !vb)
|
|
break;
|
|
|
|
hr = pDoc->QueryInterface(IID_IXMLDOMNode, (void**)&pNode);
|
|
if (FAILED(hr) || !pNode)
|
|
break;
|
|
|
|
if (FAILED(WalkTree(pNode, FALSE)))
|
|
break;
|
|
|
|
retVal = TRUE;
|
|
|
|
}while (false);
|
|
|
|
CoUninitialize();
|
|
|
|
return retVal;
|
|
}
|
|
|
|
|
|
HRESULT CMSInfo7Category::WalkTree(IXMLDOMNode* node, BOOL bCreateCategory)
|
|
{
|
|
CComPtr<IXMLDOMNodeList> childList, rowList;
|
|
IXMLDOMNodeList* columnList = NULL;
|
|
CComPtr<IXMLDOMNamedNodeMap> attributeMap;
|
|
IXMLDOMNode* pNextChild = NULL, *pNextAttribute = NULL, *pNextColumn = NULL;
|
|
|
|
//CComBSTR xmlStr;
|
|
//node->get_xml(&xmlStr);
|
|
|
|
//read attributes, data & recurse for child categories.
|
|
|
|
//attributes
|
|
if (SUCCEEDED(node->get_attributes(&attributeMap)) && attributeMap != NULL)
|
|
{
|
|
attributeMap->nextNode(&pNextAttribute);
|
|
while (pNextAttribute)
|
|
{
|
|
CComBSTR bstrName, bstrValue;
|
|
pNextAttribute->get_nodeName(&bstrName);
|
|
pNextAttribute->get_text(&bstrValue);
|
|
|
|
if (lstrcmpiW(bstrName, L"Name") == 0)
|
|
{
|
|
m_strName = bstrValue;
|
|
m_strCaption = m_strName;
|
|
|
|
pNextAttribute->Release();
|
|
break;
|
|
}
|
|
|
|
pNextAttribute->Release();
|
|
attributeMap->nextNode(&pNextAttribute);
|
|
}
|
|
}
|
|
|
|
m_iColCount = 0;
|
|
m_iRowCount = 0;
|
|
|
|
//count number of rows
|
|
if (SUCCEEDED(node->selectNodes(CComBSTR("Data"), &rowList)) && rowList != NULL)
|
|
{
|
|
long len = 0;
|
|
rowList->get_length(&len);
|
|
m_iRowCount = len;
|
|
}
|
|
|
|
if (m_iRowCount > 0)
|
|
m_afRowAdvanced = new BOOL[m_iRowCount];
|
|
|
|
int iRow = 0;
|
|
CMSInfo7Category* pPrevCat = NULL;
|
|
|
|
//children
|
|
if (SUCCEEDED(node->get_childNodes(&childList)) && childList != NULL)
|
|
{
|
|
childList->nextNode(&pNextChild);
|
|
while (pNextChild)
|
|
{
|
|
CComBSTR bstrName;
|
|
pNextChild->get_nodeName(&bstrName);
|
|
|
|
if ((lstrcmpiW(bstrName, L"Data") == 0) && (m_iRowCount > 0))
|
|
{
|
|
//for each data(row), read column name & value
|
|
if (SUCCEEDED(pNextChild->get_childNodes(&columnList)) && columnList != NULL)
|
|
{
|
|
BOOL bColumnsInitialized = m_acolumns ? TRUE : FALSE;
|
|
if (!bColumnsInitialized)//column static data. do once
|
|
{
|
|
long len = 0;
|
|
columnList->get_length(&len);
|
|
m_iColCount = len;
|
|
m_fDynamicColumns = TRUE;//for correct deletion.
|
|
if (m_iColCount > 0)
|
|
m_acolumns = new CMSInfoColumn[m_iColCount];
|
|
else
|
|
m_acolumns = NULL;
|
|
|
|
if (m_iColCount > 0 && m_iRowCount > 0)
|
|
{
|
|
m_astrData = new CString[m_iColCount * m_iRowCount];
|
|
m_adwData = new DWORD[m_iColCount * m_iRowCount];
|
|
}
|
|
else
|
|
{
|
|
m_astrData = NULL;
|
|
m_adwData = NULL;
|
|
}
|
|
}
|
|
|
|
int iColumn = 0;
|
|
|
|
columnList->nextNode(&pNextColumn);
|
|
while (pNextColumn)
|
|
{
|
|
CComBSTR bstrColHdr, bstrRowVal;
|
|
if (!bColumnsInitialized)//column static data. do once
|
|
{
|
|
pNextColumn->get_nodeName(&bstrColHdr);//column hdr
|
|
m_acolumns[iColumn].m_strCaption = bstrColHdr;
|
|
m_acolumns[iColumn].m_uiWidth = 150;//PENDING
|
|
m_acolumns[iColumn].m_fSorts = FALSE;//PENDING
|
|
m_acolumns[iColumn].m_fLexical = FALSE;//PENDING
|
|
m_acolumns[iColumn].m_fAdvanced = FALSE;//PENDING
|
|
m_acolumns[iColumn].m_uiCaption = 0;
|
|
}
|
|
|
|
pNextColumn->get_text(&bstrRowVal);//row value for the column
|
|
if(lstrcmpiW(bstrColHdr, L"MSINFOERROR") == 0)
|
|
m_hrError = _ttoi(bstrRowVal);
|
|
m_astrData[iRow * m_iColCount + iColumn] = bstrRowVal;
|
|
|
|
m_afRowAdvanced[iRow] = FALSE;//PENDING
|
|
|
|
if (m_acolumns[iColumn].m_fSorts && !m_acolumns[iColumn].m_fLexical)
|
|
{
|
|
//m_adwData[iRow * m_iColCount + iColumn] = uiSortOrder;//PENDING
|
|
}
|
|
|
|
iColumn++;
|
|
pNextColumn->Release();
|
|
columnList->nextNode(&pNextColumn);
|
|
}
|
|
}
|
|
|
|
if (columnList)
|
|
columnList->Release();
|
|
iRow++;
|
|
}
|
|
else if ((lstrcmpiW(bstrName, L"XML") == 0) || (lstrcmpiW(bstrName, L"MSInfo") == 0))
|
|
{
|
|
//get past <xml> & <msinfo>
|
|
this->WalkTree(pNextChild, bCreateCategory);
|
|
}
|
|
else if (lstrcmpiW(bstrName, L"Category") == 0)
|
|
{
|
|
if (!bCreateCategory)
|
|
{
|
|
bCreateCategory = TRUE;//First category encountered. Subsequent categories get their own node.
|
|
this->WalkTree(pNextChild, bCreateCategory);
|
|
}
|
|
else
|
|
{
|
|
CMSInfo7Category* pNewCat = new CMSInfo7Category();
|
|
pNewCat->SetParent(this);
|
|
pNewCat->SetPrevSibling(pPrevCat);
|
|
|
|
if (pPrevCat)
|
|
pPrevCat->SetNextSibling(pNewCat);
|
|
else
|
|
m_pFirstChild = pNewCat;
|
|
|
|
pPrevCat = pNewCat;
|
|
pNewCat->WalkTree(pNextChild, bCreateCategory);
|
|
}
|
|
}
|
|
|
|
pNextChild->Release();
|
|
childList->nextNode(&pNextChild);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// we want these msgs to look very similar to those displayed by the live category
|
|
void CMSInfo7Category::GetErrorText(CString * pstrTitle, CString * pstrMessage)
|
|
{
|
|
if (SUCCEEDED(m_hrError))
|
|
{
|
|
ASSERT(0 && "why call GetErrorText for no error?");
|
|
CMSInfoCategory::GetErrorText(pstrTitle, pstrMessage);
|
|
return;
|
|
}
|
|
|
|
if (pstrTitle)
|
|
pstrTitle->LoadString(IDS_CANTCOLLECT);
|
|
|
|
if (pstrMessage)
|
|
{
|
|
switch (m_hrError)
|
|
{
|
|
case WBEM_E_OUT_OF_MEMORY:
|
|
pstrMessage->LoadString(IDS_OUTOFMEMERROR);
|
|
break;
|
|
|
|
case WBEM_E_ACCESS_DENIED:
|
|
pstrMessage->LoadString(IDS_GATHERACCESS_LOCAL);
|
|
break;
|
|
|
|
case WBEM_E_INVALID_NAMESPACE:
|
|
pstrMessage->LoadString(IDS_BADSERVER_LOCAL);
|
|
break;
|
|
|
|
case 0x800706BA: // RPC Server Unavailable
|
|
case WBEM_E_TRANSPORT_FAILURE:
|
|
pstrMessage->LoadString(IDS_NETWORKERROR_LOCAL);
|
|
break;
|
|
|
|
case WBEM_E_FAILED:
|
|
case WBEM_E_INVALID_PARAMETER:
|
|
default:
|
|
pstrMessage->LoadString(IDS_UNEXPECTED);
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
{
|
|
CString strTemp;
|
|
strTemp.Format(_T("\n\r\n\rDebug Version Only: [HRESULT = 0x%08X]"), m_hrError);
|
|
*pstrMessage += strTemp;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
///////////////
|
|
//EO CMSInfo7Category
|
|
|
|
CMSInfo5Category::CMSInfo5Category()
|
|
{
|
|
|
|
this->m_pFirstChild = NULL;
|
|
this->m_pNextSibling = NULL;
|
|
this->m_pParent = NULL;
|
|
this->m_pPrevSibling = NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Saves the actual data (by row and column) to the file
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CMSInfoCategory::SaveElements(CMSInfoFile *pFile)
|
|
{
|
|
CString szWriteString;
|
|
MSIColumnSortType stColumn;
|
|
unsigned iColCount;
|
|
unsigned iRowCount;
|
|
GetCategoryDimensions((int*) &iColCount,(int*) &iRowCount);
|
|
|
|
DataComplexity dcAdvanced;
|
|
CArray <int, int &> aColumnValues;
|
|
pFile->WriteUnsignedInt(iColCount);
|
|
|
|
if (iColCount == 0)
|
|
return;
|
|
|
|
//for(unsigned iCol = 0; iCol < iColCount; iCol++)
|
|
for(int iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
{
|
|
unsigned uWidth;
|
|
BOOL bSort,bLexical;
|
|
GetColumnInfo(iCol,&szWriteString,&uWidth,&bSort,&bLexical);
|
|
if (bSort)
|
|
{
|
|
if (bLexical)
|
|
{
|
|
stColumn = LEXICAL;
|
|
}
|
|
else
|
|
{
|
|
stColumn = BYVALUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stColumn = NOSORT;
|
|
}
|
|
if (IsColumnAdvanced(iCol))
|
|
{
|
|
dcAdvanced = ADVANCED;
|
|
}
|
|
else
|
|
{
|
|
dcAdvanced = BASIC;
|
|
}
|
|
if (stColumn == BYVALUE)
|
|
{
|
|
aColumnValues.Add(iCol);
|
|
}
|
|
pFile->WriteUnsignedInt(uWidth);
|
|
pFile->WriteString(szWriteString);
|
|
pFile->WriteUnsignedInt((unsigned) stColumn);
|
|
pFile->WriteByte((BYTE)dcAdvanced);
|
|
}
|
|
int wNextColumn = -1;
|
|
unsigned iArray = 0;
|
|
pFile->WriteUnsignedInt(iRowCount);
|
|
//for(int iRow = 0; iRow < (int) iRowCount; iRow++)
|
|
for(int iRow = iRowCount - 1; iRow >= 0 ; iRow--)
|
|
{
|
|
if (IsRowAdvanced(iRow))
|
|
{
|
|
dcAdvanced = ADVANCED;
|
|
}
|
|
else
|
|
{
|
|
dcAdvanced = BASIC;
|
|
}
|
|
pFile->WriteByte((BYTE)dcAdvanced);
|
|
}
|
|
// Iterate over columns, writing sort indices for BYVALUE columns.
|
|
DWORD dwSortIndex;
|
|
//for(iCol = 0; iCol < iColCount; iCol++)
|
|
for(iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
{
|
|
//following variables are not used except for sort info
|
|
CString strUnused;
|
|
UINT iWidth;
|
|
BOOL fSorts;
|
|
BOOL fLexical;
|
|
GetColumnInfo(iCol,&strUnused,&iWidth,&fSorts,&fLexical);
|
|
CDWordArray arySortIndices;
|
|
//for(unsigned iRow = 0; iRow < iRowCount; iRow++)
|
|
for(int iRow = iRowCount - 1; iRow >= 0 ; iRow--)
|
|
{
|
|
CString * pstrData;
|
|
this->GetData(iRow, iCol, &pstrData, &dwSortIndex);
|
|
|
|
// dwSortIndex = m_adwData[iRow * m_iColCount + iCol];
|
|
if (fSorts && !fLexical)
|
|
{
|
|
arySortIndices.Add(dwSortIndex);
|
|
}
|
|
// szWriteString = m_astrData[iRow * m_iColCount + iCol];
|
|
pFile->WriteString(*pstrData);
|
|
|
|
}
|
|
if (fSorts && !fLexical)
|
|
{
|
|
ASSERT((unsigned) arySortIndices.GetSize() == iRowCount && "wrong number of Sort indices");
|
|
//for(unsigned iRow = 0; iRow < iRowCount; iRow++)
|
|
for(int iRow = iRowCount - 1; iRow >= 0 ; iRow--)
|
|
{
|
|
pFile->WriteUnsignedLong(arySortIndices.GetAt(iRow));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Fills the various data structures with information in a msinfo file
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CMSInfo5Category::LoadFromNFO(CMSInfoFile* pFile)
|
|
{
|
|
//TD: check validity of the file
|
|
try
|
|
{
|
|
pFile->ReadString(this->m_strName);
|
|
this->m_strCaption = this->m_strName;
|
|
pFile->ReadSignedInt(this->m_iColCount);
|
|
if (m_iColCount == 0)
|
|
{
|
|
this->m_iRowCount = 0;
|
|
return TRUE;
|
|
}
|
|
this->m_acolumns = new CMSInfoColumn[m_iColCount];
|
|
for(int iColumn = m_iColCount - 1; iColumn >= 0; iColumn--)
|
|
{
|
|
UINT uiWidth;
|
|
pFile->ReadUnsignedInt(uiWidth);
|
|
CString strCaption;
|
|
pFile->ReadString(strCaption);
|
|
unsigned wSortType;
|
|
pFile->ReadUnsignedInt(wSortType);
|
|
BOOL fSorts;
|
|
BOOL fLexical;
|
|
if ( NOSORT == wSortType)
|
|
{
|
|
fLexical = FALSE;
|
|
fSorts = FALSE;
|
|
}
|
|
else if (BYVALUE == wSortType)
|
|
{
|
|
fLexical = FALSE;
|
|
fSorts = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fLexical = TRUE;
|
|
fSorts = TRUE;
|
|
}
|
|
BOOL fAdvanced;
|
|
BYTE btAdvanced;
|
|
pFile->ReadByte(btAdvanced);
|
|
fAdvanced = (BOOL) btAdvanced;
|
|
m_acolumns[iColumn].m_strCaption = strCaption;
|
|
m_acolumns[iColumn].m_uiWidth = uiWidth;
|
|
m_acolumns[iColumn].m_fSorts = fSorts;
|
|
m_acolumns[iColumn].m_fLexical = fLexical;
|
|
m_acolumns[iColumn].m_fAdvanced = fAdvanced;
|
|
m_acolumns[iColumn].m_uiCaption = 0;
|
|
}
|
|
pFile->ReadSignedInt(this->m_iRowCount);
|
|
//Nodes that have no data, but only serve as parents to other nodes, have no Rows
|
|
//and a column count of 1
|
|
|
|
|
|
m_astrData = new CString[m_iColCount * m_iRowCount];
|
|
m_adwData = new DWORD[m_iColCount * m_iRowCount];
|
|
m_afRowAdvanced = new BOOL[m_iRowCount];
|
|
|
|
//for(int iRow = 0; iRow < m_iRowCount; iRow++)
|
|
for(int iRow = m_iRowCount - 1; iRow >=0; iRow--)
|
|
{
|
|
BYTE bComplexity;
|
|
pFile->ReadByte(bComplexity);
|
|
if (BASIC == bComplexity)
|
|
{
|
|
this->m_afRowAdvanced[iRow] = FALSE;
|
|
}
|
|
else
|
|
{
|
|
this->m_afRowAdvanced[iRow] = TRUE;
|
|
}
|
|
}
|
|
|
|
for(iColumn = m_iColCount - 1; iColumn >= 0; iColumn--)
|
|
{
|
|
CMSInfoColumn* pCol = &this->m_acolumns[(unsigned)iColumn];
|
|
//for(iRow = 0; iRow < this->m_iRowCount; iRow++)
|
|
for(int iRow = m_iRowCount - 1; iRow >=0; iRow--)
|
|
{
|
|
CString strData;
|
|
pFile->ReadString(strData);
|
|
m_astrData[iRow * m_iColCount + iColumn] = strData;
|
|
}
|
|
//sort values are another row of ints like Complexity
|
|
//for(iRow = 0; iRow < this->m_iRowCount; iRow++)
|
|
for(iRow = m_iRowCount - 1; iRow >=0; iRow--)
|
|
{
|
|
CMSInfoColumn* pColInfo = &this->m_acolumns[iColumn];
|
|
if (pColInfo->m_fSorts && !pColInfo->m_fLexical)
|
|
{
|
|
unsigned uiSortOrder;
|
|
pFile->ReadUnsignedInt(uiSortOrder);
|
|
m_adwData[iRow * m_iColCount + iColumn] = uiSortOrder;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//TD: exception handling
|
|
catch (CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
|
|
return FALSE;
|
|
}
|
|
catch (CFileFormatException* pException)
|
|
{
|
|
pException->Delete();
|
|
return FALSE;
|
|
}
|
|
catch (...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
//messaging is actually handled elsewhere
|
|
/*CString strCaption, strMessage;
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_BADNFOFILE);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);*/
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Read the header information found at the beginning of the file
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
BOOL ReadMSI5NFOHeader(CMSInfoFile* pFile)
|
|
{
|
|
unsigned iMsinfoFileVersion;
|
|
try
|
|
{
|
|
pFile->ReadUnsignedInt(iMsinfoFileVersion);
|
|
if (iMsinfoFileVersion == CMSInfoFile::VERSION_500_MAGIC_NUMBER)
|
|
{
|
|
unsigned uVersion;
|
|
pFile->ReadUnsignedInt(uVersion);
|
|
ASSERT(uVersion == 0x500 && "Version number does not match format #");
|
|
if (uVersion != 0x500)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
LONG l;
|
|
pFile->ReadLong(l); // Save time.
|
|
time_t tsSaveTime = (ULONG) l;
|
|
//TD: sanity test on date
|
|
CString szUnused;
|
|
pFile->ReadString(szUnused); // Network machine name
|
|
pFile->ReadString(szUnused); // Network user name
|
|
}
|
|
catch (CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
return FALSE;
|
|
}
|
|
catch (CFileFormatException* pException)
|
|
{
|
|
//TD: exception handling
|
|
pException->Delete();
|
|
return FALSE;
|
|
}
|
|
catch (...)
|
|
{
|
|
//messagebox the user in OpenMSInfoFile
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CMSInfo5Category::~CMSInfo5Category()
|
|
{
|
|
this->DeleteAllContent();
|
|
};
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Static member of CMSInfo5Category
|
|
// Starts the process of reading a file, creating a new CMSInfo5Category object
|
|
// for each category found, and returning a pointer to the root node
|
|
//-----------------------------------------------------------------------------
|
|
|
|
HRESULT CMSInfo5Category::ReadMSI5NFO(HANDLE hFile,CMSInfo5Category** ppRootCat, LPCTSTR szFilename)
|
|
{
|
|
CMSInfo5Category* pRootCat = new CMSInfo5Category();
|
|
CFile* pFile = new CFile((INT_PTR) hFile);
|
|
CMSInfoFile msiFile(pFile);
|
|
unsigned iNodeData;
|
|
if (!ReadMSI5NFOHeader(&msiFile))
|
|
{
|
|
//make sure this gets in 2/14 checkin!
|
|
/* CString strCaption, strMessage;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_BADNFOFILE);
|
|
MessageBox(NULL,strMessage, strCaption,MB_OK);*/
|
|
return E_FAIL;
|
|
}
|
|
try
|
|
{
|
|
CMSInfo5Category* pCat = NULL;
|
|
CMSInfo5Category* pPreviousCat;
|
|
if (!pRootCat->LoadFromNFO(&msiFile))
|
|
{
|
|
delete pRootCat;
|
|
pRootCat = NULL;
|
|
CString strCaption, strMessage;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_BADNFOFILE);
|
|
MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
return E_FAIL;
|
|
}
|
|
//there will be a dummy System Information node, with colcount of 1 and rowcount of 0
|
|
//we need to discard this.
|
|
if (pRootCat->m_iColCount == 1 && pRootCat->m_iRowCount == 0)
|
|
{
|
|
delete pRootCat;
|
|
pRootCat = new CMSInfo5Category();
|
|
|
|
msiFile.ReadUnsignedInt(iNodeData);
|
|
if (!pRootCat->LoadFromNFO(&msiFile))
|
|
{ delete pRootCat;
|
|
pRootCat = NULL;
|
|
CString strCaption, strMessage;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_BADNFOFILE);
|
|
MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
if (szFilename)
|
|
{
|
|
CString strAppend;
|
|
|
|
strAppend.Format(_T(" (%s)"), szFilename);
|
|
pRootCat->m_strCaption += strAppend;
|
|
}
|
|
|
|
pPreviousCat = pRootCat;
|
|
unsigned iNextNodeType = CMSInfo5Category::FIRST;
|
|
|
|
//iNextNodeType specifies where in the node tree to put the category
|
|
for(;iNextNodeType != CMSInfo5Category::END;)
|
|
{
|
|
msiFile.ReadUnsignedInt(iNodeData);
|
|
if (pPreviousCat == pRootCat)
|
|
{
|
|
//disregard this particular node position indicator, since we
|
|
//don't want an empty root category like MSInfo 5.0
|
|
iNodeData = CMSInfo5Category::CHILD;
|
|
}
|
|
iNextNodeType = iNodeData & CMSInfo5Category::MASK;
|
|
switch (iNextNodeType)
|
|
{
|
|
case CMSInfo5Category::END:
|
|
pPreviousCat->SetNextSibling(NULL);
|
|
pPreviousCat->SetFirstChild(NULL);
|
|
break;
|
|
|
|
case CMSInfo5Category::NEXT:
|
|
pCat = new CMSInfo5Category();
|
|
if (!pCat->LoadFromNFO(&msiFile))
|
|
{
|
|
delete pCat;
|
|
pCat = NULL;
|
|
return E_FAIL;
|
|
}
|
|
pCat->SetPrevSibling(pPreviousCat);
|
|
//the parent of the previous sibling should be the parent for this
|
|
if (pPreviousCat)
|
|
{
|
|
pCat->SetParent((CMSInfo5Category *) pPreviousCat->GetParent());
|
|
pPreviousCat->SetNextSibling(pCat);
|
|
pCat->SetPrevSibling(pPreviousCat);
|
|
}
|
|
pPreviousCat = pCat;
|
|
break;
|
|
case CMSInfo5Category::CHILD:
|
|
pCat = new CMSInfo5Category();
|
|
if (!pCat->LoadFromNFO(&msiFile))
|
|
{
|
|
delete pCat;
|
|
pCat = NULL;
|
|
return E_FAIL;
|
|
}
|
|
pCat->SetParent(pPreviousCat);
|
|
pPreviousCat->SetFirstChild(pCat);
|
|
pCat->SetPrevSibling(NULL);
|
|
pPreviousCat = pCat;
|
|
break;
|
|
case CMSInfo5Category::PARENT:
|
|
pCat = new CMSInfo5Category();
|
|
if (!pCat->LoadFromNFO(&msiFile))
|
|
{
|
|
delete pCat;
|
|
pCat = NULL;
|
|
return E_FAIL;
|
|
}
|
|
//if this a parent, we need to backtrack out of current branch of tree
|
|
//to find the appropriate parent, get an index from iNodeData
|
|
//and go back that many categories.
|
|
unsigned iDepth = (iNodeData & ~CMSInfo5Category::MASK);
|
|
for(unsigned i = 0; i < iDepth; i++)
|
|
{
|
|
pPreviousCat = (CMSInfo5Category *) pPreviousCat->GetParent();
|
|
|
|
}
|
|
if (!pPreviousCat)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
//now move to the end of chain of children
|
|
for(;pPreviousCat->GetNextSibling();)
|
|
{
|
|
pPreviousCat = (CMSInfo5Category *) pPreviousCat->GetNextSibling();
|
|
}
|
|
pPreviousCat->SetNextSibling(pCat);
|
|
pCat->SetParent((CMSInfo5Category *) pPreviousCat->GetParent());
|
|
pCat->SetPrevSibling(pPreviousCat);
|
|
pCat->SetNextSibling(NULL);
|
|
pPreviousCat = pCat;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
return E_FAIL;
|
|
|
|
}
|
|
catch (CFileFormatException* pException)
|
|
{
|
|
//TD: cleanup
|
|
pException->Delete();
|
|
return E_FAIL;
|
|
|
|
}
|
|
catch (...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_BADNFOFILE);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
return E_FAIL;
|
|
}
|
|
//no need to delete pFile; it will be cleaned up by CMSInfoFile destructor
|
|
*ppRootCat = pRootCat;
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Saves this category to a MSInfo 5 file, which must already have header information
|
|
// written to it
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CMSInfoCategory::SaveToNFO(CMSInfoFile* pFile)
|
|
{
|
|
CString strCaption;
|
|
GetNames(&strCaption, NULL);
|
|
|
|
pFile->WriteString(strCaption);
|
|
SaveElements(pFile);
|
|
return TRUE;
|
|
}
|
|
|
|
HANDLE CMSInfo5Category::GetFileFromCab(CString strFileName)
|
|
{
|
|
CString strDest;
|
|
GetCABExplodeDir(strDest,TRUE,"");
|
|
OpenCABFile(strFileName,strDest);
|
|
CString strFilename;
|
|
FindFileToOpen(strDest,strFilename);
|
|
return CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//Saves category information as text, recursing children in bRecursive is true
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
BOOL CMSInfoCategory::SaveAsText(CMSInfoTextFile* pTxtFile, BOOL bRecursive)
|
|
{
|
|
CString strOut;
|
|
CString strBracket;
|
|
VERIFY(strBracket.LoadString(IDS_LEFTBRACKET) && "Failed to find resource IDS_LEFTBRACKET");
|
|
strOut = strBracket;
|
|
CString strName, strCaption;
|
|
GetNames(&strCaption,&strName);
|
|
strOut += strCaption;
|
|
VERIFY(strBracket.LoadString(IDS_RIGHTBRACKET) && "Failed to find resource IDS_RIGHTBRACKET");
|
|
strOut += strBracket;
|
|
pTxtFile->WriteString("\r\n");
|
|
pTxtFile->WriteString(strOut);
|
|
int iRowCount,iColCount;
|
|
this->GetCategoryDimensions(&iColCount,&iRowCount);
|
|
CString strColHeader;
|
|
UINT uiUnused;
|
|
BOOL fUnused;
|
|
CString strColSpacing = "\t";
|
|
pTxtFile->WriteString("\r\n");
|
|
pTxtFile->WriteString("\r\n");
|
|
if (1 == iColCount && 0 == iRowCount)
|
|
{
|
|
//this is a parent node, with no data of its own
|
|
|
|
CString strCatHeading;
|
|
strCatHeading.LoadString(IDS_CATEGORYHEADING);
|
|
pTxtFile->WriteString(strCatHeading);
|
|
}
|
|
else
|
|
{
|
|
//for(int iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
GetColumnInfo(iCol,&strColHeader,&uiUnused,&fUnused,&fUnused);
|
|
pTxtFile->WriteString(strColHeader);
|
|
pTxtFile->WriteString(strColSpacing);
|
|
}
|
|
pTxtFile->WriteString("\r\n");
|
|
|
|
CString strRowInfo;
|
|
//for(int iRow = iRowCount - 1; iRow >= 0; iRow--)
|
|
for(int iRow = 0;iRow < iRowCount; iRow++)
|
|
{
|
|
//for(int iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
//this->GetData(iRow,iCol,&strRowInfo,&dwUnused);
|
|
if(m_astrData)
|
|
strRowInfo = m_astrData[iRow * m_iColCount + iCol];
|
|
else
|
|
strRowInfo.LoadString(IDS_CANTCOLLECT);
|
|
pTxtFile->WriteString(strRowInfo);
|
|
pTxtFile->WriteString(strColSpacing);
|
|
|
|
}
|
|
pTxtFile->WriteString("\r\n");
|
|
}
|
|
}
|
|
if (bRecursive && this->m_pFirstChild != NULL)
|
|
{
|
|
for(CMSInfo5Category* pChild = (CMSInfo5Category*) this->GetFirstChild();pChild != NULL;pChild = (CMSInfo5Category*) pChild->GetNextSibling())
|
|
{
|
|
pChild->SaveAsText(pTxtFile,TRUE);
|
|
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
///////////////////////
|
|
//Functions added by a-kjaw
|
|
// The code needs to be refined & is copy-paste from SaveAsText code.
|
|
//Maybe a good idea to parameterize SaveAsText func.
|
|
/*BOOL CMSInfoCategory::SaveAsXml(CMSInfoTextFile* pTxtFile, BOOL bRecursive)
|
|
{
|
|
//if this has no parent, it is topmost node, so
|
|
//if we're writing whole tree (bRecursive is TRUE) then now is when
|
|
//we want to write header
|
|
if (!this->m_pParent && bRecursive)
|
|
{
|
|
pTxtFile->WriteString("<?xml version=\"1.0\" ?>\r\n");
|
|
pTxtFile->WriteString("<MsInfo>\r\n");
|
|
}
|
|
|
|
CStringArray csarr;
|
|
|
|
CString strOut;
|
|
CString strBracket;
|
|
|
|
CString strName, strCaption;
|
|
GetNames(&strCaption,&strName);
|
|
strOut += strCaption;
|
|
strOut += strBracket;
|
|
|
|
pTxtFile->WriteString("<Category name=\"");
|
|
pTxtFile->WriteString(strOut);
|
|
pTxtFile->WriteString("\">\r\n");
|
|
|
|
int iRowCount,iColCount;
|
|
this->GetCategoryDimensions(&iColCount,&iRowCount);
|
|
CString strColHeader;
|
|
UINT uiUnused;
|
|
BOOL fUnused;
|
|
int iSpaceLoc = 0;
|
|
|
|
if (1 == iColCount && 0 == iRowCount)
|
|
{
|
|
//this is a parent node, with no data of its own
|
|
|
|
//CString strCatHeading;
|
|
//strCatHeading.LoadString(IDS_CATEGORYHEADING);
|
|
//pTxtFile->WriteString("<Category>");
|
|
//pTxtFile->WriteString(strCatHeading);
|
|
//pTxtFile->WriteString("</Category>\r\n");
|
|
}
|
|
else
|
|
{
|
|
//for(int iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
csarr.RemoveAll();
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
//XML wont accept spaces at node names. ie. <Category Name> should be <Category_Name>
|
|
GetColumnInfo(iCol,&strColHeader,&uiUnused,&fUnused,&fUnused);
|
|
while((iSpaceLoc = strColHeader.Find(_T(" ") , 0)) != -1)
|
|
strColHeader.SetAt(iSpaceLoc , _T('_'));
|
|
|
|
csarr.Add(strColHeader);
|
|
}
|
|
|
|
CString strRowInfo;
|
|
|
|
for(int iRow = 0;iRow < iRowCount; iRow++)
|
|
{
|
|
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
|
|
strRowInfo = m_astrData[iRow * m_iColCount + iCol];
|
|
|
|
pTxtFile->WriteString("<");
|
|
pTxtFile->WriteString(csarr[iCol]);
|
|
pTxtFile->WriteString(">");
|
|
|
|
//Put CDATA here to take care of all weird characters.
|
|
pTxtFile->WriteString("<![CDATA[");
|
|
pTxtFile->WriteString(strRowInfo);
|
|
pTxtFile->WriteString("]]>");
|
|
|
|
pTxtFile->WriteString("</");
|
|
pTxtFile->WriteString(csarr[iCol]);
|
|
pTxtFile->WriteString(">\r\n");
|
|
|
|
}
|
|
pTxtFile->WriteString("\r\n");
|
|
}
|
|
|
|
pTxtFile->WriteString("</Category>\r\n");
|
|
|
|
}
|
|
if (bRecursive && this->m_pFirstChild != NULL)
|
|
{
|
|
for(CMSInfo5Category* pChild = (CMSInfo5Category*) this->GetFirstChild();pChild != NULL;pChild = (CMSInfo5Category*) pChild->GetNextSibling())
|
|
{
|
|
pChild->SaveAsXml(pTxtFile,TRUE);
|
|
|
|
}
|
|
}
|
|
return TRUE;
|
|
}*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Saves this category as text to an open file, and recursively saves subcategories
|
|
// if bRecursive is true
|
|
// Assumes file can be close when last category is written
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CMSInfoCategory::SaveAsText(HANDLE hFile, BOOL bRecursive, LPTSTR lpMachineName)
|
|
{
|
|
CFile * pFileOut = new CFile((INT_PTR)hFile);
|
|
|
|
// The text file is Unicode, so it needs the marker (339423).
|
|
|
|
WCHAR wUnicodeMarker = 0xFEFF;
|
|
pFileOut->Write((const void *)&wUnicodeMarker, sizeof(WCHAR));
|
|
|
|
try
|
|
{
|
|
CMSInfoTextFile * pTxtFile = new CMSInfoTextFile(pFileOut);
|
|
|
|
CTime tNow = CTime::GetCurrentTime();
|
|
CString strTimeFormat;
|
|
|
|
VERIFY(strTimeFormat.LoadString(IDS_TIME_FORMAT) && "Failed to find resource IDS_TIME_FORMAT");
|
|
CString strHeaderText = tNow.Format(strTimeFormat);
|
|
pTxtFile->WriteString(strHeaderText);
|
|
|
|
if (NULL != lpMachineName)
|
|
{
|
|
CString strMachine;
|
|
|
|
strMachine.LoadString(IDS_SYSTEMNAME);
|
|
strMachine += _tcsupr(lpMachineName);
|
|
pTxtFile->WriteString(strMachine);
|
|
}
|
|
|
|
if (!this->SaveAsText(pTxtFile,bRecursive))
|
|
{
|
|
return FALSE;
|
|
}
|
|
delete pTxtFile;
|
|
}
|
|
catch(CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (CException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch(...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_FILESAVEERROR_UNKNOWN);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
//CloseHandle(hFile);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
///////////////////////
|
|
//Functions added by a-kjaw
|
|
// The code needs to be refined & is copy-paste from SaveAsText code.
|
|
//Maybe a good idea to parameterize SaveAsText func.
|
|
/*BOOL CMSInfoCategory::SaveAsXml(HANDLE hFile, BOOL bRecursive)
|
|
{
|
|
CFile* pFileOut = new CFile((INT_PTR)hFile);
|
|
try
|
|
{
|
|
CMSInfoTextFile* pTxtFile = new CMSInfoTextFile(pFileOut);
|
|
if (!this->SaveAsXml(pTxtFile,bRecursive))
|
|
{
|
|
return FALSE;
|
|
}
|
|
pTxtFile->WriteString("</MsInfo>\r\n");
|
|
delete pTxtFile;
|
|
}
|
|
catch(CFileException e)
|
|
{
|
|
e.ReportError();
|
|
}
|
|
catch (CException e)
|
|
{
|
|
e.ReportError();
|
|
}
|
|
catch(...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_FILESAVEERROR_UNKNOWN);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
//CloseHandle(hFile);
|
|
|
|
|
|
return TRUE;
|
|
}*/
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Static function that saves specified category to a MSInfo 5 nfo file
|
|
// writing header information (so it should be used only to save either root
|
|
// category or single category
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BOOL CMSInfoCategory::SaveNFO(HANDLE hFile, CMSInfoCategory* pCategory, BOOL fRecursive)
|
|
{
|
|
//msiFile will delete pFile in its destructor
|
|
try
|
|
{
|
|
CFile* pFile = new CFile((INT_PTR) hFile);
|
|
CMSInfoFile msiFile(pFile);
|
|
msiFile.WriteHeader(NULL);
|
|
if (!fRecursive || pCategory->GetParent() != NULL)
|
|
{
|
|
pCategory->SaveToNFO(&msiFile);
|
|
msiFile.WriteEndMark();
|
|
return TRUE;
|
|
}
|
|
CMSInfoCategory* pNext = NULL;
|
|
CMSInfoCategory* pRoot = pCategory;
|
|
//change col and row count of pCategory, to use it as a to create empty node System Information
|
|
//node, saving original col and row count
|
|
|
|
int iRowCount, iColCount;
|
|
iRowCount = pCategory->m_iRowCount;
|
|
iColCount = pCategory->m_iColCount;
|
|
pCategory->m_iColCount = 1;
|
|
pCategory->m_iRowCount = 0;
|
|
if (!pCategory->SaveToNFO(&msiFile))
|
|
{
|
|
return FALSE;
|
|
}
|
|
//restore col and row counts
|
|
pCategory->m_iColCount = iColCount;
|
|
pCategory->m_iRowCount = iRowCount;
|
|
|
|
//write child mark
|
|
msiFile.WriteChildMark();
|
|
do
|
|
{
|
|
//write the data for each category as it is encountered
|
|
if (!pCategory->SaveToNFO(&msiFile))
|
|
{
|
|
return FALSE;
|
|
}
|
|
//if we have a child, traverse it
|
|
pNext = pCategory->GetFirstChild();
|
|
if (pCategory == pRoot)
|
|
{
|
|
msiFile.WriteNextMark();
|
|
pCategory = pNext;
|
|
continue;
|
|
}
|
|
else if (pNext != NULL)
|
|
{
|
|
msiFile.WriteChildMark();
|
|
pCategory = pNext;
|
|
continue;
|
|
}
|
|
/*if (pCategory == pRoot)
|
|
{
|
|
break;
|
|
}*/
|
|
//if we have reached the bottom of our list, traverse our siblings
|
|
pNext = pCategory->GetNextSibling();
|
|
if (pNext != NULL)
|
|
{
|
|
msiFile.WriteNextMark();
|
|
pCategory = pNext;
|
|
continue;
|
|
}
|
|
//if we have no more siblings, find our nearest parent's sibling, traversing
|
|
//upwards until we find the node we started with
|
|
pNext = pCategory->GetParent();
|
|
ASSERT(pNext != NULL);
|
|
unsigned uParentCount = 0;
|
|
while (pNext != pRoot)
|
|
{
|
|
++uParentCount;
|
|
pCategory = pNext->GetNextSibling();
|
|
//our parent has a sibling, continue with it
|
|
if (pCategory != NULL)
|
|
{
|
|
msiFile.WriteParentMark(uParentCount);
|
|
break;
|
|
}
|
|
pNext = pNext->GetParent();
|
|
|
|
}
|
|
//if we've returned to our root node, we're done
|
|
if (pNext == pRoot)
|
|
{
|
|
break;
|
|
}
|
|
} while (pCategory != NULL);
|
|
msiFile.WriteEndMark();
|
|
|
|
}
|
|
catch(CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (CException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch(...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_FILESAVEERROR_UNKNOWN);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL CMSInfoCategory::SaveXML(HANDLE hFile)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
CMSInfoTextFile* pTxtFile = NULL;
|
|
CFile* pFileOut = new CFile((INT_PTR)hFile);
|
|
try
|
|
{
|
|
pTxtFile = new CMSInfoTextFile(pFileOut);
|
|
if (pTxtFile)
|
|
bRet = SaveXML(pTxtFile);
|
|
}
|
|
catch(CFileException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (CException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch(...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_FILESAVEERROR_UNKNOWN);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
|
|
if (pTxtFile)
|
|
{
|
|
delete pTxtFile;
|
|
pTxtFile = NULL;
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
BOOL CMSInfoCategory::SaveXML(CMSInfoTextFile* pTxtFile)
|
|
{
|
|
CString strData, tmpData;
|
|
|
|
if (!this->m_pParent)
|
|
{
|
|
#if defined(_UNICODE)
|
|
WORD wBom = 0xFEFF; //Unicode Byte Order Mark
|
|
pTxtFile->m_pFile->Write(&wBom, 2);
|
|
#endif
|
|
|
|
strData += _T("<?xml version=\"1.0\"?>\r\n<MsInfo>\r\n");
|
|
|
|
CTime tNow = CTime::GetCurrentTime();
|
|
CString strTime = tNow.FormatGmt(_T("%x %X"));
|
|
CString strVersion("7.0");
|
|
|
|
tmpData.Format(_T("<Metadata>\r\n<Version>%s</Version>\r\n<CreationUTC>%s</CreationUTC>\r\n</Metadata>\r\n"), strVersion, strTime);
|
|
strData += tmpData;
|
|
tmpData.Empty();
|
|
}
|
|
|
|
CString strName, strCaption;
|
|
GetNames(&strCaption,&strName);
|
|
strData += _T("<Category name=\"");
|
|
strData += strCaption;
|
|
strData += _T("\">\r\n");
|
|
|
|
CString strBadXML = _T("& '<>\"");
|
|
if(SUCCEEDED(m_hrError))
|
|
{
|
|
int iRowCount,iColCount;
|
|
GetCategoryDimensions(&iColCount, &iRowCount);
|
|
|
|
//TCHAR buf[500] = {0};
|
|
//_stprintf(buf, _T("iRowCount=%d iColCount=%d HRESULT=%d m_astrData=%d\r\n"), iRowCount, iColCount, m_hrError, m_astrData);
|
|
//pTxtFile->WriteString(buf);
|
|
|
|
UINT uiUnused;
|
|
BOOL fUnused;
|
|
int iSpaceLoc = 0;
|
|
CString strColHeader, strRowInfo;
|
|
|
|
if(!iRowCount && (iColCount > 1))
|
|
{
|
|
strData += _T("<Data>\r\n");
|
|
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
GetColumnInfo(iCol, &strColHeader, &uiUnused, &fUnused, &fUnused);
|
|
//replace blank spaces with underscores. v-stlowe here is also where we should remove any other
|
|
//characters that XML won't like, like "'" in French
|
|
//v-stlowe 7/2/2001
|
|
while((iSpaceLoc = strColHeader.FindOneOf(strBadXML)) != -1)
|
|
strColHeader.SetAt(iSpaceLoc , _T('_'));
|
|
tmpData.Format(_T("<%s>%s</%s>\r\n"), strColHeader, strRowInfo, strColHeader);
|
|
strData += tmpData;
|
|
}
|
|
|
|
strData += _T("</Data>\r\n");
|
|
}
|
|
|
|
for(int iRow = 0;iRow < iRowCount; iRow++)
|
|
{
|
|
strData += _T("<Data>\r\n");
|
|
for(int iCol = 0; iCol < iColCount ; iCol++)
|
|
{
|
|
GetColumnInfo(iCol, &strColHeader, &uiUnused, &fUnused, &fUnused);
|
|
//replace blank spaces with underscores. v-stlowe here is also where we should remove any other
|
|
//characters that XML won't like, like "'" in French
|
|
//v-stlowe 7/2/2001
|
|
while((iSpaceLoc = strColHeader.FindOneOf(strBadXML)) != -1)
|
|
strColHeader.SetAt(iSpaceLoc , _T('_'));
|
|
|
|
if(!m_astrData)
|
|
break;
|
|
strRowInfo = m_astrData[iRow * m_iColCount + iCol];
|
|
|
|
tmpData.Format(_T("<%s><![CDATA[%s]]></%s>\r\n"), strColHeader, strRowInfo, strColHeader);
|
|
strData += tmpData;
|
|
}
|
|
|
|
strData += _T("</Data>\r\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tmpData.Format(_T("<Data>\r\n<MSINFOERROR>%d</MSINFOERROR>\r\n</Data>\r\n"), m_hrError);
|
|
strData += tmpData;
|
|
}
|
|
|
|
pTxtFile->WriteString(strData);
|
|
|
|
for(CMSInfoCategory* pChild = this->GetFirstChild(); pChild != NULL; pChild = pChild->GetNextSibling())
|
|
pChild->SaveXML(pTxtFile);
|
|
|
|
pTxtFile->WriteString(_T("</Category>\r\n"));
|
|
|
|
if (!this->m_pParent)
|
|
pTxtFile->WriteString(_T("</MsInfo>"));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Prints this category, and recursively prints subcategories, if bRecursive is
|
|
// true. If nStartPage and nEndPage are 0, page range is ignored (all pages are
|
|
// printed). If bRecursive is true and a print range is specified,
|
|
// each category will be processed but only information that would fall on the page range
|
|
// will be printed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
void CMSInfoCategory::Print(HDC hDC, BOOL bRecursive,int nStartPage, int nEndPage, LPTSTR lpMachineName)
|
|
{
|
|
//nStartPage and nEndPage mark a page range to print;
|
|
//if both are 0, then print everything
|
|
CMSInfoPrintHelper* pPrintHelper = new CMSInfoPrintHelper(hDC,nStartPage,nEndPage);
|
|
//header info..do we need this?
|
|
// WCHAR wHeader = 0xFEFF;
|
|
//pTxtFile->Write( &wHeader, 2);
|
|
try
|
|
{
|
|
CTime tNow = CTime::GetCurrentTime();
|
|
CString strTimeFormat;
|
|
strTimeFormat.LoadString(IDS_TIME_FORMAT);
|
|
CString strHeaderText = tNow.Format(strTimeFormat);
|
|
pPrintHelper->PrintLine(strHeaderText);
|
|
if (NULL != lpMachineName)
|
|
{
|
|
CString strMachine;
|
|
strMachine.LoadString(IDS_SYSTEMNAME);
|
|
strMachine += _tcsupr(lpMachineName);
|
|
pPrintHelper->PrintLine(strMachine);
|
|
}
|
|
Print(pPrintHelper,bRecursive);
|
|
}
|
|
catch (CException* pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch(...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_PRINT_GENERIC);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
delete pPrintHelper;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Prints this category, and recursively prints subcategories, if bRecursive is
|
|
// true. If nStartPage and nEndPage are 0, page range is ignored (all pages are
|
|
// printed). If bRecursive is true and a print range is specified,
|
|
// each category will be processed but only information that would fall on the page range
|
|
// will be printed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
void CMSInfoCategory::Print(CMSInfoPrintHelper* pPrintHelper, BOOL bRecursive)
|
|
{
|
|
|
|
CString strOut;
|
|
CString strBracket;
|
|
VERIFY(strBracket.LoadString(IDS_LEFTBRACKET) && "Failed to find resource IDS_LEFTBRACKET");
|
|
strOut = strBracket;
|
|
CString strName, strCaption;
|
|
GetNames(&strCaption,&strName);
|
|
strOut += strCaption;
|
|
VERIFY(strBracket.LoadString(IDS_RIGHTBRACKET) && "Failed to find resource IDS_RIGHTBRACKET");
|
|
strOut += strBracket;
|
|
pPrintHelper->PrintLine("");
|
|
pPrintHelper->PrintLine(strOut);
|
|
int iRowCount,iColCount;
|
|
this->GetCategoryDimensions(&iColCount,&iRowCount);
|
|
CString strColHeader;
|
|
UINT uiUnused;
|
|
BOOL fUnused;
|
|
//TD: put in resources
|
|
CString strColSpacing = " ";
|
|
pPrintHelper->PrintLine("");
|
|
if (1 == iColCount && 0 == iRowCount)
|
|
{
|
|
//this is a parent node, with no data of its own
|
|
|
|
CString strCatHeading;
|
|
strCatHeading.LoadString(IDS_CATEGORYHEADING);
|
|
pPrintHelper->PrintLine(strCatHeading);
|
|
}
|
|
else if (iColCount > 0)
|
|
{
|
|
CString strComposite;
|
|
for(int iCol =0 ; iCol <iColCount ; iCol++)
|
|
{
|
|
GetColumnInfo(iCol,&strColHeader,&uiUnused,&fUnused,&fUnused);
|
|
strComposite += strColHeader;
|
|
strComposite += strColSpacing;
|
|
|
|
}
|
|
pPrintHelper->PrintLine(strComposite);
|
|
strComposite = "";
|
|
|
|
CString strRowInfo;
|
|
//for(int iRow = iRowCount - 1; iRow >= 0; iRow--)
|
|
for(int iRow = 0; iRow < iRowCount; iRow++)
|
|
{
|
|
//for(int iCol = iColCount - 1; iCol >= 0 ; iCol--)
|
|
for(int iCol =0 ; iCol <iColCount ; iCol++)
|
|
{
|
|
strRowInfo = m_astrData[iRow * m_iColCount + iCol];
|
|
strComposite += strRowInfo;
|
|
strComposite += strColSpacing;
|
|
}
|
|
pPrintHelper->PrintLine(strComposite);
|
|
strComposite = "";
|
|
}
|
|
}
|
|
if (bRecursive && this->m_pFirstChild != NULL)
|
|
{
|
|
for(CMSInfo5Category* pChild = (CMSInfo5Category*) this->GetFirstChild();pChild != NULL;pChild = (CMSInfo5Category*) pChild->GetNextSibling())
|
|
{
|
|
pChild->Print(pPrintHelper,TRUE);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Prints a line of text (if current page is in print range) and updates positioning
|
|
// information. If the line is too long to fit on page it is split and printed on
|
|
// multiple lines
|
|
//-----------------------------------------------------------------------------
|
|
|
|
extern void StringReplace(CString & str, LPCTSTR szLookFor, LPCTSTR szReplaceWith);
|
|
|
|
void CMSInfoPrintHelper::PrintLine( CString strLine)
|
|
{
|
|
|
|
//simple line printing function that makes sure that if line exceeds page length,
|
|
//it will wrap to the next line
|
|
//m_nCurrentLineIndex will be the line number, not the vertical position
|
|
//increment the line index, so calling object will print next line
|
|
//at appropriate vertical position
|
|
++m_nCurrentLineIndex;
|
|
strLine.TrimRight();
|
|
//replace tabs with whitespace
|
|
StringReplace(strLine, _T("\t"), _T(" ")); // strLine.Replace(_T("\t"),_T(" "));
|
|
CSize csLinecaps = m_pPrintDC->GetTextExtent(strLine);
|
|
//see if current position is on page; if not (we've printed to the bottom of the page)
|
|
//paginate, and reset index to 0
|
|
int nFooterMargin = GetFooterMargin();
|
|
int nVDeviceCaps = GetDeviceCaps(m_hDC,VERTRES);
|
|
int nPageVertSize = GetDeviceCaps(m_hDC,VERTRES) - csLinecaps.cy - GetFooterMargin();
|
|
if (GetVerticalPos(m_nCurrentLineIndex,csLinecaps) >= nPageVertSize)
|
|
{
|
|
Paginate();
|
|
if (IsInPageRange(m_nPageNumber))
|
|
{
|
|
StartPage(this->GetHDC());
|
|
PrintHeader();
|
|
m_bNeedsEndPage = TRUE;
|
|
}
|
|
}
|
|
int nHorzSize = GetDeviceCaps(m_hDC,HORZRES);
|
|
if (csLinecaps.cx > nHorzSize)
|
|
{
|
|
//line is longer than device caps, and needs to be adjusted
|
|
CString strAdjusted;
|
|
|
|
for(int i = 0;i < strLine.GetLength() ;i++)
|
|
{
|
|
strAdjusted += strLine[i];
|
|
csLinecaps = m_pPrintDC->GetTextExtent(strAdjusted);
|
|
if (csLinecaps.cx > nHorzSize)
|
|
{
|
|
strAdjusted = strAdjusted.Left(--i);
|
|
//yPosition will be m_nLineIndex* height of a line
|
|
//check to see if this page is within print range
|
|
//if it isn't, we don't want the text to actually go to the printer
|
|
if (IsInPageRange(m_nPageNumber))
|
|
{
|
|
//pDC->TextOut(this->GetVerticalPos(this->m_nCurrentLineIndex,csLinecaps),0,strAdjusted);
|
|
//m_pPrintDC->TextOut(0,this->GetVerticalPos(this->m_nCurrentLineIndex,csLinecaps),strAdjusted,strAdjusted.GetLength());
|
|
VERIFY(TextOut(m_hDC,0,this->GetVerticalPos(this->m_nCurrentLineIndex,csLinecaps),strAdjusted,strAdjusted.GetLength()));
|
|
|
|
|
|
}
|
|
PrintLine(strLine.Right(strLine.GetLength() -i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsInPageRange(m_nPageNumber))
|
|
{
|
|
//for debug...remove
|
|
int z = this->GetVerticalPos(this->m_nCurrentLineIndex,csLinecaps);
|
|
VERIFY(TextOut(m_hDC,0,this->GetVerticalPos(this->m_nCurrentLineIndex,csLinecaps),strLine,strLine.GetLength()));
|
|
//TRACE("%d %d %s\n",z,m_nCurrentLineIndex,strLine);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Manages GDI objects (DC and Font), and information about printing positions
|
|
// and page ranges
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CMSInfoPrintHelper::CMSInfoPrintHelper(HDC hDC,int nStartPage, int nEndPage)
|
|
: m_nStartPage(nStartPage),m_nEndPage(nEndPage),m_nCurrentLineIndex(0),m_nPageNumber(1),m_hDC(hDC)
|
|
|
|
{
|
|
m_pPrintDC = new CDC();
|
|
m_pPrintDC->Attach(hDC);
|
|
|
|
|
|
// Create the font for printing. Read font information from string
|
|
// resources, to allow the localizers to control what font is
|
|
// used for printing. Set the variables for the default font to use.
|
|
|
|
int nHeight = 10;
|
|
int nWeight = FW_NORMAL;
|
|
BYTE nCharSet = DEFAULT_CHARSET;
|
|
BYTE nPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
|
|
CString strFace = "Courier New";
|
|
|
|
|
|
// Load string resources to see if we should use other values
|
|
// than the defaults.
|
|
|
|
CString strHeight, strWeight, strCharSet, strPitchAndFamily, strFaceName;
|
|
strHeight.LoadString(IDS_PRINT_FONT_HEIGHT);
|
|
strWeight.LoadString(IDS_PRINT_FONT_WEIGHT);
|
|
strCharSet.LoadString(IDS_PRINT_FONT_CHARSET);
|
|
strPitchAndFamily.LoadString(IDS_PRINT_FONT_PITCHANDFAMILY);
|
|
strFaceName.LoadString(IDS_PRINT_FONT_FACENAME);
|
|
|
|
if (!strHeight.IsEmpty() && ::_ttol(strHeight))
|
|
nHeight = ::_ttoi(strHeight);
|
|
|
|
if (!strWeight.IsEmpty())
|
|
nWeight = ::_ttoi(strWeight);
|
|
|
|
if (!strCharSet.IsEmpty())
|
|
nCharSet = (BYTE) ::_ttoi(strCharSet);
|
|
|
|
if (!strPitchAndFamily.IsEmpty())
|
|
nPitchAndFamily = (BYTE) ::_ttoi(strPitchAndFamily);
|
|
|
|
strFaceName.TrimLeft();
|
|
if (!strFaceName.IsEmpty() && strFaceName != CString("facename"))
|
|
strFace = strFaceName;
|
|
m_pCurrentFont = new CFont();
|
|
nHeight = -((this->m_pPrintDC->GetDeviceCaps (LOGPIXELSY) * nHeight) / 72);
|
|
VERIFY(this->m_pCurrentFont->CreateFont(nHeight, 0, 0, 0, nWeight, 0, 0, 0,
|
|
nCharSet, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
|
|
DEFAULT_QUALITY, nPitchAndFamily, strFace));
|
|
m_pOldFont = (CFont*) m_pPrintDC->SelectObject(this->m_pCurrentFont);
|
|
ASSERT(m_pOldFont && "Error Selecting Font object into CDC");
|
|
DOCINFO docinfo;
|
|
memset(&docinfo, 0, sizeof(docinfo));
|
|
docinfo.cbSize = sizeof(docinfo);
|
|
CString strDocName;
|
|
strDocName.LoadString(IDS_PRINTING_DOCNAME);
|
|
docinfo.lpszDocName = strDocName;
|
|
m_pPrintDC->StartDoc(&docinfo);
|
|
m_pPrintDC->StartPage();
|
|
PrintHeader();
|
|
m_bNeedsEndPage = TRUE;
|
|
}
|
|
|
|
|
|
|
|
CMSInfoPrintHelper::~CMSInfoPrintHelper()
|
|
{
|
|
if (m_bNeedsEndPage)
|
|
{
|
|
VERIFY(EndPage(m_pPrintDC->m_hDC));
|
|
}
|
|
int nResult = m_pPrintDC->EndDoc();
|
|
ASSERT(nResult >= 0);
|
|
//reportprinting error
|
|
//should be if < -1
|
|
if (nResult < 0)
|
|
{
|
|
AFX_MANAGE_STATE(::AfxGetStaticModuleState());
|
|
CString strError, strTitle;
|
|
|
|
switch(nResult)
|
|
{
|
|
case SP_OUTOFDISK:
|
|
VERIFY(strError.LoadString(IDS_PRINT_NODISK));
|
|
break;
|
|
case SP_OUTOFMEMORY:
|
|
VERIFY(strError.LoadString(IDS_PRINT_NOMEMORY));
|
|
break;
|
|
case SP_USERABORT:
|
|
VERIFY(strError.LoadString(IDS_PRINT_USERABORTED));
|
|
break;
|
|
case SP_ERROR:
|
|
default:
|
|
VERIFY(strError.LoadString(IDS_PRINT_GENERIC));
|
|
break;
|
|
}
|
|
strTitle.LoadString(IDS_DESCRIPTION);
|
|
::MessageBox( ::AfxGetMainWnd()->GetSafeHwnd(), strError, strTitle, MB_OK);
|
|
}
|
|
m_pPrintDC->SelectObject(m_pOldFont);
|
|
if (m_pCurrentFont)
|
|
{
|
|
delete m_pCurrentFont;
|
|
}
|
|
this->m_pPrintDC->Detach();
|
|
delete m_pPrintDC;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Used to calculate where on printed page a line of text should go
|
|
// nLineIndex is sequenced line number; csLinecaps is size returned by
|
|
// GetTextExtent for a string of text
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CMSInfoPrintHelper::GetVerticalPos(int nLineIndex,CSize csLinecaps)
|
|
{
|
|
//returns an int which specifies the vertical position at which a given line of text
|
|
//should print
|
|
|
|
CString strLinespacing;
|
|
|
|
//spacing is based on string resource IDS_PRINT_LINESPACING
|
|
strLinespacing.LoadString(IDS_PRINT_LINESPACING);
|
|
TCHAR** ppStopChr = NULL;//not used
|
|
double flLineSpacing =_tcstod(strLinespacing,ppStopChr);
|
|
return (int)(csLinecaps.cy * flLineSpacing )*m_nCurrentLineIndex;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Performs page load-eject on printer
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CMSInfoPrintHelper::Paginate()
|
|
{
|
|
|
|
//TD: print page number in footer
|
|
//Do we assume roman numerals for page numbers?
|
|
|
|
//check to see if this page is within print range
|
|
//if it is, call StartPage and EndPage to make printer spit out paper;
|
|
//otherwise, just change indexes...
|
|
if (IsInPageRange(m_nPageNumber))
|
|
{
|
|
//use string resource for page number format
|
|
CString strPageFooter;
|
|
CString strPageFormat;
|
|
strPageFormat.LoadString(IDS_PRINT_FTR_CTR);
|
|
strPageFooter.Format(strPageFormat,m_nPageNumber);
|
|
//print number in middle of page
|
|
int nHorzRes,nVertRes;
|
|
nHorzRes = m_pPrintDC->GetDeviceCaps(HORZRES);
|
|
nVertRes = m_pPrintDC->GetDeviceCaps(VERTRES);
|
|
|
|
this->m_pPrintDC->TextOut(nHorzRes / 2,nVertRes - this->GetFooterMargin(),strPageFooter);
|
|
EndPage(this->GetHDC());
|
|
m_bNeedsEndPage = FALSE;
|
|
}
|
|
m_nCurrentLineIndex = 0;
|
|
this->m_nPageNumber++;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// determines if page ranges need to be checked
|
|
// and if a given page number is in a specified page range
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
BOOL CMSInfoPrintHelper::IsInPageRange(int nPageNumber)
|
|
{
|
|
//if both m_nStartPage and m_nEndPage are 0, we are printing all pages
|
|
if (-1 == m_nStartPage && -1 == m_nEndPage)
|
|
{
|
|
return TRUE;
|
|
}
|
|
if (nPageNumber >= this->m_nStartPage && nPageNumber <= this->m_nEndPage)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//Gets the space to leave at the bottom of the page for page number, etc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int CMSInfoPrintHelper::GetFooterMargin()
|
|
{
|
|
//use resource string to set footer margin
|
|
CString strRes;
|
|
strRes.LoadString(IDS_PRINT_FTR_CTR );
|
|
CSize sizeText = m_pPrintDC->GetTextExtent(strRes);
|
|
return sizeText.cy;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CMSInfoPrintHelper::PrintHeader()
|
|
{
|
|
CString strHeader;
|
|
strHeader.LoadString(IDS_PRINT_HDR_RIGHT_CURRENT);
|
|
CSize sizeString = m_pPrintDC->GetTextExtent(strHeader);
|
|
int nXPos = m_pPrintDC->GetDeviceCaps(HORZRES) - sizeString.cx;
|
|
this->m_pPrintDC->TextOut(nXPos,0,strHeader);
|
|
m_nCurrentLineIndex++;
|
|
}
|