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.
2811 lines
84 KiB
2811 lines
84 KiB
//=============================================================================
|
|
// MSInfo.cpp : Implementation of CMSInfo
|
|
//
|
|
// Contains implementation for some of the functions in the CMSInfo class
|
|
// (the ones which aren't inline).
|
|
//=============================================================================
|
|
|
|
#include "stdafx.h"
|
|
#include "Msinfo32.h"
|
|
#include "MSInfo.h"
|
|
#include "cabfunc.h"
|
|
#include "msictrl.h"
|
|
#include "MSInfo4Category.h"
|
|
#include "remotedialog.h"
|
|
#include "filestuff.h"
|
|
#include <afxole.h>
|
|
//a-kjaw
|
|
#include "dataset.h"
|
|
//a-kjaw
|
|
|
|
WNDPROC CMSInfo::m_wndprocParent = NULL;
|
|
CMSInfo * CMSInfo::m_pControl = NULL;
|
|
|
|
extern CMSInfoHistoryCategory catHistorySystemSummary;
|
|
extern CMSInfoHistoryCategory catHistoryResources;
|
|
extern CMSInfoHistoryCategory catHistoryComponents;
|
|
extern CMSInfoHistoryCategory catHistorySWEnv;
|
|
|
|
//=========================================================================
|
|
// Here's a very simple class to show a message when the data is
|
|
// being refreshed.
|
|
//=========================================================================
|
|
|
|
class CWaitForRefreshDialog : public CDialogImpl<CWaitForRefreshDialog>
|
|
{
|
|
public:
|
|
enum { IDD = IDD_WAITFORREFRESHDIALOG };
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Refresh the specified category using the specified source.
|
|
//-------------------------------------------------------------------------
|
|
|
|
int DoRefresh(CLiveDataSource * pSource, CMSInfoLiveCategory * pLiveCategory)
|
|
{
|
|
m_pSource = pSource;
|
|
m_pLiveCategory = pLiveCategory;
|
|
m_nCategories = pLiveCategory->GetCategoryCount();
|
|
if (m_nCategories == 0) m_nCategories = 1; // should never happen
|
|
return (int)DoModal();
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
// When the dialog initializes, the source and category pointers should
|
|
// have already been set. Start the refresh and create a timer to control
|
|
// the update of information on the dialog. The timer is set to 500ms.
|
|
//-------------------------------------------------------------------------
|
|
|
|
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
if (m_pSource && m_pSource->m_pThread && m_pLiveCategory)
|
|
m_pSource->m_pThread->StartRefresh(m_pLiveCategory, TRUE);
|
|
m_iTimer = (UINT)SetTimer(1, 500);
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Every time the timer fires, check to see if the refresh is done. If
|
|
// it is, close the dialog. Otherwise, update the progress bar and
|
|
// refreshing category string.
|
|
//-------------------------------------------------------------------------
|
|
|
|
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
if (m_pSource == NULL)
|
|
return 0;
|
|
|
|
if (!m_pSource->m_pThread->IsRefreshing())
|
|
{
|
|
KillTimer(m_iTimer);
|
|
EndDialog(0);
|
|
return 0;
|
|
}
|
|
|
|
CString strCurrent;
|
|
LONG nCount;
|
|
|
|
m_pSource->m_pThread->GetRefreshStatus(&nCount, &strCurrent);
|
|
if (nCount > 0) nCount -= 1; // this number is incremented before the refresh is complete
|
|
UpdateProgress((nCount * 100) / m_nCategories, strCurrent);
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Update the percentage complete and the refreshing category name.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void UpdateProgress(int iPercent, const CString & strCurrent = _T(""))
|
|
{
|
|
HWND hwnd = GetDlgItem(IDC_REFRESHPROGRESS);
|
|
if (hwnd != NULL)
|
|
{
|
|
if (iPercent < 0)
|
|
iPercent = 0;
|
|
if (iPercent > 100)
|
|
iPercent = 100;
|
|
|
|
::SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
|
|
::SendMessage(hwnd, PBM_SETPOS, iPercent, 0);
|
|
}
|
|
|
|
if (!strCurrent.IsEmpty())
|
|
{
|
|
hwnd = GetDlgItem(IDC_REFRESHCAT);
|
|
if (hwnd != NULL)
|
|
::SetWindowText(hwnd, strCurrent);
|
|
}
|
|
}
|
|
|
|
private:
|
|
CLiveDataSource * m_pSource;
|
|
CMSInfoLiveCategory * m_pLiveCategory;
|
|
int m_nCategories;
|
|
UINT m_iTimer;
|
|
|
|
BEGIN_MSG_MAP(CWaitForRefreshDialog)
|
|
MESSAGE_HANDLER(WM_TIMER, OnTimer)
|
|
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
|
|
END_MSG_MAP()
|
|
};
|
|
|
|
UINT HistoryRefreshDlgThreadProc( LPVOID pParam )
|
|
{
|
|
CMSInfo* pInfo = (CMSInfo*) pParam;
|
|
if (WAIT_OBJECT_0 != WaitForSingleObject(pInfo->m_hEvtHistoryComplete,60*60*10/*10 minutes*/))
|
|
{
|
|
ASSERT(0 && "Wait Abandoned or timed out");
|
|
}
|
|
pInfo->m_HistoryProgressDlg.EndDialog(MB_OK);//use if dlg was create with DoModal()
|
|
return 0;
|
|
}
|
|
|
|
//=========================================================================
|
|
// A function to check if a file is a version 4.x (compound document) file
|
|
// (for bug 582973). It checks this by opening the file, opening the
|
|
// "MSInfo" stream and checking the version number.
|
|
//=========================================================================
|
|
|
|
BOOL IsVersion4File(const CString & strFilename)
|
|
{
|
|
BOOL fReturn = FALSE;
|
|
DWORD grfMode = STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE;
|
|
CComBSTR bstrFileName(strFilename);
|
|
CComPtr<IStorage> pStorage;
|
|
|
|
if (SUCCEEDED(StgOpenStorage(bstrFileName, NULL, grfMode, NULL, 0, &pStorage)))
|
|
{
|
|
CComPtr<IStream> pStream;
|
|
CComBSTR bstrMSIStream(_T("MSInfo"));
|
|
|
|
if (SUCCEEDED(pStorage->OpenStream(bstrMSIStream, NULL, grfMode, 0, &pStream)))
|
|
{
|
|
const DWORD MSI_FILE_VER = 0x03000000;
|
|
COleStreamFile * pOStream;
|
|
DWORD dwVersion;
|
|
ULONG ulCount;
|
|
|
|
pOStream = new COleStreamFile(pStream);
|
|
if (pOStream->Read((void *) &dwVersion, sizeof(DWORD)) == sizeof(DWORD))
|
|
fReturn = (dwVersion == MSI_FILE_VER);
|
|
delete pOStream;
|
|
}
|
|
}
|
|
return fReturn;
|
|
}
|
|
|
|
//=========================================================================
|
|
// Dispatch a command from the user (such as a menu bar selection).
|
|
//=========================================================================
|
|
|
|
BOOL CMSInfo::DispatchCommand(int iCommand)
|
|
{
|
|
BOOL fHandledCommand = TRUE;
|
|
|
|
// Can't do any command while the find is in progress.
|
|
|
|
if (m_fInFind)
|
|
{
|
|
CancelFind();
|
|
SelectCategory(GetCurrentCategory());
|
|
}
|
|
|
|
// Before we execute a command, make sure that any refreshes currently
|
|
// in progress are finished.
|
|
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
if (pCategory && pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
CLiveDataSource * pLiveDataSource = (CLiveDataSource *) m_pCurrData;
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
pLiveDataSource->WaitForRefresh();
|
|
::SetCursor(hc);
|
|
}
|
|
|
|
// Check to see if the selected command is a tool that we added to the
|
|
// tools menu.
|
|
|
|
CMSInfoTool * pTool;
|
|
if (m_mapIDToTool.Lookup((WORD) iCommand, (void * &) pTool))
|
|
{
|
|
ASSERT(pTool);
|
|
if (pTool)
|
|
pTool->Execute();
|
|
return TRUE;
|
|
}
|
|
|
|
switch (iCommand)
|
|
{
|
|
case ID_FILE_OPENNFO:
|
|
if (m_fFindVisible)
|
|
DispatchCommand(ID_EDIT_FIND);
|
|
OpenNFO();
|
|
break;
|
|
|
|
case ID_FILE_SAVENFO:
|
|
SaveNFO();
|
|
break;
|
|
|
|
case ID_FILE_CLOSE:
|
|
if (m_fFindVisible)
|
|
DispatchCommand(ID_EDIT_FIND);
|
|
CloseFile();
|
|
break;
|
|
|
|
case ID_FILE_EXPORT:
|
|
Export();
|
|
break;
|
|
|
|
case ID_FILE_PRINT:
|
|
DoPrint();
|
|
break;
|
|
|
|
case ID_FILE_EXIT:
|
|
if (NULL != m_hwndParent)
|
|
::PostMessage(m_hwndParent, WM_CLOSE, 0, 0);
|
|
break;
|
|
|
|
case ID_EDIT_COPY:
|
|
EditCopy();
|
|
break;
|
|
|
|
case ID_EDIT_PASTE:
|
|
if (GetFocus() == m_wndFindWhat.m_hWnd && m_wndFindWhat.IsWindowVisible() && m_wndFindWhat.IsWindowEnabled())
|
|
{
|
|
BOOL fHandled = FALSE;
|
|
if (::OpenClipboard(m_hWnd))
|
|
{
|
|
if (::IsClipboardFormatAvailable(CF_UNICODETEXT))
|
|
{
|
|
HANDLE h = ::GetClipboardData(CF_UNICODETEXT);
|
|
if (h != NULL)
|
|
{
|
|
LPWSTR szData = (LPWSTR)GlobalLock(h);
|
|
if (szData != NULL)
|
|
{
|
|
// If the user tries to paste a tab character, replace it with spaces.
|
|
|
|
CString strTemp(szData);
|
|
if (strTemp.Find(_T('\t')) != -1)
|
|
strTemp.Replace(_T('\t'), _T(' '));
|
|
|
|
SETTEXTEX st;
|
|
st.flags = ST_SELECTION;
|
|
st.codepage = 1200; // Unicode
|
|
m_wndFindWhat.SendMessage(EM_SETTEXTEX, (WPARAM)&st, (LPARAM)(LPCTSTR)strTemp);
|
|
fHandled = TRUE;
|
|
GlobalUnlock(h);
|
|
}
|
|
}
|
|
}
|
|
|
|
::CloseClipboard();
|
|
}
|
|
|
|
if (!fHandled)
|
|
m_wndFindWhat.SendMessage(WM_PASTE);
|
|
}
|
|
break;
|
|
|
|
case ID_EDIT_SELECTALL:
|
|
EditSelectAll();
|
|
break;
|
|
|
|
case ID_EDIT_FIND:
|
|
m_fFindVisible = !m_fFindVisible;
|
|
|
|
m_fFindNext = FALSE;
|
|
m_pcatFind = NULL;
|
|
m_fCancelFind = FALSE;
|
|
m_fInFind = FALSE;
|
|
|
|
ShowFindControls();
|
|
LayoutControl();
|
|
SetMenuItems();
|
|
UpdateFindControls();
|
|
|
|
if (m_fFindVisible)
|
|
GotoDlgCtrl(m_wndFindWhat.m_hWnd);
|
|
|
|
break;
|
|
|
|
case ID_VIEW_REFRESH:
|
|
MSInfoRefresh();
|
|
break;
|
|
|
|
case ID_VIEW_BASIC:
|
|
if (m_fAdvanced)
|
|
{
|
|
m_fAdvanced = FALSE;
|
|
RefillListView(FALSE);
|
|
SetMenuItems();
|
|
}
|
|
break;
|
|
|
|
case ID_VIEW_ADVANCED:
|
|
if (!m_fAdvanced)
|
|
{
|
|
m_fAdvanced = TRUE;
|
|
RefillListView(FALSE);
|
|
SetMenuItems();
|
|
}
|
|
break;
|
|
|
|
case ID_VIEW_REMOTE_COMPUTER:
|
|
ShowRemoteDialog();
|
|
break;
|
|
|
|
case ID_VIEW_CURRENT:
|
|
case ID_VIEW_HISTORY:
|
|
{
|
|
int iShow = (iCommand == ID_VIEW_HISTORY) ? SW_SHOW : SW_HIDE;
|
|
|
|
/* v-stlowe 2/27/2001:
|
|
problem: if history was loaded from file after combo has been populated,
|
|
combo doesn't get updated. So update each time we switch to history view
|
|
|
|
if (iCommand == ID_VIEW_HISTORY && m_history.SendMessage(CB_GETCURSEL, 0, 0) == CB_ERR)
|
|
*/
|
|
if (iCommand == ID_VIEW_HISTORY)
|
|
FillHistoryCombo();
|
|
|
|
|
|
//v-stlowe 3/04/2001
|
|
//if (!this->m_pHistoryStream)
|
|
//{
|
|
if (this->m_pDCO && !((CLiveDataSource *)m_pLiveData)->GetXMLDoc() && ID_VIEW_HISTORY == iCommand)
|
|
{
|
|
VERIFY(m_pDCO && "NULL datacollection object");
|
|
if (m_pDCO)
|
|
{
|
|
HRESULT hr;
|
|
HWND hWnd = m_HistoryProgressDlg.GetDlgItem(IDC_PROGRESS1);
|
|
if(::IsWindow(hWnd))
|
|
{
|
|
::SendMessage(hWnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
|
|
::SendMessage(hWnd,PBM_SETPOS,0,0);
|
|
::SendMessage(hWnd, PBM_DELTAPOS, 0, 0L);
|
|
}
|
|
m_pDCO->ExecuteAsync();
|
|
|
|
//m_HistoryProgressDlg.Create(m_hWnd);
|
|
ResetEvent(m_hEvtHistoryComplete);
|
|
AfxBeginThread((AFX_THREADPROC) HistoryRefreshDlgThreadProc,this);
|
|
m_HistoryProgressDlg.DoModal(m_hWnd);
|
|
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
//end v-stlowe 12/17/00
|
|
m_history.ShowWindow(iShow);
|
|
m_historylabel.ShowWindow(iShow);
|
|
LayoutControl();
|
|
|
|
HTREEITEM htiToSelect = NULL;
|
|
|
|
if (iCommand == ID_VIEW_HISTORY)
|
|
{
|
|
m_pLastCurrentCategory = GetCurrentCategory();
|
|
|
|
int iIndex = (int)m_history.SendMessage(CB_GETCURSEL, 0, 0);
|
|
if (iIndex == CB_ERR)
|
|
{
|
|
iIndex = 0;
|
|
m_history.SendMessage(CB_SETCURSEL, (WPARAM)iIndex, 0);
|
|
}
|
|
ChangeHistoryView(iIndex);
|
|
|
|
// Select the appropriate history category based on the current info category.
|
|
|
|
CMSInfoHistoryCategory * pHistoryCat = NULL;
|
|
CString strName;
|
|
|
|
m_pLastCurrentCategory->GetNames(NULL, &strName);
|
|
if (!strName.IsEmpty())
|
|
{
|
|
// This is a little kludgy:
|
|
//todo: append file name if XML stream was opened from a file
|
|
if (strName.Left(13) == CString(_T("SystemSummary")))
|
|
pHistoryCat = &catHistorySystemSummary;
|
|
else if (strName.Left(9) == CString(_T("Resources")))
|
|
pHistoryCat = &catHistoryResources;
|
|
else if (strName.Left(10) == CString(_T("Components")))
|
|
pHistoryCat = &catHistoryComponents;
|
|
else if (strName.Left(5) == CString(_T("SWEnv")))
|
|
pHistoryCat = &catHistorySWEnv;
|
|
}
|
|
|
|
if (pHistoryCat)
|
|
htiToSelect = pHistoryCat->GetHTREEITEM();
|
|
}
|
|
else
|
|
{
|
|
ChangeHistoryView(-1);
|
|
|
|
// Changing to always select the system summary category when
|
|
// switching back from history view.
|
|
//
|
|
// if (m_pLastCurrentCategory)
|
|
// htiToSelect = m_pLastCurrentCategory->GetHTREEITEM();
|
|
|
|
htiToSelect = TreeView_GetRoot(m_tree.m_hWnd);
|
|
}
|
|
|
|
if (htiToSelect != NULL)
|
|
{
|
|
TreeView_EnsureVisible(m_tree.m_hWnd, htiToSelect);
|
|
TreeView_SelectItem(m_tree.m_hWnd, htiToSelect);
|
|
}
|
|
|
|
SetMenuItems();
|
|
}
|
|
break;
|
|
|
|
case ID_TOOLS_PLACEHOLDER:
|
|
break;
|
|
|
|
case ID_HELP_ABOUT:
|
|
{
|
|
CSimpleDialog<IDD_ABOUTBOX> dlg;
|
|
dlg.DoModal();
|
|
}
|
|
break;
|
|
|
|
case ID_HELP_CONTENTS:
|
|
//::HtmlHelp(m_hWnd, _T("msinfo32.chm"), HH_DISPLAY_TOPIC, 0);
|
|
ShowHSCHelp(_T("msinfo_overview.htm"));
|
|
break;
|
|
|
|
case ID_HELP_TOPIC:
|
|
ShowCategoryHelp(GetCurrentCategory());
|
|
break;
|
|
|
|
default:
|
|
fHandledCommand = FALSE;
|
|
break;
|
|
}
|
|
|
|
return fHandledCommand;
|
|
}
|
|
|
|
//=========================================================================
|
|
// Called to allow the user to remote to a different computer.
|
|
//=========================================================================
|
|
|
|
void CMSInfo::ShowRemoteDialog()
|
|
{
|
|
AFX_MANAGE_STATE(::AfxGetStaticModuleState());
|
|
|
|
CRemoteDialog dlg;
|
|
dlg.SetRemoteDialogValues(m_hWnd, !m_strMachine.IsEmpty(), m_strMachine);
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
CString strMachine;
|
|
BOOL fRemote;
|
|
|
|
dlg.GetRemoteDialogValues(&fRemote, &strMachine);
|
|
if (!fRemote)
|
|
strMachine.Empty();
|
|
|
|
if (strMachine.CompareNoCase(m_strMachine) != 0)
|
|
DoRemote(strMachine);
|
|
|
|
::SetCursor(hc);
|
|
}
|
|
}
|
|
|
|
void CMSInfo::DoRemote(LPCTSTR szMachine)
|
|
{
|
|
CString strMachine(szMachine);
|
|
|
|
// The user has changed the machine name. We need to recreate the
|
|
// current data source object with the new machine name. Also, make
|
|
// sure we aren't showing history data.
|
|
|
|
if (m_history.IsWindowVisible())
|
|
DispatchCommand(ID_VIEW_CURRENT);
|
|
|
|
CLiveDataSource * pLiveData = new CLiveDataSource;
|
|
if (pLiveData)
|
|
{
|
|
HRESULT hr = pLiveData->Create(strMachine, m_hWnd, m_strCategories);
|
|
if (FAILED(hr))
|
|
{
|
|
// bad news, report an error
|
|
delete pLiveData;
|
|
}
|
|
else
|
|
{
|
|
// Check to see if the pLiveData works. If it doesn't (for example,
|
|
// if it's to a non-existent machine), don't change the data source.
|
|
|
|
HRESULT hr = pLiveData->ValidDataSource();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pLiveData->m_pHistoryStream = ((CLiveDataSource *)m_pLiveData)->m_pHistoryStream;
|
|
pLiveData->m_pXMLDoc = ((CLiveDataSource *)m_pLiveData)->m_pXMLDoc;
|
|
|
|
if (m_pLiveData)
|
|
delete m_pLiveData;
|
|
m_pLiveData = pLiveData;
|
|
m_strMachine = strMachine;
|
|
SelectDataSource(m_pLiveData);
|
|
}
|
|
else
|
|
{
|
|
// Report the error for the bad connection.
|
|
|
|
CString strMessage;
|
|
|
|
if (strMachine.IsEmpty())
|
|
strMessage.LoadString(IDS_REMOTEERRORLOCAL);
|
|
else
|
|
strMessage.Format(IDS_REMOTEERRORREMOTE, strMachine);
|
|
|
|
MSInfoMessageBox(strMessage);
|
|
delete pLiveData;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// bad news - no memory
|
|
}
|
|
}
|
|
|
|
//=========================================================================
|
|
// Functions for managing the displayed data.
|
|
//=========================================================================
|
|
|
|
void CMSInfo::SelectDataSource(CDataSource * pDataSource)
|
|
{
|
|
ASSERT(pDataSource);
|
|
if (pDataSource == NULL || m_pCurrData == pDataSource)
|
|
return;
|
|
m_pCurrData = pDataSource;
|
|
m_pCategory = NULL;
|
|
|
|
// Clear the existing categories in the tree.
|
|
|
|
TreeClearItems();
|
|
|
|
// Load the contents of the tree from the data source.
|
|
|
|
CMSInfoCategory * pRoot = m_pCurrData->GetRootCategory();
|
|
if (pRoot)
|
|
{
|
|
BuildTree(TVI_ROOT, TVI_LAST, pRoot);
|
|
TreeView_Expand(m_tree.m_hWnd, TreeView_GetRoot(m_tree.m_hWnd), TVE_EXPAND);
|
|
TreeView_SelectItem(m_tree.m_hWnd, TreeView_GetRoot(m_tree.m_hWnd));
|
|
}
|
|
|
|
SetMenuItems();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Select the specified category.
|
|
//
|
|
// TBD - better to check columns for being the same
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SelectCategory(CMSInfoCategory * pCategory, BOOL fRefreshDataOnly)
|
|
{
|
|
ASSERT(pCategory);
|
|
if (pCategory == NULL) return;
|
|
|
|
// If there's a currently selected category, save some information
|
|
// for it (such as the widths of the columns that the user might
|
|
// have changed).
|
|
|
|
if (m_pCategory && !fRefreshDataOnly && m_pCategory->GetDataSourceType() != NFO_410)
|
|
{
|
|
int iWidth;
|
|
|
|
ASSERT(m_iCategoryColNumberLen <= 64);
|
|
for (int iListViewCol = 0; iListViewCol < m_iCategoryColNumberLen; iListViewCol++)
|
|
{
|
|
iWidth = ListView_GetColumnWidth(m_list.m_hWnd, iListViewCol);
|
|
m_pCategory->SetColumnWidth(m_aiCategoryColNumber[iListViewCol], iWidth);
|
|
}
|
|
}
|
|
|
|
ListClearItems();
|
|
if (!fRefreshDataOnly && pCategory && pCategory->GetDataSourceType() != NFO_410)
|
|
{
|
|
ListClearColumns();
|
|
m_iCategoryColNumberLen = 0;
|
|
int iColCount;
|
|
if (pCategory->GetCategoryDimensions(&iColCount, NULL))
|
|
{
|
|
CString strCaption;
|
|
UINT uiWidth;
|
|
int iListViewCol = 0;
|
|
|
|
for (int iCategoryCol = 0; iCategoryCol < iColCount; iCategoryCol++)
|
|
{
|
|
if (!m_fAdvanced && pCategory->IsColumnAdvanced(iCategoryCol))
|
|
continue;
|
|
|
|
if (pCategory->GetColumnInfo(iCategoryCol, &strCaption, &uiWidth, NULL, NULL)) // TBD - faster to return reference to string
|
|
{
|
|
// Save what the actual column number (for the category) was.
|
|
|
|
ASSERT(iListViewCol < 64);
|
|
m_aiCategoryColNumber[iListViewCol] = iCategoryCol;
|
|
ListInsertColumn(iListViewCol++, (int)uiWidth, strCaption);
|
|
m_iCategoryColNumberLen = iListViewCol;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the currently displayed category is from a 4.10 NFO file, and we're showing a
|
|
// new category, then hide the existing category.
|
|
|
|
if (m_pCategory && m_pCategory != pCategory && m_pCategory->GetDataSourceType() == NFO_410)
|
|
((CMSInfo4Category *) m_pCategory)->ShowControl(m_hWnd, this->GetOCXRect(), FALSE);
|
|
|
|
// Save the currently displayed category.
|
|
|
|
m_pCategory = pCategory;
|
|
|
|
// If this is live data and has never been refreshed, refresh and return.
|
|
// Refresh will send a message causing this function to be executed again.
|
|
|
|
if (pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
CMSInfoLiveCategory * pLiveCategory = (CMSInfoLiveCategory *) pCategory;
|
|
if (!pLiveCategory->EverBeenRefreshed())
|
|
{
|
|
SetMessage((m_history.IsWindowVisible()) ? IDS_REFRESHHISTORYMESSAGE : IDS_REFRESHMESSAGE, 0, TRUE);
|
|
|
|
CLiveDataSource * pLiveDataSource = (CLiveDataSource *) m_pCurrData;
|
|
if (pLiveDataSource->InRefresh())
|
|
{
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
pLiveCategory->Refresh((CLiveDataSource *) m_pCurrData, FALSE);
|
|
::SetCursor(hc);
|
|
}
|
|
else
|
|
pLiveCategory->Refresh((CLiveDataSource *) m_pCurrData, FALSE);
|
|
return;
|
|
}
|
|
}
|
|
else if (pCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
this->m_list.ShowWindow(SW_HIDE);
|
|
|
|
CMSInfo4Category * p4Cat = (CMSInfo4Category *) pCategory;
|
|
if (!p4Cat->IsDisplayableCategory())
|
|
SetMessage(IDS_SELECTCATEGORY, 0, TRUE);
|
|
else if (FAILED(p4Cat->ShowControl(m_hWnd,this->GetOCXRect())))
|
|
SetMessage(IDS_NOOCX, IDS_NOOCXDETAIL, TRUE);
|
|
|
|
return;
|
|
}
|
|
else if (pCategory->GetDataSourceType() == XML_SNAPSHOT)
|
|
{
|
|
((CXMLSnapshotCategory*)pCategory)->Refresh((CXMLDataSource*) m_pCurrData, FALSE);
|
|
CMSInfoLiveCategory * pLiveCategory = (CMSInfoLiveCategory *) pCategory;
|
|
// Any category that has children (besides the root category) doesn't display
|
|
// information. So put up a message to that effect.
|
|
|
|
if (pLiveCategory->GetFirstChild() != NULL && pLiveCategory->GetParent() != NULL)
|
|
{
|
|
SetMessage(IDS_SELECTCATEGORY, 0, TRUE);
|
|
return;
|
|
}
|
|
else if (!pLiveCategory->EverBeenRefreshed())
|
|
{
|
|
SetMessage((m_history.IsWindowVisible()) ? IDS_REFRESHHISTORYMESSAGE : IDS_REFRESHMESSAGE, 0, TRUE);
|
|
return;
|
|
}
|
|
|
|
}
|
|
// Set the columns and fill the rows with the data for this category.
|
|
// Note, if this is live data we need to lock it (so we don't have
|
|
// a threading problem with the refresh).
|
|
|
|
CLiveDataSource * pLiveDataSource = NULL;
|
|
if (pCategory->GetDataSourceType() == LIVE_DATA)
|
|
pLiveDataSource = (CLiveDataSource *) m_pCurrData;
|
|
|
|
if (pLiveDataSource)
|
|
pLiveDataSource->LockData();
|
|
|
|
if (SUCCEEDED(pCategory->GetHRESULT()))
|
|
{
|
|
int iColCount, iRowCount;
|
|
if (pCategory->GetCategoryDimensions(&iColCount, &iRowCount))
|
|
{
|
|
CString * pstrData , strCaption , cstring;
|
|
DWORD dwData;
|
|
int iListViewCol, iListViewRow = 0;
|
|
|
|
for (int iCategoryRow = 0; iCategoryRow < iRowCount; iCategoryRow++)
|
|
{
|
|
if (!m_fAdvanced && pCategory->IsRowAdvanced(iCategoryRow))
|
|
continue;
|
|
|
|
iListViewCol = 0;
|
|
for (int iCategoryCol = 0; iCategoryCol < iColCount; iCategoryCol++)
|
|
{
|
|
if (!m_fAdvanced && pCategory->IsColumnAdvanced(iCategoryCol))
|
|
continue;
|
|
|
|
if (pCategory->GetData(iCategoryRow, iCategoryCol, &pstrData, &dwData))
|
|
{
|
|
//a-kjaw
|
|
if(pstrData->IsEmpty())
|
|
{
|
|
pCategory->GetColumnInfo(iCategoryCol, &strCaption, NULL , NULL, NULL);
|
|
cstring.LoadString(IDS_SERVERNAME);
|
|
if( strCaption == cstring )
|
|
pstrData->LoadString(IDS_LOCALSERVER);
|
|
|
|
}
|
|
//a-kjaw
|
|
ListInsertItem(iListViewRow, iListViewCol++, *pstrData, iCategoryRow);
|
|
}
|
|
}
|
|
|
|
iListViewRow += 1;
|
|
}
|
|
}
|
|
|
|
// Return the sorting to how it was last set.
|
|
|
|
if (pCategory->m_iSortColumn != -1)
|
|
ListView_SortItems(m_list.m_hWnd, (PFNLVCOMPARE) ListSortFunc, (LPARAM) pCategory);
|
|
|
|
if (iColCount == 0 || (iRowCount == 0 && pCategory->GetFirstChild() != NULL))
|
|
SetMessage(IDS_SELECTCATEGORY, 0, TRUE);
|
|
else
|
|
SetMessage(0);
|
|
}
|
|
else
|
|
{
|
|
// The HRESULT for this category indicates some sort of failure. We should display
|
|
// an error message instead of the list view.
|
|
|
|
CString strTitle, strMessage;
|
|
pCategory->GetErrorText(&strTitle, &strMessage);
|
|
SetMessage(strTitle, strMessage, TRUE);
|
|
}
|
|
|
|
if (pLiveDataSource)
|
|
pLiveDataSource->UnlockData();
|
|
|
|
SetMenuItems();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Get the currently selected category.
|
|
//-------------------------------------------------------------------------
|
|
|
|
CMSInfoCategory * CMSInfo::GetCurrentCategory()
|
|
{
|
|
HTREEITEM hti = TreeView_GetSelection(m_tree.m_hWnd);
|
|
if (hti)
|
|
{
|
|
TVITEM tvi;
|
|
tvi.mask = TVIF_PARAM;
|
|
tvi.hItem = hti;
|
|
|
|
if (TreeView_GetItem(m_tree.m_hWnd, &tvi))
|
|
{
|
|
ASSERT(tvi.lParam);
|
|
ASSERT(tvi.lParam == (LPARAM)m_pCategory);
|
|
return (CMSInfoCategory *) tvi.lParam;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Refresh the displayed data.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::MSInfoRefresh()
|
|
{
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
if (pCategory && pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
CMSInfoLiveCategory * pLiveCategory = (CMSInfoLiveCategory *)pCategory;
|
|
ListClearItems();
|
|
SetMessage(IDS_REFRESHMESSAGE);
|
|
pLiveCategory->Refresh((CLiveDataSource *) m_pCurrData, FALSE);
|
|
}
|
|
else if (pCategory && pCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
CMSInfo4Category* p4Category = (CMSInfo4Category*) pCategory;
|
|
p4Category->Refresh();
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Present the user with a dialog box to select a file to open.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::OpenNFO()
|
|
{
|
|
// Display the dialog box and let the user select a file.
|
|
|
|
TCHAR szBuffer[MAX_PATH] = _T("");
|
|
TCHAR szFilter[MAX_PATH];
|
|
TCHAR szDefaultExtension[4];
|
|
|
|
::LoadString(_Module.GetResourceInstance(), IDS_OPENFILTER, szFilter, MAX_PATH);
|
|
::LoadString(_Module.GetResourceInstance(), IDS_DEFAULTEXTENSION, szDefaultExtension, 4);
|
|
|
|
for (int i = 0; szFilter[i]; i++)
|
|
if (szFilter[i] == _T('|'))
|
|
szFilter[i] = _T('\0');
|
|
|
|
OPENFILENAME ofn;
|
|
ZeroMemory(&ofn, sizeof(OPENFILENAME));
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.nFilterIndex = 1;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.lpstrFile = szBuffer;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = NULL; // maybe use later?
|
|
ofn.nMaxFileTitle = 0;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = NULL;
|
|
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
|
|
ofn.lpstrDefExt = szDefaultExtension;
|
|
|
|
if (!::GetOpenFileName(&ofn))
|
|
return; // user cancelled
|
|
|
|
OpenMSInfoFile(ofn.lpstrFile, ofn.nFileExtension);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// SaveNFO allows the user to select a filename, and saves the current
|
|
// data to an NFO file.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SaveNFO()
|
|
{
|
|
// Present the user with a dialog box to select a name for saving.
|
|
|
|
TCHAR szBuffer[MAX_PATH] = _T("");
|
|
TCHAR szFilter[MAX_PATH];
|
|
TCHAR szDefaultExtension[4];
|
|
|
|
//v-stlowe 3/19/2001 if (m_fHistoryAvailable && m_strMachine.IsEmpty())
|
|
if (m_fHistorySaveAvailable && m_strMachine.IsEmpty())
|
|
::LoadString(_Module.GetResourceInstance(), IDS_SAVEBOTHFILTER, szFilter, MAX_PATH);
|
|
else
|
|
::LoadString(_Module.GetResourceInstance(), IDS_SAVENFOFILTER, szFilter, MAX_PATH);
|
|
|
|
::LoadString(_Module.GetResourceInstance(), IDS_DEFAULTEXTENSION, szDefaultExtension, 4);
|
|
|
|
for (int i = 0; szFilter[i]; i++)
|
|
if (szFilter[i] == _T('|'))
|
|
szFilter[i] = _T('\0');
|
|
|
|
OPENFILENAME ofn;
|
|
ZeroMemory(&ofn, sizeof(OPENFILENAME));
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.lpstrFile = szBuffer;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = NULL; // maybe use later?
|
|
ofn.nMaxFileTitle = 0;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = NULL;
|
|
ofn.Flags = OFN_OVERWRITEPROMPT;
|
|
ofn.lpstrDefExt = szDefaultExtension;
|
|
|
|
if (!::GetSaveFileName(&ofn))
|
|
return; // user cancelled
|
|
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
|
|
CString strFileName(ofn.lpstrFile);
|
|
|
|
if (strFileName.Right(4).CompareNoCase(_T(".xml")) == 0)
|
|
SaveXML(strFileName);
|
|
else
|
|
SaveMSInfoFile(strFileName, ofn.nFilterIndex);
|
|
|
|
::SetCursor(hc);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Actually saves the current information to an NFO file.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SaveMSInfoFile(LPCTSTR szFilename, DWORD dwFilterIndex)
|
|
{
|
|
ASSERT(m_pCurrData);
|
|
|
|
if (m_history.IsWindowVisible())
|
|
DispatchCommand(ID_VIEW_CURRENT);
|
|
|
|
if (m_pCurrData)
|
|
{
|
|
CMSInfoCategory * pCategory = m_pCurrData->GetRootCategory();
|
|
if (pCategory)
|
|
{
|
|
HANDLE hFile = ::CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
CMSInfoLiveCategory * pLiveCategory = (CMSInfoLiveCategory *) pCategory;
|
|
|
|
if (m_fNoUI)
|
|
pLiveCategory->RefreshSynchronous((CLiveDataSource *) m_pCurrData, TRUE);
|
|
else
|
|
RefreshData((CLiveDataSource *)m_pCurrData, pLiveCategory);
|
|
}
|
|
else if (pCategory->GetDataSourceType() == XML_SNAPSHOT)
|
|
((CXMLSnapshotCategory *)pCategory)->Refresh((CXMLDataSource *)m_pCurrData, TRUE);
|
|
|
|
//PENDING dependence on filter order. Always add new filters to the end.
|
|
if (dwFilterIndex == 1)//NFO_700
|
|
pCategory->SaveXML(hFile);
|
|
else
|
|
pCategory->SaveNFO(hFile, pCategory, TRUE);
|
|
::CloseHandle(hFile);
|
|
}
|
|
else
|
|
{
|
|
DWORD dwError = ::GetLastError();
|
|
|
|
LPVOID lpMsgBuf;
|
|
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dwError,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL);
|
|
|
|
// TBD Process any inserts in lpMsgBuf.
|
|
|
|
CString strCaption;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
::MessageBox(m_hWnd, (LPCTSTR) lpMsgBuf, strCaption, MB_ICONEXCLAMATION | MB_OK);
|
|
::LocalFree(lpMsgBuf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Save an XML file containing the history information.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SaveXML(const CString & strFileName)
|
|
{
|
|
if (m_pHistoryStream == NULL)
|
|
{
|
|
MSInfoMessageBox(IDS_XMLSAVEERR);
|
|
return;
|
|
}
|
|
|
|
// get the stream status, so we can determine the size of the stream
|
|
|
|
STATSTG streamStat;
|
|
HRESULT hr = m_pHistoryStream->Stat(&streamStat,STATFLAG_NONAME );
|
|
if (FAILED(hr))
|
|
{
|
|
ASSERT(0 && "couldn't get stream statistics");
|
|
MSInfoMessageBox(IDS_XMLSAVEERR);
|
|
return;
|
|
}
|
|
|
|
// allocate buffer of appropriate size
|
|
|
|
BYTE* pBuffer = new BYTE[streamStat.cbSize.LowPart];
|
|
ULONG ulRead;
|
|
|
|
// seek to beginning of stream
|
|
|
|
ULARGE_INTEGER uliSeekPtr;
|
|
LARGE_INTEGER liSeekLoc;
|
|
liSeekLoc.QuadPart = 0;
|
|
hr = m_pHistoryStream->Seek(liSeekLoc,0,&uliSeekPtr);
|
|
if (FAILED(hr))
|
|
{
|
|
MSInfoMessageBox(IDS_XMLSAVEERR);
|
|
if (pBuffer)
|
|
delete [] pBuffer;
|
|
return;
|
|
}
|
|
hr = m_pHistoryStream->Read(pBuffer,streamStat.cbSize.LowPart,&ulRead);
|
|
if (FAILED(hr) || !pBuffer)
|
|
{
|
|
MSInfoMessageBox(IDS_XMLSAVEERR);
|
|
if (pBuffer)
|
|
delete [] pBuffer;
|
|
return;
|
|
}
|
|
if(ulRead != streamStat.cbSize.LowPart)
|
|
{
|
|
ASSERT(0 && "Not enough bytes read from stream");
|
|
MSInfoMessageBox(IDS_XMLSAVEERR);
|
|
if (pBuffer)
|
|
delete [] pBuffer;
|
|
return;
|
|
}
|
|
|
|
CFile file;
|
|
try
|
|
{
|
|
|
|
file.Open(strFileName, CFile::modeCreate | CFile::modeWrite);
|
|
file.Write(pBuffer,ulRead);
|
|
|
|
}
|
|
catch (CFileException * pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (...)
|
|
{
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CString strCaption, strMessage;
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
strMessage.LoadString(IDS_XMLSAVEERR);
|
|
::MessageBox(NULL,strMessage, strCaption,MB_OK);
|
|
}
|
|
delete [] pBuffer;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Export allows the user to select a filename, and saves the current
|
|
// data to a text or XML file.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::Export()
|
|
{
|
|
// Present the user with a dialog box to select a name for saving.
|
|
|
|
TCHAR szBuffer[MAX_PATH] = _T("");
|
|
TCHAR szFilter[MAX_PATH];
|
|
TCHAR szTitle[MAX_PATH] = _T("");
|
|
TCHAR szDefaultExtension[4];
|
|
|
|
::LoadString(_Module.GetResourceInstance(), IDS_EXPORTFILTER, szFilter, MAX_PATH); // TBD - add XML
|
|
::LoadString(_Module.GetResourceInstance(), IDS_DEFAULTEXPORTEXTENSION, szDefaultExtension, 4);
|
|
::LoadString(_Module.GetResourceInstance(), IDS_EXPORTDIALOGTITLE, szTitle, MAX_PATH);
|
|
|
|
for (int i = 0; szFilter[i]; i++)
|
|
if (szFilter[i] == _T('|'))
|
|
szFilter[i] = _T('\0');
|
|
|
|
OPENFILENAME ofn;
|
|
ZeroMemory(&ofn, sizeof(OPENFILENAME));
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.nFilterIndex = 0;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.lpstrFile = szBuffer;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrFileTitle = NULL; // maybe use later?
|
|
ofn.nMaxFileTitle = 0;
|
|
ofn.lpstrInitialDir = NULL;
|
|
ofn.lpstrTitle = (szTitle[0] == _T('\0')) ? NULL : szTitle;
|
|
ofn.Flags = OFN_OVERWRITEPROMPT;
|
|
ofn.lpstrDefExt = szDefaultExtension;
|
|
|
|
if (!::GetSaveFileName(&ofn))
|
|
return; // user cancelled
|
|
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
ExportFile(ofn.lpstrFile, ofn.nFileExtension);
|
|
::SetCursor(hc);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Open the specified file (it might be XML, NFO, CAB, etc.). If the open
|
|
// succeeds, we should show the contents of the file.
|
|
//-------------------------------------------------------------------------
|
|
|
|
HRESULT CMSInfo::OpenMSInfoFile(LPCTSTR szFilename, int nFileExtension)
|
|
{
|
|
if (m_pFileData != NULL && m_strFileName.Right(4).CompareNoCase(_T(".xml")) == 0)
|
|
CloseFile();
|
|
|
|
HRESULT hr = S_OK;
|
|
CDataSource * pOldOpenFile = m_pFileData;
|
|
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
/* v-stlowe 3/04/2001...we don't want to automatically switch from history
|
|
in the event we're opening XML.
|
|
if (m_history.IsWindowVisible())
|
|
DispatchCommand(ID_VIEW_CURRENT);*/
|
|
|
|
// Open the file.
|
|
|
|
LPCTSTR szExtension = szFilename + nFileExtension;
|
|
|
|
if (_tcsicmp(szExtension, _T("NFO")) == 0)
|
|
{
|
|
// If this is a version 4 NFO, check to see if that's enabled. Bug 582973.
|
|
|
|
if (IsVersion4File(szFilename))
|
|
{
|
|
HKEY hkey;
|
|
BOOL fDisabled = TRUE;
|
|
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Shared Tools\\MSInfo"), 0, KEY_READ, &hkey))
|
|
{
|
|
DWORD dwType = REG_DWORD, dwValue, dwSize = sizeof(DWORD);
|
|
if (ERROR_SUCCESS == RegQueryValueEx(hkey, _T("AllowVersion4NFO"), NULL, &dwType, (LPBYTE)&dwValue, &dwSize))
|
|
fDisabled = (dwValue == 0);
|
|
RegCloseKey(hkey);
|
|
}
|
|
|
|
if (fDisabled)
|
|
{
|
|
MSInfoMessageBox(IDS_VER4NFODISABLED);
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
// First, try opening it as a 4.x file.
|
|
|
|
CNFO4DataSource* pMSI4Source = new CNFO4DataSource();
|
|
hr = pMSI4Source->Create(szFilename);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pFileData = pMSI4Source;
|
|
}
|
|
else
|
|
{
|
|
delete pMSI4Source;
|
|
|
|
if (STG_E_ACCESSDENIED == hr || STG_E_SHAREVIOLATION == hr || STG_E_LOCKVIOLATION == hr)
|
|
{
|
|
MSInfoMessageBox(IDS_OLDNFOSHARINGVIOLATION);
|
|
return E_FAIL;
|
|
|
|
}
|
|
}
|
|
|
|
// If that failed, then try opening it as a 5.0/6.0 file.
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
HANDLE h = ::CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (INVALID_HANDLE_VALUE == h)
|
|
{
|
|
MSInfoMessageBox(IDS_BADNFOFILE);
|
|
return E_FAIL;
|
|
}
|
|
CNFO6DataSource * p60Source = new CNFO6DataSource;
|
|
if (p60Source)
|
|
{
|
|
hr = p60Source->Create(h, szFilename);
|
|
if (FAILED(hr))
|
|
{
|
|
delete p60Source;
|
|
//MSInfoMessageBox(IDS_BADNFOFILE);
|
|
}
|
|
else
|
|
m_pFileData = p60Source;
|
|
}
|
|
else
|
|
hr = E_FAIL; // TBD no memory
|
|
::CloseHandle(h);
|
|
}
|
|
|
|
//Try 7.0
|
|
if (FAILED(hr))
|
|
{
|
|
CNFO7DataSource * p70Source = new CNFO7DataSource;
|
|
if (!p70Source)
|
|
hr = E_FAIL;
|
|
else
|
|
{
|
|
hr = p70Source->Create(szFilename);//blocks while parsing
|
|
if (FAILED(hr))
|
|
{
|
|
delete p70Source;
|
|
MSInfoMessageBox(IDS_BADNFOFILE);
|
|
}
|
|
else
|
|
m_pFileData = p70Source;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (_tcsicmp(szExtension, _T("CAB")) == 0)
|
|
{
|
|
CString strDest;
|
|
|
|
GetCABExplodeDir(strDest, TRUE, _T(""));
|
|
if (!strDest.IsEmpty())
|
|
{
|
|
if (OpenCABFile(szFilename, strDest))
|
|
{
|
|
LoadGlobalToolsetWithOpenCAB(m_mapIDToTool, strDest);
|
|
UpdateToolsMenu();
|
|
CString strFileInCAB;
|
|
//first, look for xml files (the incident file specified in the registry, and (possibly) dataspec.xml
|
|
|
|
//Get default incident file name from registry (create it if it's not there)
|
|
CString strIncidentFileName;
|
|
HKEY hkey;
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Shared Tools\\MSInfo"), 0, KEY_ALL_ACCESS, &hkey))
|
|
{
|
|
TCHAR szBuffer[MAX_PATH];
|
|
DWORD dwType, dwSize = MAX_PATH * sizeof(TCHAR);
|
|
long lErr = RegQueryValueEx(hkey, _T("incidentfilename"), NULL, &dwType, (LPBYTE)szBuffer, &dwSize);
|
|
if (ERROR_SUCCESS == lErr)
|
|
{
|
|
if (dwType == REG_SZ)
|
|
{
|
|
|
|
strIncidentFileName = szBuffer;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0 && "invalid incidentfilename reg key");
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
//check lErr to make sure it's appropriate error for value not existing
|
|
else if (2 == lErr)
|
|
{
|
|
//create registry key.
|
|
CString strDefaultValue = _T("Incident.xml");
|
|
lErr = RegSetValueEx(hkey,_T("incidentfilename"),NULL,REG_SZ,(BYTE*) strDefaultValue.GetBuffer(strDefaultValue.GetLength()),strDefaultValue.GetLength() * sizeof(TCHAR));
|
|
strIncidentFileName = strDefaultValue;
|
|
}
|
|
|
|
}
|
|
if (IsIncidentXMLFilePresent(strDest,strIncidentFileName))
|
|
{
|
|
strFileInCAB = strDest + "\\";
|
|
strFileInCAB += strIncidentFileName;
|
|
OpenMSInfoFile(strFileInCAB,strFileInCAB.Find(_T(".xml")) +1);
|
|
return S_OK;
|
|
}
|
|
//if there are no xml incident files
|
|
|
|
FindFileToOpen(strDest, strFileInCAB);
|
|
|
|
if (!strFileInCAB.IsEmpty())
|
|
{
|
|
int iExtension = strFileInCAB.GetLength() - 1;
|
|
while (iExtension && strFileInCAB[iExtension] != _T('.'))
|
|
iExtension--;
|
|
if (iExtension)
|
|
return OpenMSInfoFile(strFileInCAB, iExtension + 1 /* skip the dot */);
|
|
else
|
|
{
|
|
ASSERT(0 && "couldn't find dot in file name");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TBD - do something about the error.
|
|
ASSERT(0 && "could get not CAB destination directory");
|
|
}
|
|
MSInfoMessageBox(IDS_BADCABFILE);
|
|
return E_FAIL;
|
|
}
|
|
else if (_tcsicmp(szExtension, _T("XML")) == 0)
|
|
{
|
|
/* v-stlowe 3/04/2001
|
|
CXMLDataSource* pSSDataSource = new CXMLDataSource();
|
|
|
|
hr = pSSDataSource->Create(szFilename,(CMSInfoLiveCategory *) this->m_pLiveData->GetRootCategory(),m_hWnd);
|
|
CXMLSnapshotCategory* pRootXML = (CXMLSnapshotCategory*) pSSDataSource->GetRootCategory();
|
|
pRootXML->AppendFilenameToCaption(szFilename);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_pFileData = pSSDataSource;
|
|
}
|
|
else
|
|
{
|
|
delete pSSDataSource;
|
|
}*/
|
|
try
|
|
{
|
|
hr = ((CLiveDataSource *)m_pLiveData)->LoadXMLDoc(szFilename);
|
|
m_pFileData = m_pLiveData;
|
|
this->m_strFileName = szFilename;
|
|
//trigger refresh
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
if (pCategory)
|
|
ChangeHistoryView(((CMSInfoHistoryCategory*) pCategory)->m_iDeltaIndex);
|
|
if (FAILED(hr))//v-stlowe 3/9/2001 || !varBSuccess)
|
|
{
|
|
ASSERT(0 && "unable to load xml document");
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
catch(...)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
DispatchCommand(ID_VIEW_HISTORY);
|
|
|
|
}
|
|
else
|
|
{
|
|
// Report that we can't open this kind of file.
|
|
|
|
MSInfoMessageBox(IDS_UNKNOWNFILETYPE);
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// It succeeded, so we should show the new data and update the menu
|
|
// for the new state.
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (pOldOpenFile && pOldOpenFile != m_pFileData)
|
|
delete pOldOpenFile;
|
|
|
|
SelectDataSource(m_pFileData);
|
|
}
|
|
else
|
|
; // report the error
|
|
|
|
return hr;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Export to the specified file. This will be either a TXT or an XML file.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::ExportFile(LPCTSTR szFilename, int nFileExtension)
|
|
{
|
|
ASSERT(m_pCurrData);
|
|
|
|
if (m_pCurrData)
|
|
{
|
|
// If there is a selected category, export that node only (bug 185305).
|
|
|
|
CMSInfoCategory * pCategory = (m_pCategory) ? m_pCategory : m_pCurrData->GetRootCategory();
|
|
if (pCategory)
|
|
{
|
|
if (pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
if (m_history.IsWindowVisible() == TRUE)
|
|
{
|
|
((CMSInfoHistoryCategory*)pCategory)->Refresh((CLiveDataSource*)m_pCurrData,TRUE);
|
|
}
|
|
else
|
|
{
|
|
CMSInfoLiveCategory * pLiveCategory = (CMSInfoLiveCategory *) pCategory;
|
|
|
|
if (m_fNoUI)
|
|
pLiveCategory->RefreshSynchronous((CLiveDataSource *) m_pCurrData, TRUE);
|
|
else
|
|
RefreshData((CLiveDataSource *)m_pCurrData, pLiveCategory);
|
|
}
|
|
}
|
|
else if (pCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
((CMSInfo4Category *) pCategory)->RefreshAllForPrint(m_hWnd,this->GetOCXRect());
|
|
}
|
|
else if (pCategory->GetDataSourceType() == XML_SNAPSHOT)
|
|
{
|
|
((CXMLSnapshotCategory *) pCategory)->Refresh((CXMLDataSource *)m_pCurrData,TRUE);
|
|
}
|
|
|
|
/*HANDLE hFile = ::CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
pCategory->SaveAsText(hFile, TRUE);
|
|
::CloseHandle(hFile);
|
|
}
|
|
else
|
|
{
|
|
// TBD - handle the error
|
|
}*/
|
|
//a-stephl: Fixing OSR4.1 bug #133823, not displaying message when saving to write-protected diskette
|
|
try
|
|
{
|
|
HANDLE hFile = ::CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;
|
|
LPTSTR lpMachineName = new TCHAR[dwSize];
|
|
GetMachineName(lpMachineName, &dwSize);
|
|
|
|
/*a-kjaw To implement save as XML
|
|
if( _tcsicmp(szFilename + nFileExtension , _T("XML")) == 0)
|
|
{
|
|
pCategory->SaveAsXml(hFile, TRUE);
|
|
}
|
|
//a-kjaw */
|
|
// else
|
|
// {
|
|
pCategory->SaveAsText(hFile, TRUE, lpMachineName);
|
|
// }
|
|
|
|
delete [] lpMachineName;
|
|
::CloseHandle(hFile);
|
|
}
|
|
else
|
|
{
|
|
DWORD dwError = ::GetLastError();
|
|
|
|
LPVOID lpMsgBuf;
|
|
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dwError,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL);
|
|
|
|
// TBD Process any inserts in lpMsgBuf.
|
|
|
|
CString strCaption;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
::MessageBox(m_hWnd, (LPCTSTR) lpMsgBuf, strCaption, MB_ICONEXCLAMATION | MB_OK);
|
|
::LocalFree(lpMsgBuf);
|
|
}
|
|
}
|
|
catch (CFileException * pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (CException * pException)
|
|
{
|
|
pException->ReportError();
|
|
pException->Delete();
|
|
}
|
|
catch (...)
|
|
{
|
|
DWORD dwError = ::GetLastError();
|
|
|
|
LPVOID lpMsgBuf;
|
|
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dwError,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
|
(LPTSTR) &lpMsgBuf,
|
|
0,
|
|
NULL);
|
|
|
|
// TBD Process any inserts in lpMsgBuf.
|
|
|
|
CString strCaption;
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
strCaption.LoadString(IDS_SYSTEMINFO);
|
|
::MessageBox(m_hWnd, (LPCTSTR) lpMsgBuf, strCaption, MB_ICONEXCLAMATION | MB_OK);
|
|
::LocalFree(lpMsgBuf);
|
|
}
|
|
}
|
|
//end a-stephl: Fixing OSR4.1 bug #133823, not displaying message when saving to write-protected diskette
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Close the currently open file (if there is one). Displays the current
|
|
// system information.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::CloseFile()
|
|
{
|
|
ASSERT(m_pFileData);
|
|
//v-stlowe 3/12/2001
|
|
|
|
SelectDataSource(m_pLiveData);
|
|
if (m_strFileName.Right(4).CompareNoCase(_T(".xml")) == 0)
|
|
{
|
|
((CLiveDataSource *)m_pLiveData)->RevertToLiveXML();
|
|
}
|
|
if (m_pFileData)
|
|
{
|
|
//v-stlowe: so we can use livedata as filedata when opening history xml
|
|
if (m_pFileData != this->m_pLiveData)
|
|
{
|
|
delete m_pFileData;
|
|
}
|
|
m_pFileData = NULL;
|
|
}
|
|
if (!m_history.IsWindowVisible())
|
|
{
|
|
DispatchCommand(ID_VIEW_CURRENT);
|
|
}
|
|
else
|
|
{
|
|
DispatchCommand(ID_VIEW_HISTORY);
|
|
}
|
|
SetMenuItems();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Enable or disable menu items based on the current state.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SetMenuItems()
|
|
{
|
|
if (NULL == m_hmenu || NULL == m_hwndParent)
|
|
return;
|
|
|
|
// This struct will be used a bunch in this function to set menu item states.
|
|
|
|
MENUITEMINFO mii;
|
|
mii.cbSize = sizeof(MENUITEMINFO);
|
|
|
|
// The type of data being displayed will also be used frequently.
|
|
|
|
DataSourceType datatype = LIVE_DATA;
|
|
if (m_pCurrData)
|
|
{
|
|
CMSInfoCategory * pCategory = m_pCurrData->GetRootCategory();
|
|
if (pCategory)
|
|
datatype = pCategory->GetDataSourceType();
|
|
}
|
|
|
|
// Enable or disable items in the File menu.
|
|
|
|
HMENU hmenuFile = ::GetSubMenu(m_hmenu, 0);
|
|
if (hmenuFile)
|
|
{
|
|
mii.fMask = MIIM_STATE;
|
|
mii.fState = (m_pFileData == m_pCurrData) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuFile, ID_FILE_CLOSE, FALSE, &mii);
|
|
|
|
mii.fState = MFS_ENABLED; // Was: (m_pFileData != m_pCurrData) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuFile, ID_FILE_OPENNFO, FALSE, &mii);
|
|
|
|
mii.fState = (datatype == LIVE_DATA || datatype == XML_SNAPSHOT) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuFile, ID_FILE_SAVENFO, FALSE, &mii);
|
|
|
|
//mii.fState = MFS_ENABLED;
|
|
mii.fState = (datatype != NFO_410) ? MFS_ENABLED : MFS_GRAYED;;
|
|
::SetMenuItemInfo(hmenuFile, ID_FILE_EXPORT, FALSE, &mii);
|
|
|
|
mii.fState = MFS_ENABLED;
|
|
::SetMenuItemInfo(hmenuFile, ID_FILE_PRINT, FALSE, &mii);
|
|
|
|
if (NULL == m_hwndParent)
|
|
{
|
|
// Remove the last two items (the exit command and the divider).
|
|
|
|
int nItems = ::GetMenuItemCount(hmenuFile);
|
|
if (ID_FILE_EXIT == ::GetMenuItemID(hmenuFile, nItems - 1))
|
|
{
|
|
::RemoveMenu(hmenuFile, nItems - 1, MF_BYPOSITION);
|
|
::RemoveMenu(hmenuFile, nItems - 2, MF_BYPOSITION);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable or disable items in the Edit menu.
|
|
|
|
HMENU hmenuEdit = ::GetSubMenu(m_hmenu, 1);
|
|
if (hmenuEdit)
|
|
{
|
|
mii.fMask = MIIM_STATE;
|
|
|
|
if (datatype == NFO_410 || ListView_GetNextItem(m_list.m_hWnd, -1, LVNI_SELECTED) != -1)
|
|
mii.fState = MFS_ENABLED;
|
|
else
|
|
mii.fState = MFS_GRAYED;
|
|
|
|
// Disable copy if the list view is not visible.
|
|
|
|
if (!m_list.IsWindowVisible())
|
|
mii.fState = MFS_GRAYED;
|
|
|
|
// If the find control has focus, enable copy based on that control.
|
|
|
|
if (GetFocus() == m_wndFindWhat.m_hWnd && m_wndFindWhat.IsWindowVisible() && m_wndFindWhat.IsWindowEnabled())
|
|
mii.fState = MFS_ENABLED;
|
|
|
|
::SetMenuItemInfo(hmenuEdit, ID_EDIT_COPY, FALSE, &mii);
|
|
|
|
mii.fState = (m_list.IsWindowVisible()) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuEdit, ID_EDIT_SELECTALL, FALSE, &mii);
|
|
|
|
mii.fState = (datatype != NFO_410) ? MFS_ENABLED : MFS_GRAYED;
|
|
mii.fState |= ((!m_fFindVisible) ? MFS_CHECKED : MFS_UNCHECKED);
|
|
::SetMenuItemInfo(hmenuEdit, ID_EDIT_FIND, FALSE, &mii);
|
|
}
|
|
|
|
// Enable or disable items in the View menu.
|
|
|
|
HMENU hmenuView = ::GetSubMenu(m_hmenu, 2);
|
|
if (hmenuView)
|
|
{
|
|
mii.fMask = MIIM_STATE;
|
|
mii.fState = (datatype == LIVE_DATA && !m_history.IsWindowVisible()) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_REFRESH, FALSE, &mii);
|
|
|
|
mii.fState = MFS_ENABLED | ((!m_fAdvanced) ? MFS_CHECKED : MFS_UNCHECKED);
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_BASIC, FALSE, &mii);
|
|
|
|
mii.fState = MFS_ENABLED | ((m_fAdvanced) ? MFS_CHECKED : MFS_UNCHECKED);
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_ADVANCED, FALSE, &mii);
|
|
|
|
// Set the menu item for the current system view or snapshot, depending on whether
|
|
// or not there is an XML file open.
|
|
|
|
BOOL fEnableHistoryLive = FALSE;
|
|
if (datatype == LIVE_DATA && m_fHistoryAvailable && m_strMachine.IsEmpty())
|
|
fEnableHistoryLive = TRUE;
|
|
|
|
BOOL fEnableHistoryXML = FALSE;
|
|
if (m_pFileData)
|
|
{
|
|
CMSInfoCategory * pCategory = m_pFileData->GetRootCategory();
|
|
if (pCategory && (pCategory->GetDataSourceType() == XML_SNAPSHOT || pCategory == &catHistorySystemSummary))
|
|
fEnableHistoryXML = TRUE;
|
|
}
|
|
|
|
BOOL fShowingHistory = FALSE;
|
|
if (m_pCurrData)
|
|
{
|
|
CMSInfoCategory * pCategory = m_pCurrData->GetRootCategory();
|
|
if (pCategory == &catHistorySystemSummary)
|
|
fShowingHistory = TRUE;
|
|
}
|
|
|
|
// Whether or not you can remote depends on if you are showing live data.
|
|
|
|
mii.fState = (datatype == LIVE_DATA && !fEnableHistoryXML) ? MFS_ENABLED : MFS_GRAYED;
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_REMOTE_COMPUTER, FALSE, &mii);
|
|
|
|
// Enabling the menu items to switch between current (or snapshot) and history
|
|
// are based on whether history is available.
|
|
|
|
mii.fState = (fEnableHistoryLive || fEnableHistoryXML) ? MFS_ENABLED : MFS_GRAYED;
|
|
mii.fState |= (!m_history.IsWindowVisible()) ? MFS_CHECKED : MFS_UNCHECKED;
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_CURRENT, FALSE, &mii);
|
|
|
|
mii.fState = (fEnableHistoryLive || fEnableHistoryXML) ? MFS_ENABLED : MFS_GRAYED;
|
|
mii.fState |= (m_history.IsWindowVisible()) ? MFS_CHECKED : MFS_UNCHECKED;
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_HISTORY, FALSE, &mii);
|
|
|
|
// Set the menu item text (for system snapshot/current system information) based on
|
|
// whether or not we have an XML file open.
|
|
|
|
UINT uiMenuCaption = IDS_VIEWCURRENTSYSTEMINFO;
|
|
if (m_pFileData)
|
|
{
|
|
CMSInfoCategory * pCategory = m_pCurrData->GetRootCategory();
|
|
// v-stlowe 6/26/2001...pCategory && (pCategory->GetDataSourceType() == XML_SNAPSHOT no longer possible... if (pCategory && (pCategory->GetDataSourceType() == XML_SNAPSHOT || pCategory == &catHistorySystemSummary))
|
|
if (pCategory && (pCategory == &catHistorySystemSummary))
|
|
{
|
|
//v-stlowe 6/26/2001: we need to remove "snapshot" uiMenuCaption = IDS_VIEWSYSTEMSNAPSHOT;
|
|
uiMenuCaption = IDS_VIEWCURRENTSYSTEMINFO;
|
|
}
|
|
}
|
|
|
|
CString strMenuItem;
|
|
strMenuItem.LoadString(uiMenuCaption);
|
|
|
|
MENUITEMINFO miiName;
|
|
miiName.cbSize = sizeof(MENUITEMINFO);
|
|
miiName.fMask = MIIM_TYPE;
|
|
miiName.fType = MFT_STRING;
|
|
miiName.dwTypeData = (LPTSTR)(LPCTSTR)strMenuItem;
|
|
::SetMenuItemInfo(hmenuView, ID_VIEW_CURRENT, FALSE, &miiName);
|
|
}
|
|
|
|
// Enable or disable items in the Help menu.
|
|
|
|
HMENU hmenuHelp = ::GetSubMenu(m_hmenu, 4);
|
|
if (hmenuHelp)
|
|
{
|
|
mii.fMask = MIIM_STATE;
|
|
mii.fState = MFS_ENABLED;
|
|
::SetMenuItemInfo(hmenuHelp, ID_HELP_ABOUT, FALSE, &mii);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Set a message in the right hand pane (hiding the list view).
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::SetMessage(const CString & strTitle, const CString & strMessage, BOOL fRedraw)
|
|
{
|
|
m_strMessTitle = strTitle;
|
|
m_strMessText = strMessage;
|
|
|
|
if (strTitle.IsEmpty() && strMessage.IsEmpty())
|
|
{
|
|
m_list.ShowWindow(SW_SHOW);
|
|
return;
|
|
}
|
|
|
|
m_list.ShowWindow(SW_HIDE);
|
|
|
|
if (fRedraw)
|
|
{
|
|
RECT rectList;
|
|
m_list.GetWindowRect(&rectList);
|
|
ScreenToClient(&rectList);
|
|
InvalidateRect(&rectList, TRUE);
|
|
UpdateWindow();
|
|
}
|
|
}
|
|
|
|
void CMSInfo::SetMessage(UINT uiTitle, UINT uiMessage, BOOL fRedraw)
|
|
{
|
|
CString strTitle(_T(""));
|
|
CString strMessage(_T(""));
|
|
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
|
|
if (uiTitle)
|
|
strTitle.LoadString(uiTitle);
|
|
|
|
if (uiMessage)
|
|
strMessage.LoadString(uiMessage);
|
|
|
|
SetMessage(strTitle, strMessage, fRedraw);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// This function is used to sort the list by a specified column.
|
|
//---------------------------------------------------------------------------
|
|
|
|
int CALLBACK ListSortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
|
{
|
|
int iReturn = 0;
|
|
CMSInfoCategory * pCategory = (CMSInfoCategory *) lParamSort;
|
|
|
|
if (pCategory)
|
|
{
|
|
CString * pstrFirst;
|
|
CString * pstrSecond;
|
|
DWORD dwFirst = 0, dwSecond = 0;
|
|
|
|
pCategory->GetData((int)lParam1, pCategory->m_iSortColumn, &pstrFirst, &dwFirst);
|
|
pCategory->GetData((int)lParam2, pCategory->m_iSortColumn, &pstrSecond, &dwSecond);
|
|
|
|
//a-kjaw . To fix bug "Sort order style for an nfo file differs from that of live data."
|
|
if(pCategory->GetDataSourceType() == NFO_500/*|| pCategory->GetDataSourceType() == NFO_410 */) //BugBug
|
|
if(pstrFirst->Left(3) == _T("IRQ"))//Very weird fix. Need Loc?
|
|
{
|
|
LPTSTR strIrq = pstrFirst->GetBuffer(pstrFirst->GetLength() + 1);
|
|
dwFirst = _ttoi(strIrq + 4 );
|
|
pstrFirst->ReleaseBuffer();
|
|
|
|
strIrq = pstrSecond->GetBuffer(pstrSecond->GetLength() + 1);
|
|
dwSecond = _ttoi(strIrq + 4 );
|
|
pstrSecond->ReleaseBuffer();
|
|
|
|
}
|
|
//a-kjaw
|
|
|
|
if (pCategory->m_fSortLexical)
|
|
iReturn = pstrFirst->Collate(*pstrSecond);
|
|
else
|
|
iReturn = (dwFirst < dwSecond) ? -1 : (dwFirst == dwSecond) ? 0 : 1;
|
|
|
|
if (!pCategory->m_fSortAscending)
|
|
iReturn *= -1;
|
|
}
|
|
|
|
return iReturn;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Copy selected text from the list view into the clipboard.
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CMSInfo::EditCopy()
|
|
{
|
|
if (GetFocus() == m_wndFindWhat.m_hWnd && m_wndFindWhat.IsWindowVisible() && m_wndFindWhat.IsWindowEnabled())
|
|
{
|
|
m_wndFindWhat.SendMessage(WM_COPY);
|
|
return;
|
|
}
|
|
|
|
CString strClipboardText(_T(""));
|
|
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
if (pCategory == NULL)
|
|
return;
|
|
|
|
if (pCategory && pCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
CMSInfo4Category * pCategory4 = (CMSInfo4Category *) pCategory;
|
|
CMSIControl * p4Ctrl = NULL;
|
|
|
|
if (CMSInfo4Category::s_pNfo4DataSource->GetControlFromCLSID(pCategory4->m_strCLSID, p4Ctrl) && p4Ctrl)
|
|
p4Ctrl->MSInfoCopy();
|
|
|
|
return;
|
|
}
|
|
|
|
int iRowCount, iColCount;
|
|
pCategory->GetCategoryDimensions(&iColCount, &iRowCount);
|
|
|
|
// Build the string to put in the clipboard by finding all of the
|
|
// selected lines in the list view.
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
|
|
CString * pstrCell;
|
|
int iSelected = ListView_GetNextItem(m_list.m_hWnd, -1, LVNI_SELECTED);
|
|
while (iSelected != -1)
|
|
{
|
|
lvi.iItem = iSelected;
|
|
if (ListView_GetItem(m_list.m_hWnd, &lvi))
|
|
{
|
|
ASSERT(lvi.lParam < iRowCount);
|
|
if (lvi.lParam < iRowCount)
|
|
for (int iCol = 0; iCol < iColCount; iCol++)
|
|
if (SUCCEEDED(pCategory->GetData((int)lvi.lParam, iCol, &pstrCell, NULL)))
|
|
{
|
|
if (iCol)
|
|
strClipboardText += _T("\t");
|
|
strClipboardText += *pstrCell;
|
|
}
|
|
strClipboardText += _T("\r\n");
|
|
}
|
|
|
|
iSelected = ListView_GetNextItem(m_list.m_hWnd, iSelected, LVNI_SELECTED);
|
|
}
|
|
|
|
// Put the string in the clipboard.
|
|
|
|
if (OpenClipboard())
|
|
{
|
|
if (EmptyClipboard())
|
|
{
|
|
DWORD dwSize = (strClipboardText.GetLength() + 1) * sizeof(TCHAR);
|
|
HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, dwSize);
|
|
|
|
if (hMem)
|
|
{
|
|
LPVOID lpvoid = ::GlobalLock(hMem);
|
|
if (lpvoid)
|
|
{
|
|
memcpy(lpvoid, (LPCTSTR) strClipboardText, dwSize);
|
|
::GlobalUnlock(hMem);
|
|
SetClipboardData(CF_UNICODETEXT, hMem);
|
|
}
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Select all of the text in the list view.
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CMSInfo::EditSelectAll()
|
|
{
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
|
|
if (pCategory && pCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
CMSInfo4Category * pCategory4 = (CMSInfo4Category *) pCategory;
|
|
CMSIControl * p4Ctrl = NULL;
|
|
|
|
if (CMSInfo4Category::s_pNfo4DataSource->GetControlFromCLSID(pCategory4->m_strCLSID, p4Ctrl) && p4Ctrl)
|
|
p4Ctrl->MSInfoSelectAll();
|
|
}
|
|
else
|
|
{
|
|
int iCount = ListView_GetItemCount(m_list.m_hWnd);
|
|
for (int i = 0; i < iCount; i++)
|
|
ListView_SetItemState(m_list.m_hWnd, i, LVIS_SELECTED, LVIS_SELECTED);
|
|
}
|
|
}
|
|
|
|
void CMSInfo::GetMachineName(LPTSTR lpBuffer, LPDWORD lpnSize)
|
|
{
|
|
if (_tcslen(m_strMachine) == 0)
|
|
GetComputerName(lpBuffer, lpnSize);
|
|
else
|
|
_tcsncpy(lpBuffer, m_strMachine, *lpnSize);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Print the currently displayed information.
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CMSInfo::DoPrint(BOOL fNoUI)
|
|
{
|
|
if (m_pCurrData == NULL)
|
|
return;
|
|
|
|
CMSInfoCategory * pRootCategory = m_pCurrData->GetRootCategory();
|
|
CMSInfoCategory * pSelectedCategory = GetCurrentCategory();
|
|
|
|
if (pRootCategory == NULL)
|
|
return;
|
|
|
|
DWORD dwFlags = PD_CURRENTPAGE | PD_NOPAGENUMS | PD_RETURNDC | PD_HIDEPRINTTOFILE | PD_USEDEVMODECOPIESANDCOLLATE;
|
|
if (pSelectedCategory == NULL)
|
|
dwFlags |= PD_NOCURRENTPAGE | PD_NOSELECTION | PD_ALLPAGES;
|
|
|
|
PRINTDLGEX pd;
|
|
::ZeroMemory(&pd, sizeof(PRINTDLGEX));
|
|
pd.Flags = dwFlags;
|
|
pd.lStructSize = sizeof(PRINTDLGEX);
|
|
pd.hwndOwner = this->m_hWnd;
|
|
pd.ExclusionFlags = PD_EXCL_COPIESANDCOLLATE;
|
|
pd.nStartPage = START_PAGE_GENERAL;
|
|
|
|
if (fNoUI)
|
|
pd.Flags |= PD_RETURNDEFAULT;
|
|
|
|
if (SUCCEEDED(::PrintDlgEx(&pd)) && pd.dwResultAction == PD_RESULT_PRINT)
|
|
{
|
|
BOOL fPrintCategory = ((pd.Flags & PD_SELECTION) != 0) || ((pd.Flags & PD_CURRENTPAGE) != 0);
|
|
BOOL fPrintRecursive = ((pd.Flags & PD_CURRENTPAGE) == 0);
|
|
|
|
CMSInfoCategory * pPrintCategory = (fPrintCategory) ? pSelectedCategory : pRootCategory;
|
|
if (pPrintCategory)
|
|
{
|
|
if (pPrintCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
RefreshData((CLiveDataSource *)m_pCurrData, (CMSInfoLiveCategory *)pPrintCategory);
|
|
}
|
|
else if (pPrintCategory->GetDataSourceType() == NFO_410)
|
|
{
|
|
((CMSInfo4Category *) pPrintCategory)->RefreshAllForPrint(m_hWnd,this->GetOCXRect());
|
|
}
|
|
else if (pPrintCategory->GetDataSourceType() == XML_SNAPSHOT)
|
|
{
|
|
((CXMLSnapshotCategory*) pPrintCategory)->Refresh((CXMLDataSource*) m_pCurrData, TRUE);
|
|
}
|
|
|
|
DWORD dwSize = MAX_COMPUTERNAME_LENGTH + 1;
|
|
LPTSTR lpMachineName = new TCHAR[dwSize];
|
|
GetMachineName(lpMachineName, &dwSize);
|
|
pPrintCategory->Print(pd.hDC, fPrintRecursive, -1, -1, lpMachineName); // -1's to include all pages
|
|
delete [] lpMachineName;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Update the tools menu to match the contents of the tools map.
|
|
//---------------------------------------------------------------------------
|
|
|
|
void CMSInfo::UpdateToolsMenu()
|
|
{
|
|
if (NULL == m_hmenu)
|
|
return;
|
|
|
|
HMENU hmenuTool = ::GetSubMenu(m_hmenu, 3);
|
|
if (hmenuTool)
|
|
{
|
|
// Remove all the current tools in the menu.
|
|
|
|
while (DeleteMenu(hmenuTool, 0, MF_BYPOSITION));
|
|
|
|
// Add the tools from the map. This will add the top level tools.
|
|
|
|
WORD wCommand;
|
|
CMSInfoTool * pTool;
|
|
|
|
for (POSITION pos = m_mapIDToTool.GetStartPosition(); pos != NULL; )
|
|
{
|
|
m_mapIDToTool.GetNextAssoc(pos, wCommand, (void * &) pTool);
|
|
if (pTool && pTool->GetParentID() == 0)
|
|
{
|
|
if (!pTool->HasSubitems())
|
|
InsertMenu(hmenuTool, 0xFFFFFFFF, MF_BYPOSITION | MF_STRING, (UINT) pTool->GetID(), pTool->GetName());
|
|
else
|
|
{
|
|
HMENU hmenuNew = CreatePopupMenu();
|
|
InsertMenu(hmenuTool, 0xFFFFFFFF, MF_BYPOSITION | MF_STRING | MF_POPUP, (UINT_PTR) hmenuNew, pTool->GetName());
|
|
pTool->SetHMENU(hmenuNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now add the second level tools (the subitems).
|
|
|
|
for (pos = m_mapIDToTool.GetStartPosition(); pos != NULL; )
|
|
{
|
|
m_mapIDToTool.GetNextAssoc(pos, wCommand, (void * &) pTool);
|
|
if (pTool && pTool->GetParentID())
|
|
{
|
|
CMSInfoTool * pParentTool;
|
|
|
|
if (m_mapIDToTool.Lookup((WORD) pTool->GetParentID(), (void * &) pParentTool))
|
|
InsertMenu(pParentTool->GetHMENU(), 0xFFFFFFFF, MF_BYPOSITION | MF_STRING, (UINT) pTool->GetID(), pTool->GetName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Gets the right pane rect in which to display a MSInfo 4.x OCX.
|
|
//---------------------------------------------------------------------------
|
|
|
|
CRect CMSInfo::GetOCXRect()
|
|
{
|
|
CRect rectList;
|
|
|
|
m_list.GetWindowRect(&rectList);
|
|
ScreenToClient(&rectList);
|
|
rectList.DeflateRect(1, 1, 2, 2);
|
|
|
|
return rectList;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Find Functionality
|
|
//=============================================================================
|
|
|
|
//-------------------------------------------------------------------------
|
|
// CancelFind does what is says. It also waits until the find is done
|
|
// before returning.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::CancelFind()
|
|
{
|
|
if (m_fInFind)
|
|
{
|
|
m_fCancelFind = TRUE;
|
|
m_fFindNext = FALSE;
|
|
GotoDlgCtrl(m_wndStopFind.m_hWnd);
|
|
UpdateFindControls();
|
|
|
|
if (m_pcatFind && m_pcatFind->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
CLiveDataSource * pLiveDataSource = (CLiveDataSource *) m_pCurrData;
|
|
HCURSOR hc = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
|
|
pLiveDataSource->WaitForRefresh();
|
|
::SetCursor(hc);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// When the user clicks on Stop Find, it will either cancel the current
|
|
// find operation (if there is one in progress) or hide the find controls
|
|
// (if there is no find in progress).
|
|
//-------------------------------------------------------------------------
|
|
|
|
LRESULT CMSInfo::OnStopFind(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
|
|
{
|
|
if (m_fInFind)
|
|
{
|
|
m_fCancelFind = TRUE;
|
|
m_fFindNext = FALSE;
|
|
GotoDlgCtrl(m_wndStopFind.m_hWnd);
|
|
UpdateFindControls();
|
|
}
|
|
else
|
|
{
|
|
m_fFindNext = FALSE;
|
|
DispatchCommand(ID_EDIT_FIND);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// UpdateFindControls updates the state of the controls (the text and
|
|
// enabling/disabling) based on the settings of the find member vars.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::UpdateFindControls()
|
|
{
|
|
if (!m_fFindVisible)
|
|
return;
|
|
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
|
|
m_wndCancelFind.ShowWindow(m_fInFind ? SW_SHOW : SW_HIDE);
|
|
m_wndStopFind.ShowWindow(m_fInFind ? SW_HIDE : SW_SHOW);
|
|
m_wndFindNext.ShowWindow(m_fFindNext ? SW_SHOW : SW_HIDE);
|
|
m_wndStartFind.ShowWindow(m_fFindNext ? SW_HIDE : SW_SHOW);
|
|
|
|
m_wndStopFind.EnableWindow(!m_fInFind && ((m_fInFind && !m_fCancelFind) || !m_fInFind));
|
|
m_wndCancelFind.EnableWindow(m_fInFind && ((m_fInFind && !m_fCancelFind) || !m_fInFind));
|
|
m_wndStartFind.EnableWindow(!m_fFindNext && (!m_fInFind && !m_strFind.IsEmpty()));
|
|
m_wndFindNext.EnableWindow(m_fFindNext && (!m_fInFind && !m_strFind.IsEmpty()));
|
|
|
|
m_wndFindWhatLabel.EnableWindow(!m_fInFind);
|
|
m_wndFindWhat.EnableWindow(!m_fInFind);
|
|
m_wndSearchSelected.EnableWindow(!m_fInFind);
|
|
m_wndSearchCategories.EnableWindow(!m_fInFind);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// When the user changes the text in the find what edit box, we need to
|
|
// make sure we keep track of the string, and that we are in "find" (rather
|
|
// than "find next") mode.
|
|
//-------------------------------------------------------------------------
|
|
|
|
LRESULT CMSInfo::OnChangeFindWhat(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
|
|
{
|
|
m_fFindNext = FALSE;
|
|
|
|
// Get the find text from the rich edit control (use EM_GETTEXTEX
|
|
// to preserve its Unicode-ness).
|
|
|
|
TCHAR szBuffer[MAX_PATH];
|
|
GETTEXTEX gte;
|
|
|
|
gte.cb = MAX_PATH;
|
|
gte.flags = GT_DEFAULT;
|
|
gte.codepage = 1200; // Unicode
|
|
gte.lpDefaultChar = NULL;
|
|
gte.lpUsedDefChar = NULL;
|
|
m_wndFindWhat.SendMessage(EM_GETTEXTEX, (WPARAM)>e, (LPARAM)szBuffer);
|
|
m_strFind = szBuffer;
|
|
|
|
UpdateFindControls();
|
|
SetMenuItems();
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// When the user clicks on Find, it will either be for a "Find" or a
|
|
// "Find Next".
|
|
//-------------------------------------------------------------------------
|
|
|
|
LRESULT CMSInfo::OnFind(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
|
|
{
|
|
m_fSearchCatNamesOnly = IsDlgButtonChecked(IDC_CHECKSEARCHCATSONLY);
|
|
m_fSearchSelectedCatOnly = IsDlgButtonChecked(IDC_CHECKSEARCHSELECTED);
|
|
|
|
if (!m_fFindNext)
|
|
{
|
|
m_fInFind = TRUE;
|
|
m_fCancelFind = FALSE;
|
|
m_fFindNext = TRUE;
|
|
m_iFindLine = -2;
|
|
|
|
// Based on the user's setting of "Search selected category only", start
|
|
// with either the selected category or the root category.
|
|
|
|
if (m_fSearchSelectedCatOnly)
|
|
m_pcatFind = GetCurrentCategory();
|
|
else
|
|
m_pcatFind = m_pCurrData->GetRootCategory();
|
|
|
|
UpdateFindControls();
|
|
::SetFocus(m_wndCancelFind.m_hWnd);
|
|
}
|
|
else
|
|
{
|
|
if (FindInCurrentCategory())
|
|
return 0;
|
|
|
|
m_fInFind = TRUE;
|
|
m_fCancelFind = FALSE;
|
|
UpdateFindControls();
|
|
::SetFocus(m_wndCancelFind.m_hWnd);
|
|
}
|
|
|
|
// The refresh will post a message that data is ready, so we can search the
|
|
// specified category. If we aren't going to refresh the category, we'll just
|
|
// post the message ourselves.
|
|
|
|
if (m_pcatFind)
|
|
{
|
|
SetMessage(IDS_SEARCHMESSAGE, 0, TRUE);
|
|
if (m_pcatFind->GetDataSourceType() == LIVE_DATA && !m_fSearchCatNamesOnly)
|
|
((CMSInfoLiveCategory *) m_pcatFind)->Refresh((CLiveDataSource *) m_pCurrData, FALSE);
|
|
else
|
|
PostMessage(WM_MSINFODATAREADY, 0, (LPARAM)m_pcatFind);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// This is the function that's called when the data ready message is
|
|
// received by the window. Look for the data in the current find category.
|
|
// If there is a match, it will be shown and the find operation stopped.
|
|
// Otherwise (unless the option's been selected to search only the current
|
|
// category) continue the find operation with the next category.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::FindRefreshComplete()
|
|
{
|
|
if (m_fCancelFind)
|
|
{
|
|
m_fInFind = FALSE;
|
|
m_fFindNext = FALSE;
|
|
GotoDlgCtrl(m_wndStopFind.m_hWnd);
|
|
// SetMessage(0);
|
|
SelectCategory(GetCurrentCategory());
|
|
UpdateFindControls();
|
|
::SetFocus(m_wndStartFind.m_hWnd);
|
|
return;
|
|
}
|
|
|
|
if (FindInCurrentCategory())
|
|
return;
|
|
|
|
// If the user checked "Search selected category only", then we should
|
|
// not look through any additional categories.
|
|
|
|
if (m_fSearchSelectedCatOnly)
|
|
m_pcatFind = NULL;
|
|
else
|
|
{
|
|
m_iFindLine = -2;
|
|
|
|
CMSInfoCategory * pNextCategory;
|
|
pNextCategory = m_pcatFind->GetFirstChild();
|
|
if (pNextCategory == NULL)
|
|
while (m_pcatFind)
|
|
{
|
|
pNextCategory = m_pcatFind->GetNextSibling();
|
|
if (pNextCategory)
|
|
break;
|
|
|
|
m_pcatFind = m_pcatFind->GetParent();
|
|
}
|
|
|
|
m_pcatFind = pNextCategory;
|
|
}
|
|
|
|
// If the category is NULL, there are no more matches. Return the
|
|
// controls to a normal state and notify the user.
|
|
|
|
if (m_pcatFind == NULL)
|
|
{
|
|
m_fInFind = FALSE;
|
|
m_fFindNext = FALSE;
|
|
UpdateFindControls();
|
|
MSInfoMessageBox(IDS_NOMOREMATCHES);
|
|
SelectCategory(GetCurrentCategory());
|
|
GotoDlgCtrl(m_wndStopFind.m_hWnd);
|
|
|
|
return;
|
|
}
|
|
|
|
SetMessage(IDS_SEARCHMESSAGE);
|
|
if (m_pcatFind->GetDataSourceType() == LIVE_DATA && !m_fSearchCatNamesOnly)
|
|
((CMSInfoLiveCategory *) m_pcatFind)->Refresh((CLiveDataSource *) m_pCurrData, FALSE);
|
|
else
|
|
PostMessage(WM_MSINFODATAREADY, 0, (LPARAM)m_pcatFind);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Look for the string in the current category. This function will be
|
|
// called when data is avaible for the category. If there is a match, show
|
|
// it and return TRUE, otherwise return FALSE.
|
|
//
|
|
// m_iFindLine contains the list view row number of the last match. If it
|
|
// is -1, it means we are just starting on this category (since we would
|
|
// start looking at row 0). If it is -2, then we should look for the string
|
|
// in the category name. (Note - this might be all we do, depending on the
|
|
// setting for m_fSearchCatNamesOnly.)
|
|
//-------------------------------------------------------------------------
|
|
|
|
BOOL CMSInfo::FindInCurrentCategory()
|
|
{
|
|
if (m_pcatFind == NULL)
|
|
return FALSE;
|
|
|
|
// The search is case insensitive, so convert our search string to lower case.
|
|
|
|
CString strLookFor(m_strFind);
|
|
strLookFor.TrimLeft(_T("\t\r\n "));
|
|
strLookFor.TrimRight(_T("\t\r\n "));
|
|
strLookFor.MakeLower();
|
|
|
|
// If m_iFindLine is -2, then we should look at the category name for a match.
|
|
|
|
if (m_iFindLine == -2)
|
|
{
|
|
m_iFindLine += 1;
|
|
|
|
CString strCatName;
|
|
m_pcatFind->GetNames(&strCatName, NULL);
|
|
strCatName.MakeLower();
|
|
if (strCatName.Find(strLookFor) != -1)
|
|
{
|
|
// There was a match. Get the HTREEITEM for the category and select it.
|
|
|
|
HTREEITEM hti = m_pcatFind->GetHTREEITEM();
|
|
if (hti)
|
|
{
|
|
m_fInFind = FALSE;
|
|
m_fFindNext = TRUE;
|
|
TreeView_EnsureVisible(m_tree.m_hWnd, hti);
|
|
TreeView_SelectItem(m_tree.m_hWnd, hti);
|
|
SetMessage(0);
|
|
UpdateFindControls();
|
|
GotoDlgCtrl(m_wndFindNext.m_hWnd);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are search category names only, then we stop here (before looking
|
|
// through the data for this category).
|
|
|
|
if (m_fSearchCatNamesOnly)
|
|
return FALSE;
|
|
|
|
// If m_iFindLine is -1, then we need to look in the data for this category
|
|
// to see if there is a match. If there is, then we select the category and
|
|
// start looking through the lines of the list view (we can't use the index
|
|
// we found looking through the data directly, because if the list view is
|
|
// sorted we would be searching out of order).
|
|
|
|
int iRow, iCol, iRowCount, iColCount;
|
|
if (!m_pcatFind->GetCategoryDimensions(&iColCount, &iRowCount))
|
|
return FALSE;
|
|
|
|
if (m_iFindLine == -1)
|
|
{
|
|
CString * pstrCell, strCell;
|
|
BOOL fFound = FALSE;
|
|
|
|
for (iRow = 0; iRow < iRowCount && !fFound; iRow++)
|
|
if (m_fAdvanced || !m_pcatFind->IsRowAdvanced(iRow))
|
|
for (iCol = 0; iCol < iColCount && !fFound; iCol++)
|
|
if (m_fAdvanced || !m_pcatFind->IsColumnAdvanced(iCol))
|
|
if (m_pcatFind->GetData(iRow, iCol, &pstrCell, NULL))
|
|
{
|
|
strCell = *pstrCell;
|
|
strCell.MakeLower();
|
|
if (strCell.Find(strLookFor) != -1)
|
|
fFound = TRUE;
|
|
}
|
|
|
|
if (!fFound)
|
|
return FALSE;
|
|
|
|
// We found data in this category. Select it so it populates the list view.
|
|
|
|
HTREEITEM hti = m_pcatFind->GetHTREEITEM();
|
|
if (hti)
|
|
{
|
|
TreeView_EnsureVisible(m_tree.m_hWnd, hti);
|
|
TreeView_SelectItem(m_tree.m_hWnd, hti);
|
|
SetMessage(0);
|
|
}
|
|
}
|
|
|
|
// If we get here, m_iFindLine will be >= -1, and represents the line in the
|
|
// list view after which we should start searching.
|
|
|
|
m_iFindLine += 1;
|
|
|
|
CString strData;
|
|
int iListRowCount = ListView_GetItemCount(m_list.m_hWnd);
|
|
int iListColCount = 0;
|
|
|
|
// Determine the number of columns in the list view.
|
|
|
|
for (iCol = 0; iCol < iColCount; iCol++)
|
|
if (m_fAdvanced || !m_pcatFind->IsColumnAdvanced(iCol))
|
|
iListColCount += 1;
|
|
|
|
while (m_iFindLine < iListRowCount)
|
|
{
|
|
for (iCol = 0; iCol < iListColCount; iCol++)
|
|
{
|
|
ListView_GetItemText(m_list.m_hWnd, m_iFindLine, iCol, strData.GetBuffer(MAX_PATH), MAX_PATH);
|
|
strData.ReleaseBuffer();
|
|
if (strData.GetLength())
|
|
{
|
|
strData.MakeLower();
|
|
if (strData.Find(strLookFor) != -1)
|
|
{
|
|
// We found a match. The category should already be selected,
|
|
// so all we need to do is select the line (and make sure
|
|
// all the other lines are not selected).
|
|
|
|
for (int iRow = 0; iRow < iListRowCount; iRow++)
|
|
if (iRow == m_iFindLine)
|
|
{
|
|
ListView_EnsureVisible(m_list.m_hWnd, iRow, TRUE);
|
|
ListView_SetItemState(m_list.m_hWnd, iRow, LVIS_SELECTED, LVIS_SELECTED);
|
|
}
|
|
else
|
|
{
|
|
ListView_SetItemState(m_list.m_hWnd, iRow, 0, LVIS_SELECTED);
|
|
}
|
|
|
|
m_fInFind = FALSE;
|
|
m_fFindNext = TRUE;
|
|
SetMessage(0);
|
|
UpdateFindControls();
|
|
GotoDlgCtrl(m_wndFindNext.m_hWnd);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
m_iFindLine += 1;
|
|
}
|
|
|
|
// If we fall through to here, then there were no more matches in the
|
|
// list view. Return FALSE.
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// ShowFindControls is called to show or hide the dialog controls used
|
|
// for find.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::ShowFindControls()
|
|
{
|
|
int iShowCommand = (m_fFindVisible) ? SW_SHOW : SW_HIDE;
|
|
|
|
if (m_fFindVisible)
|
|
PositionFindControls();
|
|
|
|
m_wndFindWhatLabel.ShowWindow(iShowCommand);
|
|
m_wndFindWhat.ShowWindow(iShowCommand);
|
|
m_wndSearchSelected.ShowWindow(iShowCommand);
|
|
m_wndSearchCategories.ShowWindow(iShowCommand);
|
|
m_wndStartFind.ShowWindow(iShowCommand);
|
|
m_wndStopFind.ShowWindow(iShowCommand);
|
|
m_wndFindNext.ShowWindow(iShowCommand);
|
|
m_wndCancelFind.ShowWindow(iShowCommand);
|
|
|
|
if (iShowCommand == SW_HIDE)
|
|
{
|
|
m_wndFindWhatLabel.EnableWindow(FALSE);
|
|
m_wndFindWhat.EnableWindow(FALSE);
|
|
m_wndSearchSelected.EnableWindow(FALSE);
|
|
m_wndSearchCategories.EnableWindow(FALSE);
|
|
m_wndStartFind.EnableWindow(FALSE);
|
|
m_wndStopFind.EnableWindow(FALSE);
|
|
m_wndFindNext.EnableWindow(FALSE);
|
|
m_wndCancelFind.EnableWindow(FALSE);
|
|
}
|
|
|
|
if (!m_fFindVisible)
|
|
return;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Position the find controls on the control surface. This will be called
|
|
// when the find controls are shown, or when the control is resized.
|
|
//-------------------------------------------------------------------------
|
|
|
|
int CMSInfo::PositionFindControls()
|
|
{
|
|
if (!m_fFindVisible)
|
|
return 0;
|
|
|
|
// Get some useful sizes of the various controls we need to move around
|
|
// the window.
|
|
|
|
CRect rectFindWhatLabel, rectFindWhat, rectSearchSelected, rectSearchCategories;
|
|
CRect rectStartFind, rectStopFind, rectClient;
|
|
|
|
GetClientRect(&rectClient);
|
|
m_wndFindWhatLabel.GetWindowRect(&rectFindWhatLabel);
|
|
m_wndFindWhat.GetWindowRect(&rectFindWhat);
|
|
m_wndStartFind.GetWindowRect(&rectStartFind);
|
|
m_wndStopFind.GetWindowRect(&rectStopFind);
|
|
m_wndSearchSelected.GetWindowRect(&rectSearchSelected);
|
|
m_wndSearchCategories.GetWindowRect(&rectSearchCategories);
|
|
|
|
int iSpacer = 5;
|
|
|
|
// The control rect is the space we have to work with for placing the controls.
|
|
|
|
CRect rectControl(rectClient);
|
|
rectControl.DeflateRect(iSpacer, iSpacer);
|
|
|
|
// Determine if we have enough room to lay out the controls
|
|
// horizontally, or if we need to stack them. Horizontally, it looks like:
|
|
//
|
|
// <spacer><Find What label><spacer><Find What edit><spacer><Start Find><spacer><Stop Find><spacer>
|
|
// <spacer><Search Selected check><spacer><Search Cats check><spacer>
|
|
|
|
int cxTopLine = iSpacer * 5 + rectFindWhatLabel.Width() * 2 + rectStartFind.Width() + rectStopFind.Width();
|
|
int cxBottomLine = iSpacer * 3 + rectSearchSelected.Width() + rectSearchCategories.Width();
|
|
BOOL fHorizontal = (cxTopLine <= rectClient.Width() && cxBottomLine <= rectClient.Width());
|
|
|
|
// If it get's wider than a certain size, it becomes less usable. So put a reasonable
|
|
// limit on the width:
|
|
|
|
int cxMaxWidth = iSpacer * 5 + rectFindWhatLabel.Width() + rectSearchSelected.Width() + rectSearchCategories.Width() + rectStartFind.Width() + rectStopFind.Width();
|
|
if (fHorizontal && rectControl.Width() > cxMaxWidth)
|
|
rectControl.DeflateRect((rectControl.Width() - cxMaxWidth) / 2, 0);
|
|
|
|
// Figure the height of the control rectangle.
|
|
|
|
int cyControlRectHeight = rectStartFind.Height() + ((fHorizontal) ? 0 : rectStopFind.Height() + iSpacer);
|
|
int cyLeftSideHeight;
|
|
|
|
if (fHorizontal)
|
|
cyLeftSideHeight = rectFindWhat.Height() + iSpacer + rectSearchSelected.Height();
|
|
else
|
|
cyLeftSideHeight = rectFindWhat.Height() + iSpacer * 2 + rectSearchSelected.Height() * 2;
|
|
|
|
if (cyControlRectHeight < cyLeftSideHeight)
|
|
cyControlRectHeight = cyLeftSideHeight;
|
|
|
|
rectControl.top = rectControl.bottom - cyControlRectHeight;
|
|
|
|
// Position the buttons appropriately.
|
|
|
|
if (fHorizontal)
|
|
{
|
|
rectStopFind.OffsetRect(rectControl.right - rectStopFind.right, rectControl.top - rectStopFind.top);
|
|
rectStartFind.OffsetRect(rectStopFind.left - rectStartFind.right - iSpacer, rectControl.top - rectStartFind.top);
|
|
}
|
|
else
|
|
{
|
|
rectStartFind.OffsetRect(rectControl.right - rectStartFind.right, rectControl.top - rectStartFind.top);
|
|
rectStopFind.OffsetRect(rectControl.right - rectStopFind.right, rectStartFind.bottom + iSpacer - rectStopFind.top);
|
|
}
|
|
|
|
// Position the find label and the find edit box.
|
|
|
|
rectFindWhatLabel.OffsetRect(rectControl.left - rectFindWhatLabel.left, rectControl.top - rectFindWhatLabel.top + (rectFindWhat.Height() - rectFindWhatLabel.Height()) / 2);
|
|
rectFindWhat.OffsetRect(rectFindWhatLabel.right - rectFindWhat.left + iSpacer, rectControl.top - rectFindWhat.top);
|
|
rectFindWhat.right = rectStartFind.left - iSpacer;
|
|
|
|
// Position the check boxes.
|
|
|
|
rectSearchSelected.OffsetRect(rectControl.left - rectSearchSelected.left, rectFindWhat.bottom - rectSearchSelected.top + iSpacer);
|
|
|
|
if (fHorizontal)
|
|
rectSearchCategories.OffsetRect(rectSearchSelected.right - rectSearchCategories.left + iSpacer, rectSearchSelected.top - rectSearchCategories.top);
|
|
else
|
|
rectSearchCategories.OffsetRect(rectControl.left - rectSearchCategories.left, rectSearchSelected.bottom - rectSearchCategories.top + iSpacer);
|
|
|
|
// If the check boxes are going to overlap the buttons (we'd be very narrow), adjust the button
|
|
// position (which might end up off the control, but what're ya gonna do?).
|
|
|
|
int iRightMostCheckboxEdge = rectSearchCategories.right;
|
|
if (iRightMostCheckboxEdge < rectSearchSelected.right)
|
|
iRightMostCheckboxEdge = rectSearchSelected.right;
|
|
iRightMostCheckboxEdge += iSpacer;
|
|
|
|
if (!fHorizontal && rectStopFind.left < iRightMostCheckboxEdge)
|
|
{
|
|
rectStopFind.OffsetRect(iRightMostCheckboxEdge - rectStopFind.left, 0);
|
|
rectStartFind.OffsetRect(rectStopFind.left - rectStartFind.left, 0);
|
|
rectFindWhat.right = rectStartFind.left - iSpacer;
|
|
}
|
|
|
|
m_wndStopFind.MoveWindow(&rectStopFind);
|
|
m_wndStartFind.MoveWindow(&rectStartFind);
|
|
m_wndFindNext.MoveWindow(&rectStartFind);
|
|
m_wndCancelFind.MoveWindow(&rectStopFind);
|
|
m_wndFindWhatLabel.MoveWindow(&rectFindWhatLabel);
|
|
m_wndFindWhat.MoveWindow(&rectFindWhat);
|
|
m_wndSearchSelected.MoveWindow(&rectSearchSelected);
|
|
m_wndSearchCategories.MoveWindow(&rectSearchCategories);
|
|
|
|
return (rectControl.Height() + iSpacer * 2);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Refresh all of the data prior to saving, exporting, printing. This will
|
|
// present a dialog box with the refresh message and a progress bar, but
|
|
// will not return until the refresh is completed.
|
|
//-------------------------------------------------------------------------
|
|
|
|
void CMSInfo::RefreshData(CLiveDataSource * pSource, CMSInfoLiveCategory * pLiveCategory)
|
|
{
|
|
if (pSource == NULL || pSource->m_pThread == NULL)
|
|
return;
|
|
|
|
// Create the dialog with the refresh message and progress
|
|
// bar, and display it.
|
|
|
|
CWaitForRefreshDialog dlg;
|
|
dlg.DoRefresh(pSource, pLiveCategory);
|
|
}
|
|
|
|
//=============================================================================
|
|
// Functions for managing the DCO (the object providing history).
|
|
//=============================================================================
|
|
|
|
STDMETHODIMP CMSInfo::UpdateDCOProgress(VARIANT varPctDone)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState())
|
|
//V-stlowe 1/30/2001
|
|
VERIFY(SUCCEEDED(VariantChangeType(&varPctDone,&varPctDone,0,VT_INT)));
|
|
if (this->m_HistoryProgressDlg.IsWindow())//todo: is there a better state function to determine if dlg is modal?
|
|
{
|
|
HWND hWnd = m_HistoryProgressDlg.GetDlgItem(IDC_PROGRESS1);
|
|
if(::IsWindow(hWnd))
|
|
{
|
|
//int nOffset = varPctDone.iVal - (int) ::SendMessage(m_hWnd, PBM_GETPOS, 0, 0);
|
|
//To do: don't rely on 3 (current SAF progress step); find way to get offset.
|
|
::SendMessage(hWnd, PBM_DELTAPOS,3, 0L);
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CMSInfo::SetHistoryStream(IStream *pStream)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
#ifdef A_STEPHL
|
|
ASSERT(0);
|
|
#endif
|
|
//v-stlowe 2/23/2001 shut down progress bar dialog
|
|
SetEvent(m_hEvtHistoryComplete);
|
|
HRESULT hr = pStream->QueryInterface(IID_IStream,(void**) &m_pHistoryStream);
|
|
if (FAILED(hr) || !m_pHistoryStream)
|
|
{
|
|
m_pHistoryStream = NULL;
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (m_pLiveData)
|
|
((CLiveDataSource *)m_pLiveData)->SetHistoryStream(m_pHistoryStream);
|
|
|
|
// When the history stream is available, we need to modify the UI to allow
|
|
// the user to select the history.
|
|
|
|
if (!m_fHistoryAvailable)
|
|
{
|
|
m_fHistoryAvailable = TRUE;//actually this should already be true...
|
|
|
|
SetMenuItems();
|
|
}
|
|
m_fHistorySaveAvailable = TRUE;
|
|
FillHistoryCombo();
|
|
//if history window is current view, refresh
|
|
if (m_history.IsWindowVisible())
|
|
{
|
|
CMSInfoCategory * pCategory = GetCurrentCategory();
|
|
if (pCategory != NULL && pCategory->GetDataSourceType() == LIVE_DATA)
|
|
{
|
|
m_pLastCurrentCategory = GetCurrentCategory();
|
|
|
|
int iIndex = (int)m_history.SendMessage(CB_GETCURSEL, 0, 0);
|
|
if (iIndex == CB_ERR)
|
|
{
|
|
iIndex = 0;
|
|
m_history.SendMessage(CB_SETCURSEL, (WPARAM)iIndex, 0);
|
|
}
|
|
ChangeHistoryView(iIndex);
|
|
|
|
}
|
|
}
|
|
else if (m_fShowPCH && !m_history.IsWindowVisible() && m_strMachine.IsEmpty())
|
|
{
|
|
// If m_fShowPCH is set, then the command line option to launch into
|
|
// the history view was selected.
|
|
|
|
DispatchCommand(ID_VIEW_HISTORY);
|
|
}
|
|
|
|
#ifdef A_STEPHL
|
|
//STATSTG streamStat;
|
|
//hr = m_pHistoryStream->Stat(&streamStat,STATFLAG_NONAME );
|
|
//ASSERT(SUCCEEDED(hr) && "couldn't get stream statistics");
|
|
//BYTE* pBuffer = new BYTE[streamStat.cbSize.LowPart];
|
|
//ULONG ulRead;
|
|
//m_pHistoryStream->Read(pBuffer,streamStat.cbSize.LowPart,&ulRead);
|
|
// CFile file;
|
|
//file.Open("c:\\history.xml", CFile::modeCreate | CFile::modeWrite);
|
|
// file.Write(pBuffer,ulRead);
|
|
// delete pBuffer;
|
|
#endif
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CMSInfo::get_DCO_IUnknown(IUnknown **pVal)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
|
|
if (m_pDCO == NULL)
|
|
return E_FAIL;
|
|
|
|
return (m_pDCO->QueryInterface(IID_IUnknown,(void**) pVal));
|
|
}
|
|
|
|
STDMETHODIMP CMSInfo::put_DCO_IUnknown(IUnknown *newVal)
|
|
{
|
|
//v-stlowe 2/23/2001
|
|
//beware situation where put_DCO_IUnknown gets called before control is finished initializing.
|
|
WaitForSingleObject(m_evtControlInit,INFINITE);
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
|
|
HRESULT hr = newVal->QueryInterface( __uuidof(ISAFDataCollection), (void**)&m_pDCO );
|
|
if (FAILED(hr))
|
|
return E_FAIL;
|
|
//end v-stlowe 2/23/2001
|
|
|
|
TCHAR szDataspecPath[MAX_PATH];
|
|
if (ExpandEnvironmentStrings(_T("%windir%\\pchealth\\helpctr\\config\\dataspec.xml"), szDataspecPath, MAX_PATH))
|
|
{
|
|
CComBSTR bstrPath(szDataspecPath);
|
|
|
|
if (m_pDCO != NULL && (BSTR)bstrPath != NULL)
|
|
{
|
|
hr = m_pDCO->put_MachineData_DataSpec(bstrPath);
|
|
hr = m_pDCO->put_History_DataSpec(bstrPath);
|
|
}
|
|
// This is done by the script now: m_pDCO->ExecuteAsync();
|
|
}
|
|
|
|
// Have to put this after the calls made using the DCO, so that the /pch
|
|
// flag (which is to start MSInfo with history showing) works.
|
|
|
|
if (!m_fHistoryAvailable)
|
|
{
|
|
m_fHistoryAvailable = TRUE;
|
|
if (m_fShowPCH && !m_history.IsWindowVisible() && m_strMachine.IsEmpty())
|
|
DispatchCommand(ID_VIEW_HISTORY);
|
|
SetMenuItems();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Interface methods to do a silent save of a file.
|
|
//=============================================================================
|
|
|
|
STDMETHODIMP CMSInfo::SaveFile(BSTR filename, BSTR computer, BSTR category)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
|
|
CString strFilename(filename);
|
|
CString strComputer(computer);
|
|
CString strCategory(category);
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
::AfxSetResourceHandle(_Module.GetResourceInstance());
|
|
CLiveDataSource * pSilentSource = new CLiveDataSource;
|
|
if (pSilentSource)
|
|
hr = pSilentSource->Create(strComputer, NULL, strCategory);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_fNoUI = TRUE;
|
|
|
|
CDataSource * pOldSource = m_pCurrData;
|
|
m_pCurrData = pSilentSource;
|
|
if (strFilename.Right(4).CompareNoCase(CString(_T(".nfo"))) == 0)
|
|
SaveMSInfoFile(strFilename);
|
|
else
|
|
ExportFile(strFilename, 0);
|
|
m_pCurrData = pOldSource;
|
|
|
|
delete pSilentSource;
|
|
|
|
m_fNoUI = FALSE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
LRESULT CHistoryRefreshDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
return 1; // Let the system set the focus
|
|
}
|