Click here to Skip to main content
15,894,343 members
Articles / Desktop Programming / MFC

VssReporter 2.1 - A Visual SourceSafe reporting tool for build administrators

Rate me:
Please Sign up or sign in to vote.
4.88/5 (100 votes)
25 Mar 200610 min read 627.1K   8.9K   162  
A support tool to allow those performing builds to independently determine exactly what source files have been changed and by whom
// vssreporterDlg.cpp : implementation file
//

#include "stdafx.h"
#include "vssreporter.h"
#include "vssreporterDlg.h"
#include "ConfigDiffToolDlg.h"

#include "..\shared\regkey.h"
#include "..\shared\folderdialog.h"
#include "..\shared\filemisc.h"
#include "..\shared\deferwndmove.h"

#define COMPILE_MULTIMON_STUBS
#include <multimon.h>

#include <direct.h>
#include <mmsystem.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CVssreporterDlg dialog

enum { UNKNOWN = -1, CREATED, MODIFED, LABELLED, DELETED };
enum { TYPE, FILENAME, FILEPATH, VERSION, LABEL, USER, MODDATE, COMMENT, NUMCOLUMNS };
LPCTSTR COLUMN[] = { "", "Name", "Path", "Ver", "Label", "User", "Date", "Comment" };

#ifdef _VSS5_
# define IVSSVERSION 5
#else
# define IVSSVERSION 6
#endif

CVssreporterDlg::CVssreporterDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CVssreporterDlg::IDD, pParent), m_sizeOrg(0, 0), 
	m_sCreated("CREATED"), m_sCheckedIn("CHECKED IN"), m_sLabeled("LABELED")
{
	//{{AFX_DATA_INIT(CVssreporterDlg)
	//}}AFX_DATA_INIT

	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_bReporting = FALSE;
	m_sCurProject.Empty();
	m_vssdb = NULL;
	m_sUsername = GetUserName();
	m_sQuery = CVssQueryDlg::GetLastQuery();

	InitLocaleStrings();

	m_ilFolders.Create(IDB_FOLDERS, 18, 1, RGB(255, 0, 0));
	m_ilSorting.Create(IDB_SORTARROWS, 8, 1, RGB(255, 0, 0));
	m_ilResults.Create(IDB_RESULTS, 16, 1, RGB(255, 0, 0));
}

CVssreporterDlg::~CVssreporterDlg()
{
}

void CVssreporterDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CVssreporterDlg)
	DDX_Control(pDX, IDC_DATABASELIST, m_cbDatabases);
	DDX_Control(pDX, IDC_FILELIST, m_lcResults);
	DDX_Control(pDX, IDC_VSSTREE, m_tcVSS);
	DDX_CBString(pDX, IDC_DATABASELIST, m_sDbPath);
	DDX_Text(pDX, IDC_FILESFOUND, m_sFilesFound);
	DDX_Text(pDX, IDC_PASSWORD, m_sPassword);
	DDX_Text(pDX, IDC_USERNAME, m_sUsername);
	DDX_Text(pDX, IDC_PROGRESS, m_sProgress);
	DDX_Text(pDX, IDC_QUERY, m_sQuery);
	DDX_Check(pDX, IDC_RECURSIVE, m_bRecursive);
	DDX_Check(pDX, IDC_IGNOREDELETED, m_bIgnoreDeleted);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CVssreporterDlg, CDialog)
	//{{AFX_MSG_MAP(CVssreporterDlg)
	ON_BN_CLICKED(IDC_REPORT, OnReport)
	ON_BN_CLICKED(IDC_CANCELREPORT, OnCancelreport)
	ON_CBN_SELCHANGE(IDC_DATABASELIST, OnSelchangeDatabaselist)
	ON_NOTIFY(TVN_SELCHANGED, IDC_VSSTREE, OnSelchangedVsstree)
	ON_BN_CLICKED(IDC_BROWSEDATABASE, OnBrowsedatabase)
	ON_WM_CLOSE()
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_COPYRESULTS, OnCopyResults)
	ON_WM_CONTEXTMENU()
	ON_COMMAND(ID_RESULTS_VISUALSOURCESAFE, OnShowVSS)
	ON_EN_CHANGE(IDC_USERNAME, OnChangeUsernamePassword)
	ON_COMMAND(ID_COPY_FILESTOFOLDER, OnCopyFilestofolder)
	ON_BN_CLICKED(IDC_EDITQUERY, OnEditquery)
	ON_NOTIFY(LVN_COLUMNCLICK, IDC_FILELIST, OnColumnclickFilelist)
	ON_COMMAND(ID_COPY_TEXTTOCLIPBOARD_TABBED, OnCopyTexttoclipboardTabbed)
	ON_COMMAND(ID_COPY_TEXTTOCLIPBOARD_FORMATTED, OnCopyTexttoclipboardFormatted)
	ON_COMMAND(ID_COPY_TEXTTOFILE_FORMATTED, OnCopyTexttofileFormatted)
	ON_COMMAND(ID_COPY_TEXTTOFILE_TABBED, OnCopyTexttofileTabbed)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_FILELIST, OnSelchangedFilelist)
	ON_COMMAND(ID_RESULTS_SELECTALL, OnResultsSelectall)
	ON_COMMAND(ID_COPY_TEXTTOFILE_XML, OnCopyTexttofileXml)
	ON_COMMAND(ID_COPY_TEXTTOCLIPBOARD_XML, OnCopyTexttoclipboardXml)
	ON_COMMAND(ID_RESULTS_DIFF, OnResultsDiff)
	ON_EN_CHANGE(IDC_PASSWORD, OnChangeUsernamePassword)
	ON_BN_CLICKED(IDC_DIFFTOOL, OnDiffTool)
	//}}AFX_MSG_MAP
	ON_WM_SIZE()
	ON_WM_GETMINMAXINFO()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CVssreporterDlg message handlers

BOOL CVssreporterDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	m_tcVSS.SetImageList(&m_ilFolders, TVSIL_NORMAL);
	m_lcResults.SetImageList(&m_ilResults, LVSIL_SMALL);
	m_lcResults.GetHeaderCtrl()->SetImageList(&m_ilSorting);
	
	m_lcResults.InsertColumn(TYPE, COLUMN[TYPE], LVCFMT_LEFT, 24);
	m_lcResults.InsertColumn(FILENAME, COLUMN[FILENAME], LVCFMT_LEFT, 60);
	m_lcResults.InsertColumn(FILEPATH, COLUMN[FILEPATH], LVCFMT_LEFT, 90);
	m_lcResults.InsertColumn(VERSION, COLUMN[VERSION], LVCFMT_RIGHT, 40);
	m_lcResults.InsertColumn(LABEL, COLUMN[LABEL], LVCFMT_LEFT, 50);
	m_lcResults.InsertColumn(USER, COLUMN[USER], LVCFMT_LEFT, 70);
	m_lcResults.InsertColumn(MODDATE, COLUMN[MODDATE], LVCFMT_LEFT, 130);
	m_lcResults.InsertColumn(COMMENT, COLUMN[COMMENT], LVCFMT_LEFT, 300);
	m_lcResults.SetExtendedStyle(m_lcResults.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	// ensure vss is registered and if not allow user to navigate to it
    int nVSSver = GetVSSVersion();

	if (nVSSver == -1 || nVSSver < IVSSVERSION)
	{
        CString sMessage;

        if (nVSSver != -1) // interface incompatibility
        {
            sMessage.Format("This version of VSSReporter requires Visual SourceSafe %d to run.", IVSSVERSION);

            MessageBox(sMessage, "VSSReporter", MB_OK);
		    EndDialog(-1);
            return TRUE;
        }
        else
        {
		    if (IDNO == MessageBox("VSSReporter requires the Visual SourceSafe component 'ssapi.dll' which does not appear to be registered.\n\nClick 'Yes' to navigate to this component and have VSS Reporter register it for you, \n or 'No' to quit VSSReporter and register it manually",
								    "VSSReporter", MB_YESNO))
		    {
			    EndDialog(-1);
			    return TRUE;
		    }
		    else
		    {
			    CFileDialog dialog(TRUE, "dll", "", OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_NONETWORKBUTTON | OFN_HIDEREADONLY, 
							    "ssapi.dll|ssapi.dll||");
						    
			    dialog.m_ofn.lpstrTitle = "Locate VSS Component";
						    
			    if (dialog.DoModal() == IDCANCEL)
				    EndDialog(-1);
			    else
			    {
				    CString sCmdline;
				    sCmdline.Format("\"%s\" -s", dialog.GetPathName());

				    // we need to wait here until it done
				    SHELLEXECUTEINFO sei;
				    ZeroMemory(&sei, sizeof(sei));

				    sei.cbSize = sizeof(sei);
				    sei.fMask = SEE_MASK_NOCLOSEPROCESS;
				    sei.hwnd = *this;
				    sei.lpFile = "regsvr32.exe";
				    sei.lpParameters = (LPCTSTR)sCmdline;
				    sei.nShow = SW_HIDE;

				    if (ShellExecuteEx(&sei) && sei.hProcess)
				    {
					    WaitForSingleObject(sei.hProcess, INFINITE);
					    CloseHandle(sei.hProcess); // cleanup
				    }
			    }
		    }
        }
	}

	// add interface version to caption
	CString sCaption, sVer;
	GetWindowText(sCaption);
	sVer.Format(" (for VSS %d)", IVSSVERSION);
	SetWindowText(sCaption + sVer);

	RegLoadSettings();
	EnableControls();

	// position resize icon as close to bottom left as pos
	GetDlgItem(IDC_GRIPPER)->ModifyStyle(0, SBS_SIZEGRIP | SBS_SIZEBOXTOPLEFTALIGN);
	CDeferWndMove(1).OffsetCtrl(this, IDC_GRIPPER, 1, 2);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CVssreporterDlg::OnReport() 
{
	UpdateData();
	
	CString sProjPath = m_tcVSS.GetSelectedProject();

	if (!sProjPath.IsEmpty())
	{
		m_bReporting = TRUE;

		if (OpenDatabase(m_sDbPath))
		{
			BOOL bCanReport = FALSE;
			CString sError;

			// we enable the stop button here so that the label finding can be terminated if nec.
			EnableControls();

			// get the query
			VssQuery query;
			CVssQueryDlg::GetLastQuery(query);

			switch (query.nFindFilter)
			{
				case MATCHINGLABEL:
				case MATCHINGCOMMENT:
					bCanReport = !query.sMatch.IsEmpty();
					query.sMatch.MakeUpper();
					break;

				case LASTMODIFIED:
				case MODIFIED:
					{
						switch (query.nDateFilter)
						{
						case AFTER:
							if (query.nTypeFilter == TYPELABEL)
							{
								query.dtAfter = FindLabel(sProjPath, query.sAfterLabel, FALSE);
								
								if (query.dtAfter.m_dt == 0)
									sError.Format("The label '%s' could not be found in the selected project", query.sAfterLabel);
							}
							
							bCanReport = (query.dtAfter.m_dt != 0);
							break;
							
						case BEFORE:
							if (query.nTypeFilter == TYPELABEL)
							{
								query.dtBefore = FindLabel(sProjPath, query.sBeforeLabel, TRUE);
								
								if (query.dtBefore.m_dt == 0)
									sError.Format("The label '%s' could not be found in the selected project", query.sBeforeLabel);
							}
							
							bCanReport = (query.dtBefore.m_dt != 0);
							break;
							
						case BETWEEN:
							if (query.nTypeFilter == TYPELABEL)
							{
								query.dtAfter = FindLabel(sProjPath, query.sAfterLabel, TRUE);
								query.dtBefore = FindLabel(sProjPath, query.sBeforeLabel, FALSE);
								
								if (query.dtBefore.m_dt == 0 && query.dtAfter.m_dt == 0)
									sError.Format("Neither the label '%s' nor the label '%s' could be found in the selected project", query.sBeforeLabel, query.sAfterLabel);
								
								else if (query.dtBefore.m_dt == 0)
									sError.Format("The label '%s' could not be found in the selected project", query.sBeforeLabel);
								
								else if (query.dtAfter.m_dt == 0)
									sError.Format("The label '%s' could not be found in the selected project", query.sAfterLabel);
							}
							
							bCanReport = (query.dtAfter.m_dt != 0 && query.dtBefore.m_dt != 0);
							break;
						}
					}
					break;
			}

			if (bCanReport && Continue(m_bReporting))
			{
				m_lcResults.DeleteAllItems();
				m_sFilesFound = "Files Found: 0";
				m_sortInfo.Reset();
				m_aColLengths.RemoveAll();

				UpdateData(FALSE);

				// do the report
				ReportOnItem(sProjPath, query);

				// sort the results
				m_lcResults.SortItems(ResultsSortFunc, (DWORD)&m_sortInfo);

				// notify user
				PlaySound("SystemAsterisk", NULL, SND_SYNC | SND_ALIAS);
			}
			else if (!sError.IsEmpty() && Continue(m_bReporting))
			{
				MessageBox(sError, "VssReporter � AbstractSpoon"); 
			}
		}
		else
		{
			MessageBox("Unable to open SourceSafe database", "VssReporter � AbstractSpoon"); 
		}

		m_bReporting = FALSE;
		EnableControls();

		m_sProgress.Empty();
		UpdateData(FALSE);
		UpdateWindow();
	}
}

void CVssreporterDlg::OnCancelreport() 
{
	m_bReporting = FALSE;
	EnableControls();
}

void CVssreporterDlg::OnSelchangeDatabaselist() 
{
	// cache previous db path
	CString sPrevDb = m_sDbPath;

	UpdateData();

	if (m_tcVSS.OpenDatabase(m_sDbPath, m_sUsername, m_sPassword))
	{
		// clear results list
		m_lcResults.DeleteAllItems();
		m_sFilesFound.Empty();

		UpdateData(FALSE);
	}
	else // failed so restore previous db
	{
		m_cbDatabases.SelectString(-1, sPrevDb);

		CString sMessage;
		sMessage.Format("The SourceSafe database '%s'\ncould not be found or could not be opened.\n\nThe previously open database has been restored.",
						m_sDbPath);

		MessageBox(sMessage, "VssReporter � AbstractSpoon"); 

		UpdateData();
	}
}

CString CVssreporterDlg::GetUserName()
{
	CString sName;

	DWORD dwLen = MAX_COMPUTERNAME_LENGTH + 1;
	::GetUserName(sName.GetBuffer(dwLen), &dwLen);
	sName.ReleaseBuffer();

	return sName;
}

BOOL CVssreporterDlg::OpenDatabase(LPCTSTR szDatabasePath)
{
	CWaitCursor cursor;
	COleVariant var1;
	IVSSItemPtr	vssi, vssi2p;
	IVSSItemsPtr	vssis;
	
	try
	{
		if (m_vssdb)
		{
			m_vssdb.Release();
			m_vssdb = NULL;
		}

		m_vssdb.CreateInstance(__uuidof(VSSDatabase));
		m_vssdb->Open(szDatabasePath, (LPCTSTR)m_sUsername, (LPCTSTR)m_sPassword);
	}
	catch (...)
	{
		return FALSE;
	}

	return TRUE;
}

BOOL CVssreporterDlg::Continue(BOOL& bContinue)
{
	MSG msg;

	// check messages for the cancel dialog or paint messages
	while (::PeekMessage((LPMSG) &msg, NULL, 0, 0, PM_REMOVE))
	{
		BOOL bDlgMsg = ::IsDialogMessage(*this, &msg);

		if (!bDlgMsg) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return bContinue; 
}

void CVssreporterDlg::ReportOnProject(IVSSItemPtr& vssi, LPCTSTR szVSSPath, const VssQuery& query)
{
	ASSERT (vssi->GetType() == 0l);

	m_sProgress.Format("Reporting on project '%s'", szVSSPath);
	UpdateData(FALSE);
	UpdateWindow();

 	try // ssapi spits sometimes
	{
		IVSSItemsPtr vssis = vssi->GetItems(TRUE); // TRUE also gets deleted items

		int nCount = vssis->GetCount();
		
		for(int x = 0; x < nCount; x++)
		{
			COleVariant var1 = (long)(x + 1);
			IVSSItemPtr vssi2p = vssis->GetItem(var1); 
			CString sItem = CString((LPCTSTR)vssi2p->GetSpec());
			
			ReportOnItem(sItem, query);
		}
	}
	catch (...)
	{
	}
}

void CVssreporterDlg::ReportOnItem(IVSSItemPtr& vssi, LPCTSTR szVSSPath, const VssQuery& query, BOOL bDeleted)
{
	ASSERT (vssi->GetType() != 0l);

	CString sUser, sDate, sLabel;
	CWaitCursor cursor;
	COleVariant var1;		
	COleDateTime date;
	IUnknownPtr lpunk;
	IVSSVersionsPtr objVersions;
	IVSSVersionPtr objVersion;
	
	IEnumVARIANTPtr ppvobj;
	ULONG fetched;
	VARIANT st;

 	try // ssapi spits sometimes
	{
		objVersions = vssi->GetVersions(0L);
		
		// Prepare to loop through the items
		lpunk = objVersions->_NewEnum();
		lpunk.QueryInterface(IID_IEnumVARIANT, (void **)&ppvobj);
		
		CString sUserFilter = GetUserFilter(query);
		BOOL bMatchingLabel = FALSE;
		
		// Loop through the versions
		do 
		{
			ppvobj->Next(1UL, &st, &fetched);
			
			if(fetched != 0) 
			{
				// Try to get the item
				REFIID riid = __uuidof(IVSSVersion);
				HRESULT hr = st.punkVal->QueryInterface(riid, (void**)&objVersion);

				if(!FAILED(hr)) 
				{
					int nModType = bDeleted ? DELETED : GetModType(objVersion);

					// switch on the find filter
					switch (query.nFindFilter)
					{
					case MATCHINGLABEL:
						if (!bDeleted) // doesn't make much sense to search labels on deleted items
						{
							// unfortunately its not as simple as simply testing for the
							// a matching label because then we have to find the last
							// mod preceding this label
							if (!bMatchingLabel) // means we've not yet found the label
							{
								if (LabelMatches(objVersion, query))
									bMatchingLabel = TRUE;
							}
							
							// label has been found so now we must check for a (prior) mod
							// note: we don't 'else' this with the found label because
							// the label may have been attached to the check-in
							if (bMatchingLabel && 
								(nModType == CREATED || nModType == MODIFIED || 
								 (nModType == DELETED && !m_bIgnoreDeleted) || nModType == LABELLED))
							{
								AddResult(szVSSPath, objVersion, nModType);
								fetched = 0; // end loop
							}
						}
						break;
						
					case MATCHINGCOMMENT:
						if (!bDeleted) // doesn't make much sense to search comments on deleted items
						{
							CString sComment(GetComment(objVersion));
							sComment.TrimLeft();
							sComment.TrimRight();

							CString sTemp(sComment);
							sTemp.MakeUpper();

							if (sTemp.Find(query.sMatch) != -1)
							{
								AddResult(szVSSPath, objVersion, nModType);
								fetched = 0; // end loop
							}
						}
						break;
						
					case LASTMODIFIED:
						if (nModType == CREATED || nModType == MODIFIED || 
							(nModType == DELETED && !m_bIgnoreDeleted))
						{
							// first check the user matches
							CString sItemUser = (LPCTSTR) objVersion->GetUsername();
							
							if (sUserFilter.IsEmpty() || sUserFilter.CompareNoCase(sItemUser) == 0)
							{
								COleDateTime date;
								
								// note: versions start at the most recent
								if (DateMatches(objVersion, query, date))
								{
									AddResult(szVSSPath, objVersion, nModType);
									fetched = 0; // end the loop
								}

								// if the file has been deleted we're only interested in the last mod
								// having been within the dates. so if the first date does not match
								// then we drop out regardless
								if (bDeleted)
									fetched = 0; 
							}
						}
						break;

					case MODIFIED:
						if (nModType == CREATED || nModType == MODIFIED || 
							(nModType == DELETED && !m_bIgnoreDeleted))
						{
							// first check the user matches
							CString sItemUser = (LPCTSTR) objVersion->GetUsername();
							
							if (sUserFilter.IsEmpty() || sUserFilter.CompareNoCase(sItemUser) == 0)
							{
								COleDateTime date;

								if (DateMatches(objVersion, query, date))
								{
									AddResult(szVSSPath, objVersion, nModType);
								}

								// if the file has been deleted we're only interested in the last mod
								// having been within the dates. so if the first date does not match
								// or even if it does, we drop out regardless
								if (bDeleted)
									fetched = 0; 
							}
						}
						break;
					}
				}
				st.punkVal->Release();
			}
		} 
		while (fetched != 0 && Continue(m_bReporting));
		
		ppvobj.Release();

		// something very strange can happen here when the pointers are the same
		if (lpunk != ppvobj)
			lpunk.Release();
	}
	catch (...)
	{
		// do nothing
		return;
	}
}

CString CVssreporterDlg::GetComment(const IVSSVersionPtr& objVer)
{
	CString sComment = (LPCTSTR)objVer->GetComment();

#ifndef _VSS5_
#   pragma message("------")
#   pragma message("If compiling for use with Visual SourceSafe 5 you will need to ")
#   pragma message("#define _VSS5_, else you'll have a problem with GetLabelComment()")

	if (sComment.IsEmpty())
		sComment = (LPCTSTR)objVer->GetLabelComment();
#   pragma message("------")
#endif

	return sComment;
}

BOOL CVssreporterDlg::LabelMatches(const IVSSVersionPtr& objVer, const VssQuery& query)
{
	CString sLabel;

	if (GetLabel(objVer, sLabel))
	{
		if (query.bRegExp)
		{
		}
		else
		{
			sLabel.MakeUpper();

			if (sLabel.Find(query.sMatch) >= 0)
				return TRUE;
		}
	}

	return FALSE;
}

void CVssreporterDlg::AddResult(const CString& sVSSPath, LPCTSTR szUser, int nVersion, 
								const COleDateTime& date, LPCTSTR szComment, int nModType,
								LPCTSTR szLabel)
{
	if (date.m_dt <= 0)
		return;

	// add to list
	ResultInfo ri;

	ri.sFile = sVSSPath.Mid(sVSSPath.ReverseFind('/') + 1);
	ri.sPath = sVSSPath.Left(sVSSPath.ReverseFind('/'));
	ri.nVersion = (nModType == DELETED) ? 0 : nVersion;
	ri.sUser = (nModType == DELETED) ? "" : szUser;
	ri.date = (nModType == DELETED) ? 0 : date;
	ri.sComment = (nModType == DELETED) ? "" : szComment;
	ri.sLabel = (nModType == DELETED) ? "" : szLabel;
	ri.nModType = nModType;

	int nIndex = m_lcResults.InsertItem(m_lcResults.GetItemCount(), "", nModType);
	
	if (nIndex >= 0)
	{
		int nResult = m_sortInfo.aResults.Add(ri);

		// permanently tag item with sort data
		m_lcResults.SetItemData(nIndex, nResult);
		m_lcResults.SetItemText(nIndex, FILENAME, ri.sFile);
		m_lcResults.SetItemText(nIndex, FILEPATH, ri.sPath);

		CString sVer, sDate;
		
		if (nModType != DELETED)
		{
			sVer.Format("%d", nVersion);
			m_lcResults.SetItemText(nIndex, VERSION, sVer);

			m_lcResults.SetItemText(nIndex, USER, szUser);
			
			sDate.Format("%s (%s)", date.Format(VAR_DATEVALUEONLY), date.Format(VAR_TIMEVALUEONLY));

			m_lcResults.SetItemText(nIndex, MODDATE, sDate);
			m_lcResults.SetItemText(nIndex, COMMENT, szComment);
			m_lcResults.SetItemText(nIndex, LABEL, szLabel);
		}
		m_lcResults.UpdateWindow();
		
		m_sFilesFound.Format("Files Found: %d", m_lcResults.GetItemCount());
		UpdateData(FALSE);
		
		// update longest items
		if (!m_aColLengths.GetSize()) // first item
		{
			m_aColLengths.SetSize(NUMCOLUMNS);
			m_aColLengths[FILENAME] = ri.sFile.GetLength();
			m_aColLengths[FILEPATH] = ri.sPath.GetLength();
			m_aColLengths[VERSION] = sVer.GetLength();
			m_aColLengths[USER] = ri.sUser.GetLength();
			m_aColLengths[MODDATE] = sDate.GetLength();
			m_aColLengths[COMMENT] = ri.sComment.GetLength();
			m_aColLengths[LABEL] = ri.sLabel.GetLength();
		}
		else
		{
			m_aColLengths[FILENAME] = max(m_aColLengths[FILENAME], ri.sFile.GetLength());
			m_aColLengths[FILEPATH] = max(m_aColLengths[FILEPATH], ri.sPath.GetLength());
			m_aColLengths[VERSION] = max(m_aColLengths[VERSION], sVer.GetLength());
			m_aColLengths[USER] = max(m_aColLengths[USER], ri.sUser.GetLength());
			m_aColLengths[MODDATE] = max(m_aColLengths[MODDATE], sDate.GetLength());
			m_aColLengths[COMMENT] = max(m_aColLengths[COMMENT], ri.sComment.GetLength());
			m_aColLengths[LABEL] = max(m_aColLengths[LABEL], ri.sLabel.GetLength());
		}
	}
}

void CVssreporterDlg::AddResult(const CString& sVSSPath, const IVSSVersionPtr& objVer, int nModType)
{
	int nVer = objVer->GetVersionNumber();
	DATE date = objVer->GetDate();
	CString sUser = (LPCTSTR)objVer->GetUsername();
	CString sComment = GetComment(objVer);
	CString sLabel = GetLabel(objVer);
	
	AddResult(sVSSPath, sUser, nVer, date, sComment, nModType, sLabel);
}

void CVssreporterDlg::ReportOnItem(CString sVSSPath, const VssQuery& query)
{
	CWaitCursor cursor;
	IVSSItemPtr	vssi;	// A vss item
	COleVariant varBuf(sVSSPath);	// special overloaded variant used to talk to OLE
	BOOL bDeleted = FALSE;
	
	try // ssapi spits sometimes
	{
		vssi = m_vssdb->GetVSSItem(varBuf.bstrVal, 0);	// Get the Item passed to us.
	}
	catch (...)
	{
		// we'll get here if the item is deleted so we try again
		try
		{
			vssi = m_vssdb->GetVSSItem(varBuf.bstrVal, 1);	// Get the Item passed to us.
			bDeleted = TRUE; // if we get here its deleted
		}
		catch (...)
		{
			// do nothing
			return;
		}
	}

	if (!Continue(m_bReporting))
		return;

	if (vssi->GetType() == 0l) // See if the thing we got was a project
	{
		if (!bDeleted && (m_bRecursive || sVSSPath == m_sCurProject))
			ReportOnProject(vssi, sVSSPath, query);
	}
	else
		ReportOnItem(vssi, sVSSPath, query, bDeleted);
}

COleDateTime CVssreporterDlg::FindLabel(CString sVSSItem, LPCTSTR szLabel, BOOL bFirst)
{
	CWaitCursor cursor;
	int			x;				// Counter to run thru project
	COleVariant var1;			// special overloaded variant used to talk to OLE
	IVSSItemPtr	vssi, vssi2p;	// A vss item
	IVSSItemsPtr	vssis;		// A vss collection
	COleDateTime date;
	IUnknownPtr       lpunk;
	IVSSVersionsPtr   objVersions;
	IVSSVersionPtr    objVersion;
	
	IEnumVARIANTPtr   ppvobj;
	ULONG             fetched;
	VARIANT           st;

	m_sProgress.Format("Looking for label '%s' in project '%s'", szLabel, sVSSItem);
	UpdateData(FALSE);
	UpdateWindow();

 	try // ssapi spits sometimes
	{
		COleVariant varBuf(sVSSItem);	// special overloaded variant used to talk to OLE
		vssi = m_vssdb->GetVSSItem(varBuf.bstrVal, 0);	// Get the Item passed to us.

		if (!Continue(m_bReporting))
			return date;

		if (vssi->GetType() == 0L) // See if the thing we got was a project
		{
			TRACE ("CVssreporterDlg::FindLabel(project = %s)\n", (LPCTSTR)vssi->GetSpec());

			vssis = vssi->GetItems(FALSE);
			for(x = 0; x < vssis->GetCount(); x++)
			{
				var1 = (long)(x + 1);
				vssi2p = vssis->GetItem(var1); 
				CString sItem = CString((LPCTSTR)vssi2p->GetSpec());

				date = FindLabel(sItem, szLabel, bFirst); // pass it on until it hits an item

				if (date.m_dt)
					break;
			}
		}
		else // its an item
		{
			TRACE ("CVssreporterDlg::FindLabel(item = %s)\n", (LPCTSTR)vssi->GetSpec());

			objVersions = vssi->GetVersions(0L);
			
			// Prepare to loop through the items
			lpunk = objVersions->_NewEnum();
			lpunk.QueryInterface(IID_IEnumVARIANT, (void **)&ppvobj);
			
			// Loop through the versions
			do 
			{
				ppvobj->Next(1UL, &st, &fetched);

				if (fetched != 0) 
				{
					// Try to get the item
					if(!FAILED(st.punkVal->QueryInterface(__uuidof(IVSSVersion), (void**)&objVersion))) 
					{
						CString sItemLabel;

						if (GetLabel(objVersion, sItemLabel) && sItemLabel.CompareNoCase(szLabel) == 0)
						{
							TRACE ("CVssreporterDlg::FindLabel(label = %s)\n", sItemLabel);

							date = objVersion->GetDate();

							// if we are only interested in the first label then stop here
							// else we carry on to the end
							if (bFirst)
								break;
						} 
					}
					st.punkVal->Release();
				}
			} 
			while (fetched != 0 && Continue(m_bReporting));
			
			ppvobj.Release();

			// something very strange can happen here when the pointers are the same
			if (lpunk != ppvobj)
				lpunk.Release();
   		}
	}
	catch (...)
	{
		// do nothing
	}

	return date; 
}

void CVssreporterDlg::OnOK()
{
	m_bReporting = FALSE;

	CDialog::OnOK();
	RegSaveSettings();
}

void CVssreporterDlg::OnCancel()
{
	m_bReporting = FALSE;

	// do nothing else
}

void CVssreporterDlg::RegSaveSettings()
{
	// pos
	WINDOWPLACEMENT wp;
	GetWindowPlacement(&wp);

	AfxGetApp()->WriteProfileInt("Pos", "TopLeft", MAKELPARAM(wp.rcNormalPosition.left, wp.rcNormalPosition.top));
	AfxGetApp()->WriteProfileInt("Pos", "BottomRight", MAKELPARAM(wp.rcNormalPosition.right, wp.rcNormalPosition.bottom));
	
	AfxGetApp()->WriteProfileString("Settings", "VSSPath", m_sVSSPath);
	AfxGetApp()->WriteProfileString("Settings", "Database", m_sDbPath);
	AfxGetApp()->WriteProfileString("Settings", "Username", m_sUsername);
	AfxGetApp()->WriteProfileInt("Settings", "Recursive", m_bRecursive);
	AfxGetApp()->WriteProfileInt("Settings", "IgnoreDeleted", m_bIgnoreDeleted);

	// save database list
	int nCount = m_cbDatabases.GetCount();
	AfxGetApp()->WriteProfileInt("Settings", "DatabaseCount", nCount);

	for (int nDatabase = 0; nDatabase < nCount; nDatabase++)
	{
		CString sDatabase;
		m_cbDatabases.GetLBText(nDatabase, sDatabase);

		if (!sDatabase.IsEmpty())
		{
			CString sKey;
			sKey.Format("Database%02d", nDatabase + 1);
			AfxGetApp()->WriteProfileString("Settings", sKey, sDatabase);
		}
	}

	// save column widths
	if (m_lcResults.GetHeaderCtrl())
	{
		int nCol = m_lcResults.GetHeaderCtrl()->GetItemCount();
		AfxGetApp()->WriteProfileInt("Settings", "ColumnCount", nCol);

		while (nCol--)
		{
			CString sCol;
			sCol.Format("Col%dWidth", nCol);
			AfxGetApp()->WriteProfileInt("Settings", sCol, m_lcResults.GetColumnWidth(nCol));
		}
	}

	// save sort info
	AfxGetApp()->WriteProfileInt("Settings", "SortColumn", m_sortInfo.nSortColumn);
	AfxGetApp()->WriteProfileInt("Settings", "SortAscending", m_sortInfo.bSortAscending);
}

void CVssreporterDlg::RegLoadSettings()
{
	// pos
	WINDOWPLACEMENT wp;
	GetWindowPlacement(&wp);
	
	DWORD dwTopLeft = (DWORD)AfxGetApp()->GetProfileInt("Pos", "TopLeft", -1);
	DWORD dwBottomRight = (DWORD)AfxGetApp()->GetProfileInt("Pos", "BottomRight", -1);
	
	if (dwTopLeft != -1 && dwBottomRight != -1)
	{
		CRect rect(LOWORD(dwTopLeft), HIWORD(dwTopLeft), LOWORD(dwBottomRight), HIWORD(dwBottomRight));
		
		// ensure this intersects with the desktop
		if (NULL != MonitorFromRect(rect, MONITOR_DEFAULTTONULL))
		{
			wp.rcNormalPosition = rect;
			wp.showCmd = SW_HIDE;
			
			SetWindowPlacement(&wp);
		}
	}

	m_sVSSPath = AfxGetApp()->GetProfileString("Settings", "VSSPath", "");
	m_sDbPath = AfxGetApp()->GetProfileString("Settings", "Database", "");
	m_sUsername = AfxGetApp()->GetProfileString("Settings", "Username", "");
	m_bRecursive = AfxGetApp()->GetProfileInt("Settings", "Recursive", TRUE);
	m_bIgnoreDeleted = AfxGetApp()->GetProfileInt("Settings", "IgnoreDeleted", TRUE);

	UpdateData(FALSE);

	// load database list
	int nCount = AfxGetApp()->GetProfileInt("Settings", "DatabaseCount", 0);

	for (int nDatabase = 0; nDatabase < nCount; nDatabase++)
	{
		CString sKey;
		sKey.Format("Database%02d", nDatabase + 1);
		CString sDatabase = AfxGetApp()->GetProfileString("Settings", sKey, "");

		if (!sDatabase.IsEmpty() && m_cbDatabases.FindString(-1, sDatabase) == CB_ERR)
			m_cbDatabases.AddString(sDatabase);
	}

	if (!m_sDbPath.IsEmpty() && m_tcVSS.OpenDatabase(m_sDbPath, m_sUsername, m_sPassword))
	{
		// select active db
		if (m_cbDatabases.FindString(-1, m_sDbPath) == CB_ERR)
			m_cbDatabases.AddString(m_sDbPath);
			
		m_cbDatabases.SelectString(-1, m_sDbPath);
		m_tcVSS.SelectItem(m_tcVSS.GetRootItem());
	}
	else
		m_cbDatabases.SetCurSel(-1);

	// restore column widths unless column count has changed
	if (m_lcResults.GetHeaderCtrl())
	{
		int nCol = m_lcResults.GetHeaderCtrl()->GetItemCount();

		if (nCol == (int)AfxGetApp()->GetProfileInt("Settings", "ColumnCount", -1))
		{
			while (nCol--)
			{
				CString sCol;
				sCol.Format("Col%dWidth", nCol);
				m_lcResults.SetColumnWidth(nCol, AfxGetApp()->GetProfileInt("Settings", sCol, m_lcResults.GetColumnWidth(nCol)));
			}
		}
	}

	// restore sort info
	SetSortColumn(AfxGetApp()->GetProfileInt("Settings", "SortColumn", 0),
					AfxGetApp()->GetProfileInt("Settings", "SortAscending", TRUE));
}

void CVssreporterDlg::EnableControls()
{
	UpdateData();

	GetDlgItem(IDC_REPORT)->EnableWindow(!m_bReporting && !m_sCurProject.IsEmpty());
	GetDlgItem(IDC_CANCELREPORT)->EnableWindow(m_bReporting);
	GetDlgItem(IDC_COPYRESULTS)->EnableWindow(!m_bReporting && m_lcResults.GetSelectedCount());
	GetDlgItem(IDC_PRINT)->EnableWindow(!m_bReporting && m_lcResults.GetItemCount());
	GetDlgItem(IDOK)->EnableWindow(!m_bReporting);
	GetDlgItem(IDC_DATABASELIST)->EnableWindow(!m_bReporting);
}

void CVssreporterDlg::OnSelchangedVsstree(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	m_sCurProject = m_tcVSS.GetSelectedProject();
	EnableControls();

	*pResult = 0;
}

void CVssreporterDlg::MakeValidPath(CString& sPath)
{
	sPath.Replace('/', '\\');
}

void CVssreporterDlg::OnBrowsedatabase() 
{
	CFileDialog dialog(TRUE, NULL, m_sDbPath, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "VSS Databases (srcsafe.ini)|*.ini||");

	dialog.m_ofn.lpstrTitle = "Open Database";

	if (dialog.DoModal() == IDOK)
	{
		CString sDatabase = dialog.GetPathName();

		if (!m_tcVSS.OpenDatabase(sDatabase, m_sUsername, m_sPassword))
		{
			MessageBox("Please ensure that the user name and password are correct and \nthat the named user has the correct access rights.",
						"Unable to Open Vss Database", MB_OK);
			return;
		}

		// add to database list if new and it could be opened
		m_sDbPath = sDatabase;

		if (m_cbDatabases.FindString(-1, sDatabase) == CB_ERR)
			m_cbDatabases.AddString(sDatabase);
			
		m_cbDatabases.SelectString(-1, sDatabase);

		UpdateData(FALSE);
		EnableControls();
	}
}

void CVssreporterDlg::OnClose() 
{
	OnOK();
}

void CVssreporterDlg::OnDestroy() 
{
	CDialog::OnDestroy();
	
	if (m_vssdb)
	{
		m_vssdb.Release();
		m_vssdb = NULL;
	}
}	

void CVssreporterDlg::OnCopyResults() 
{
	CRect rButton;
	GetDlgItem(IDC_COPYRESULTS)->GetWindowRect(rButton);
	
	CMenu menu;
	
	if (menu.LoadMenu(IDR_COPYPOPUP))
	{
		CMenu* pPopup = menu.GetSubMenu(0);
		
		if (pPopup)
		{
			TPMPARAMS tpmp;
			tpmp.cbSize = sizeof(TPMPARAMS);
			tpmp.rcExclude = rButton;

			::TrackPopupMenuEx(*pPopup, TPM_LEFTALIGN, rButton.right, rButton.top, *this, &tpmp);
		}
	}
}

CString CVssreporterDlg::GetResultsAsText(BOOL bTabbed)
{
	int nNumItems = m_lcResults.GetSelectedCount();

	if (!nNumItems)
		return CString();

	CWaitCursor cursor;

	// create a single string containing all results
	// padding as we go
	CString sDate = m_lcResults.GetItemText(0, MODDATE);

	// calculate the final string length 
	// (note: this will be too long for tabbed but we resize at the end)
	ASSERT (m_aColLengths.GetSize());
	int nLength = 0;

	for (int nCol = 0; nCol < NUMCOLUMNS; nCol++)
		nLength += nNumItems * (m_aColLengths[nCol] + 3); // 3 for spacing

	nLength += 2 * nNumItems; // for '\r\n' on each line

	// get the buffer as a raw char* of the required length
	CString sResults;
	LPTSTR szResults = sResults.GetBuffer(nLength);

	// init with spaces (so we don't have to manually pad)
	memset(szResults, bTabbed ? '\t' : ' ', nLength);
	int nOffset = 0;

	POSITION pos = m_lcResults.GetFirstSelectedItemPosition();

	while (pos)
	{
		int nItem = m_lcResults.GetNextSelectedItem(pos);

		for (int nCol = 0; nCol < NUMCOLUMNS; nCol++)
		{
			CString sItemCol = m_lcResults.GetItemText(nItem, nCol);

			CopyMemory(szResults + nOffset, sItemCol, sItemCol.GetLength());

			if (bTabbed)
				nOffset += sItemCol.GetLength() + 1;
			else
				nOffset += m_aColLengths[nCol] + 3; // 3 for spacing
		}

		CopyMemory(szResults + nOffset, "\r\n", 2);
		nOffset += 2;
	}

	ASSERT (bTabbed || nOffset == nLength);
	sResults.ReleaseBuffer(nLength);

	if (bTabbed)
		sResults.TrimRight();

	return sResults;

}

CString CVssreporterDlg::GetResultsAsXml()
{
	int nNumItems = m_lcResults.GetSelectedCount();

	if (!nNumItems)
		return CString();

	CWaitCursor cursor;

	// create a single string containing all results
	// padding as we go
	CString sDate = m_lcResults.GetItemText(0, MODDATE);

	// calculate the final string length 
	ASSERT (m_aColLengths.GetSize());
	int nLength = 0;

	LPCTSTR szOpenTags[] = { "<FILE>", "<PATH>", "<VERSION>", "<USER>", "<DATE>", "<COMMENT>" };
	LPCTSTR szCloseTags[] = { "</FILE>", "</PATH>", "</VERSION>", "</USER>", "</DATE>", "</COMMENT>" };

	nLength += nNumItems * lstrlen("</RESULT>\r\n") * 2;

	for (int nCol = 0; nCol < NUMCOLUMNS; nCol++)
	{
		nLength += nNumItems * 2; // '\t\t'
		nLength += nNumItems * lstrlen(szOpenTags[nCol]);
		nLength += nNumItems * m_aColLengths[nCol];
		nLength += nNumItems * lstrlen(szCloseTags[nCol]);
		nLength += nNumItems * 2;  // '\r\n' on each line
	}

	// add a bit for the query and toplevel tags
	nLength += m_sQuery.GetLength() * 2; // to allow for replacing quotes and such
	nLength += lstrlen("</RESULTS>\r\n") * 2;

	// get the buffer as a raw char* of the required length
	CString sResults;
	LPTSTR szResults = sResults.GetBuffer(nLength);

	// init with spaces 
	memset(szResults, ' ', nLength);

	// add top level tag
	CopyMemory(szResults, "<RESULTS>\r\n", lstrlen("<RESULTS>\r\n"));
	szResults += lstrlen("<RESULTS>\r\n");

	// add query
	CString sQuery;
	sQuery.Format("\t<QUERY>%s</QUERY>\r\n", m_sQuery);

	// replace quotes and such like

	CopyMemory(szResults, (LPCTSTR)sQuery, sQuery.GetLength());
	szResults += sQuery.GetLength();

	// add results
	POSITION pos = m_lcResults.GetFirstSelectedItemPosition();

	while (pos)
	{
		int nItem = m_lcResults.GetNextSelectedItem(pos);

		CopyMemory(szResults, "\t<RESULT>\r\n", lstrlen("\t<RESULT>\r\n"));
		szResults += lstrlen("\t<RESULT>\r\n");

		for (int nCol = 0; nCol < NUMCOLUMNS; nCol++)
		{
			CString sItemCol = m_lcResults.GetItemText(nItem, nCol);

			CString sResult;
			sResult.Format("\t\t%s%s%s\r\n", szOpenTags[nCol], sItemCol, szCloseTags[nCol]);

			CopyMemory(szResults, sResult, sResult.GetLength());
			szResults += sResult.GetLength();
		}

		CopyMemory(szResults, "\t</RESULT>\r\n", lstrlen("\t</RESULT>\r\n"));
		szResults += lstrlen("\t</RESULT>\r\n");
	}

	// add top level tag
	CopyMemory(szResults, "</RESULTS>\r\n", lstrlen("</RESULTS>\r\n"));
	szResults += lstrlen("</RESULTS>\r\n");

	sResults.ReleaseBuffer(nLength);

	return sResults;

}

CString CVssreporterDlg::GetUserFilter(const VssQuery& query)
{
	UpdateData();

	switch (query.nUserFilter)
	{
		case ME:
			return m_sUsername;

		case OTHERUSER:
			return query.sOtherUser;

		case EVERYONE:
		default:
			return "";
	}
}

void CVssreporterDlg::OnSize(UINT nType, int cx, int cy)
{
	CDialog::OnSize(nType, cx, cy);

	if (nType != SIZE_MINIMIZED)
	{
		if (m_sizeOrg.cx && m_sizeOrg.cy)
		{
			CDeferWndMove dwm(20);

			int nXOffset = cx - m_sizeOrg.cx, nYOffset = cy - m_sizeOrg.cy;

			// repos left-hand ctrls up or down only
			if (nYOffset)
			{
				dwm.OffsetCtrl(this, IDC_QUERYLABEL, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_QUERY, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_EDITQUERY, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_QUERYDIVIDER, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_REPORT, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_CANCELREPORT, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_LEFTDIVIDER, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_RECURSIVE, 0, nYOffset);
				dwm.OffsetCtrl(this, IDC_IGNOREDELETED, 0, nYOffset);

				// resize vss tree
				dwm.ResizeCtrl(this, IDC_VSSTREE, 0, nYOffset); 
			}

			// repos right-hand ctrls
			if (nXOffset || nYOffset)
			{
				// gripper
				dwm.OffsetCtrl(this, IDC_GRIPPER, nXOffset, nYOffset);

				// vert only
				dwm.OffsetCtrl(this, IDC_FILESFOUND, 0, nYOffset);

				// resize
				dwm.ResizeCtrl(this, IDC_VERTDIVIDER, 0, nYOffset);

				// vert and/or horz
				dwm.OffsetCtrl(this, IDC_COPYTOCLIPBOARD, nXOffset, nYOffset);
				dwm.OffsetCtrl(this, IDC_DIFFTOOL, nXOffset, nYOffset);
				dwm.OffsetCtrl(this, IDC_PRINT, nXOffset, nYOffset);
				dwm.OffsetCtrl(this, IDC_RESIZE, nXOffset, nYOffset);
				
				CRect rOK = dwm.OffsetCtrl(this, IDOK, nXOffset, nYOffset);

				// resize progress text to fit
				CRect rCtrl = dwm.OffsetCtrl(this, IDC_PROGRESS);
				rCtrl.OffsetRect(0, nYOffset);
				rCtrl.right = rOK.left - 10;
				dwm.MoveWindow(GetDlgItem(IDC_PROGRESS), rCtrl);

				// resize results list to suit
				rCtrl = dwm.OffsetCtrl(this, IDC_FILELIST); // just gets the current rect
				rCtrl.right += (nXOffset);
				rCtrl.bottom += (nYOffset);
				dwm.MoveWindow(&m_lcResults, rCtrl);

				// and base divider
				rCtrl = dwm.OffsetCtrl(this, IDC_BASEDIVIDER); // just gets the current rect
				rCtrl.OffsetRect(0, nYOffset);
				rCtrl.right += (nXOffset);
				dwm.MoveWindow(GetDlgItem(IDC_BASEDIVIDER), rCtrl);
			}

			GetDlgItem(IDC_GRIPPER)->ShowWindow(nType == SIZE_MAXIMIZED ? SW_HIDE : SW_SHOW);
		}

		m_sizeOrg.cx = cx;
		m_sizeOrg.cy = cy;
	}
}

void CVssreporterDlg::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
	CDialog::OnGetMinMaxInfo(lpMMI);

	// original dialog template size
	lpMMI->ptMinTrackSize.x = 640;
	lpMMI->ptMinTrackSize.y = 480;
}

void CVssreporterDlg::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	if (pWnd == &m_lcResults)
	{
		CMenu menu;

		if (menu.LoadMenu(IDR_RESULTSPOPUP))
		{
			CMenu* pPopup = menu.GetSubMenu(0);

			if (pPopup)
			{
				int nNumSel = m_lcResults.GetSelectedCount();

				// modify string to show project name if a single item is selected
				if (nNumSel == 1)
				{
					CString sItem, sProject = GetSelectedResult(FILEPATH);

					if (!sProject.IsEmpty())
					{
						sItem.Format("Run Visual SourceSafe (%s)", sProject);
						pPopup->ModifyMenu(0, MF_BYPOSITION, ID_RESULTS_VISUALSOURCESAFE, sItem);
					}
				}

				// enable diff item if only one item is selected
				if (nNumSel == 1)
					pPopup->ModifyMenu(ID_RESULTS_DIFF, MF_BYCOMMAND, ID_RESULTS_DIFF, "Show &Differences with local version");

				else if (nNumSel != 2)
					pPopup->EnableMenuItem(ID_RESULTS_DIFF, MF_BYCOMMAND | MF_GRAYED);

				// enable copy items if any items selected
				pPopup->EnableMenuItem(2, MF_BYPOSITION | (nNumSel ? MF_ENABLED : MF_GRAYED));
				pPopup->EnableMenuItem(4, MF_BYPOSITION | (nNumSel ? MF_ENABLED : MF_GRAYED));
				pPopup->EnableMenuItem(5, MF_BYPOSITION | (nNumSel ? MF_ENABLED : MF_GRAYED));
				pPopup->EnableMenuItem(6, MF_BYPOSITION | (nNumSel ? MF_ENABLED : MF_GRAYED));

				pPopup->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
			}
		}
	}
}

void CVssreporterDlg::OnShowVSS() 
{
	LPCTSTR szVSSPath = GetVSSExplorerPath();

	if (!szVSSPath)
		return;

	CString sCmdLine, sProject = GetSelectedResult(FILEPATH);

	if (!sProject.IsEmpty())
		sCmdLine.Format("-p\"%s\" -y%s,%s", sProject, m_sUsername, m_sPassword);

	ShellExecute(*this, NULL, szVSSPath, sCmdLine, NULL, SW_NORMAL);	
}

CString CVssreporterDlg::GetSelectedResult(int nCol, int nItem)
{
	CString sSel;
	POSITION pos = m_lcResults.GetFirstSelectedItemPosition();

	if (pos)
	{
		int nSel = -1;

		do
		{
			nSel = m_lcResults.GetNextSelectedItem(pos);
		}
		while (nItem-- > 0 && pos);

		if (nSel != -1)
			sSel = m_lcResults.GetItemText(nSel, nCol);
	}

	return sSel;
}

LPCTSTR CVssreporterDlg::GetVSSExplorerPath()
{
	DWORD dwAttr = ::GetFileAttributes(m_sVSSPath);
				
	if (dwAttr == 0xffffffff)
	{
		// see if we can find it ourselves
		m_sVSSPath = GetVSSPath() + "\\ssexp.exe";

		if (::GetFileAttributes(m_sVSSPath) == 0xffffffff)
		{
			UINT uRes = MessageBox("Before the you can use this feature for the first time, \nyou must specify the location of the VSS executable.", 
							"VSSReporter", MB_ICONINFORMATION | MB_OKCANCEL);
						
			if (uRes == IDCANCEL)
				return NULL;
						
			// else
			CFileDialog dialog(TRUE, "exe", "", OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_NONETWORKBUTTON | OFN_HIDEREADONLY, 
							"Visual SourceSafe (ssexp.exe)|ssexp.exe||");
						
			dialog.m_ofn.lpstrTitle = "Locate VSS Executable";
						
			if (dialog.DoModal() == IDCANCEL)
				return NULL;
						
			m_sVSSPath = dialog.GetPathName();
		}
	}
				
	return (m_sVSSPath.IsEmpty() ? NULL : (LPCTSTR)m_sVSSPath);			
}

BOOL CVssreporterDlg::IsVSSRegistered()
{
	if (m_vssdb)
		return TRUE;

	// else
	try
	{
		m_vssdb.CreateInstance(__uuidof(VSSDatabase));
	}
	catch(...)
	{
	}

	return (m_vssdb != NULL);
}

int CVssreporterDlg::GetVSSVersion()
{
    if (IsVSSRegistered())
        return IVSSVERSION; // exact version not important

    // else
    int nVer = -1;

#ifndef _VSS5_ // ie. version 6
    // try old getting old db interface
   	IVSSDatabaseOldPtr vssOld;

	try
	{
		vssOld.CreateInstance(__uuidof(VSSDatabase));

        if (vssOld)
            nVer = 5;
	}
	catch(...)
	{
	}
#endif

    return nVer;
}

CString CVssreporterDlg::GetVSSPath()
{
	GUID id = __uuidof(VSSDatabase);
	unsigned char* pszGuid;
	
	RPC_STATUS rpcs = UuidToString(&id, &pszGuid);

	CString sKey;
	sKey.Format("CLSID\\{%s}\\InprocServer32", pszGuid);
	RpcStringFree(&pszGuid);

	CRegKey reg;
	CString sPath;

	if (reg.Open(HKEY_CLASSES_ROOT, sKey) == ERROR_SUCCESS)
	{
		reg.Read(NULL, sPath);

		// make sure it exists
		if (GetFileAttributes(sPath) == 0xffffffff)
			sPath.Empty();
		else
			sPath = sPath.Left(sPath.ReverseFind('\\'));
	}

	return sPath;
}

void CVssreporterDlg::OnChangeUsernamePassword() 
{
	UpdateData();
}

void CVssreporterDlg::InitLocaleStrings()
{
	char szCountryCode[3];
	GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, szCountryCode, 3);

	CString sResPath;
	sResPath.Format("%s\\ss%s.dll", GetVSSPath(), szCountryCode);

	HMODULE hRes = LoadLibrary(sResPath);

	if (hRes)
	{
		char szFormat[64];

		if (::LoadString(hRes, 4040, szFormat, 64))
		{
			m_sCreated.Format(szFormat, ""); // remove %s
			m_sCreated.TrimLeft();
			m_sCreated.TrimRight();
			m_sCreated.MakeUpper();
		}

		if (::LoadString(hRes, 4053, szFormat, 64))
		{
			m_sCheckedIn.Format(szFormat, ""); // remove %s
			m_sCheckedIn.TrimLeft();
			m_sCheckedIn.TrimRight();
			m_sCheckedIn.MakeUpper();
		}

		if (::LoadString(hRes, 4042, szFormat, 64))
		{
			m_sLabeled.Format(szFormat, ""); // remove %s
			m_sLabeled.Replace("''", ""); // remove single quotes
			m_sLabeled.TrimLeft();
			m_sLabeled.TrimRight();
			m_sLabeled.MakeUpper();
		}
	}
}

BOOL CVssreporterDlg::GetLabel(const IVSSVersionPtr& objVer, CString& sLabel)
{
/*	CString vssAction = (LPCTSTR) objVer->GetAction();
	vssAction.MakeUpper();

	TRACE ("GetLabel: Action = %s\n", vssAction);

	// compare action with our locale specific string
	if (vssAction.Find(m_sLabeled) != -1) 
	{
		sLabel = vssAction.Mid(m_sLabeled.GetLength() + 2);
		sLabel = sLabel.Left(sLabel.GetLength() - 1);

		return TRUE;
	}
	else // see if its got a label
*/	{
		sLabel = (LPCTSTR) objVer->GetLabel();

		if (sLabel.GetLength())
			return TRUE;
	}

	return FALSE;
}

CString CVssreporterDlg::GetLabel(const IVSSVersionPtr& objVer)
{
	CString sLabel = (LPCTSTR)objVer->GetLabel();
	return sLabel;
}

int CVssreporterDlg::GetModType(const IVSSVersionPtr& objVer)
{
	CString vssAction = (LPCTSTR) objVer->GetAction();
	vssAction.MakeUpper();

	// compare action with our locale specific strings
	if (vssAction.Find(m_sCheckedIn) != -1)
		return MODIFIED;

	else if (vssAction.Find(m_sCreated) != -1)
		return CREATED;

	else if (vssAction.Find(m_sLabeled) != -1)
		return LABELLED;

	// else
	return UNKNOWN;
}

BOOL CVssreporterDlg::IsCheckIn(const IVSSVersionPtr& objVer)
{
	CString vssAction = (LPCTSTR) objVer->GetAction();
	vssAction.MakeUpper();

	// compare action with our locale specific string
	return (vssAction.Find(m_sCheckedIn) != -1);
}

BOOL CVssreporterDlg::IsCreated(const IVSSVersionPtr& objVer)
{
	CString vssAction = (LPCTSTR) objVer->GetAction();
	vssAction.MakeUpper();

	// compare action with our locale specific string
	return (vssAction.Find(m_sCreated) != -1);
}

BOOL CVssreporterDlg::DateMatches(const IVSSVersionPtr& objVer, const VssQuery& query, COleDateTime& date)
{
	// check the date
	date = objVer->GetDate();
	
	switch (query.nDateFilter)
	{
	case AFTER:
		return (date > query.dtAfter);
								
	case BEFORE:
		return (date < query.dtBefore);
								
	case BETWEEN:
		return (date > query.dtAfter) && (date < query.dtBefore);
	}

	return FALSE;
}

void CVssreporterDlg::OnCopyFilestofolder() 
{
	CFolderDialog dialog("Select the folder to which you want to copy the matching files", NULL, this);

	if (dialog.DoModal() == IDOK)
	{
		int nRes = AfxMessageBox("Do you want to retain the folder structure of the matching files?"
								"\n\nSelecting 'No' will flatten the folder structure", MB_YESNOCANCEL);

		if (nRes == IDCANCEL)
			return;

		BOOL bCopyTree = (nRes == IDYES);

		CString sDestFolder = dialog.GetFolderPath();

		// iterate the files, getting the latest version and copying as we go
		POSITION pos = m_lcResults.GetFirstSelectedItemPosition();

		while (pos)
		{
			int nItem = m_lcResults.GetNextSelectedItem(pos);

			CString sFile = m_lcResults.GetItemText(nItem, FILENAME);
			CString sPath = m_lcResults.GetItemText(nItem, FILEPATH);
			long nVer = atoi(m_lcResults.GetItemText(nItem, VERSION));

			CString sTempFile = GetVssFile(sFile, sPath, nVer);

			if (!sTempFile.IsEmpty())
			{
				CString sDestPath;

				if (bCopyTree)
					sDestPath.Format("%s\\%s\\%s", sDestFolder, sPath.Mid(2), sFile);
				else
					sDestPath.Format("%s\\%s", sDestFolder, sFile);

				// make sure the destination folder exist
				MakeValidPath(sDestPath);
				FileMisc::CreateFolder(sDestPath.Left(sDestPath.ReverseFind('\\')));
				
				if (!CopyFile(sTempFile, sDestPath, FALSE))
				{
					// notify user
				}

				// delete the file
				// make sure we reset any attributes first
				::SetFileAttributes(sTempFile, FILE_ATTRIBUTE_NORMAL);
				DeleteFile(sTempFile);
			}
			else // notify user
			{
				// TODO
			}
		}
	}
}

void CVssreporterDlg::CopyTexttoclipboard(const CString& sText) 
{
	if (sText.IsEmpty())
		return;
	
	if (!::OpenClipboard(GetSafeHwnd())) 
		return; 

    ::EmptyClipboard(); 
 
    // Allocate a global memory object for the text. 
	HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (sText.GetLength() + 1) * sizeof(TCHAR)); 

	if (!hglbCopy) 
	{ 
		CloseClipboard(); 
		return; 
	} 
	
	// Lock the handle and copy the text to the buffer. 
	LPTSTR lptstrCopy = (LPTSTR)GlobalLock(hglbCopy); 

	memcpy(lptstrCopy, (LPVOID)(LPCTSTR)sText, sText.GetLength() * sizeof(TCHAR)); 

	lptstrCopy[sText.GetLength()] = (TCHAR) 0;    // null character 
	GlobalUnlock(hglbCopy); 
	
	// Place the handle on the clipboard. 
	::SetClipboardData(CF_TEXT, hglbCopy); 

	::CloseClipboard();
}

void CVssreporterDlg::CopyTexttofile(const CString& sText, BOOL bXml) 
{
	if (sText.IsEmpty())
		return;
	
	CFileDialog dialog(FALSE, bXml ? "xml" : "txt", NULL, 0, 
						bXml ? "Xml Files (*.xml)|*.xml||" : "Text Files (*.txt, *.csv)|*.txt;*.csv||", this);
	
	if (dialog.DoModal() == IDOK)
	{
		CStdioFile file;

		if (file.Open(dialog.GetFileName(), CFile::modeCreate | CFile::modeWrite))
		{
			file.WriteString(sText);
			file.Close();
		}
	}
}

BOOL CVssreporterDlg::PreTranslateMessage(MSG* pMsg) 
{
	// handle return key ourselves
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		if (pMsg->hwnd == ::GetDlgItem(*this, IDC_PASSWORD) ||
			pMsg->hwnd == ::GetDlgItem(*this, IDC_USERNAME) ||
			pMsg->hwnd == ::GetDlgItem(*this, IDC_DATABASELIST))
		{
			UpdateData();

			if (!m_sDbPath.IsEmpty())
				m_tcVSS.OpenDatabase(m_sDbPath, m_sUsername, m_sPassword);
		}

		return TRUE; // never pass it on
	}
	// handle shortcuts
	else if (pMsg->message == WM_CHAR)
	{
		switch (pMsg->wParam)
		{
		case 1: // Ctrl + A
			if (pMsg->hwnd == ::GetDlgItem(*this, IDC_FILELIST))
				OnResultsSelectall();
			break;
		}
	}
	
	return CDialog::PreTranslateMessage(pMsg);
}

void CVssreporterDlg::OnEditquery() 
{
	CVssQueryDlg dialog;
	
	if (dialog.DoModal() == IDOK)
	{
		m_sQuery = dialog.GetQuery();
		UpdateData(FALSE);
	}
}

void CVssreporterDlg::OnColumnclickFilelist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	// update sort column
	SetSortColumn(pNMListView->iSubItem);

	// do the sort
	m_lcResults.SortItems(ResultsSortFunc, (DWORD)&m_sortInfo);
	
	*pResult = 0;
}

void CVssreporterDlg::SetSortColumn(int nCol, BOOL bAscending)
{
	if (nCol == m_sortInfo.nSortColumn)
	{
		if (bAscending == -1)
			bAscending = !m_sortInfo.bSortAscending;

		m_sortInfo.bSortAscending = bAscending;

		// set image
		HDITEM hdi;
		hdi.mask = HDI_IMAGE | HDI_FORMAT;

		m_lcResults.GetHeaderCtrl()->GetItem(nCol, &hdi);
		hdi.fmt |= HDF_IMAGE | (nCol == TYPE ? HDF_RIGHT : HDF_BITMAP_ON_RIGHT);
		hdi.iImage = m_sortInfo.bSortAscending ? 1 : 0;
		m_lcResults.GetHeaderCtrl()->SetItem(nCol, &hdi);
	}
	else // remove previous image if nec and add the new one
	{
		HDITEM hdi;
		hdi.mask = HDI_IMAGE | HDI_FORMAT;
	
		if (m_sortInfo.nSortColumn != -1)
		{
			m_lcResults.GetHeaderCtrl()->GetItem(m_sortInfo.nSortColumn, &hdi);
			hdi.fmt &= ~HDF_IMAGE;
			m_lcResults.GetHeaderCtrl()->SetItem(m_sortInfo.nSortColumn, &hdi);
		}

		if (bAscending != -1)
			m_sortInfo.bSortAscending = bAscending;

		m_lcResults.GetHeaderCtrl()->GetItem(nCol, &hdi);
		hdi.fmt |= HDF_IMAGE | (nCol == TYPE ? HDF_RIGHT : HDF_BITMAP_ON_RIGHT);
		hdi.iImage = m_sortInfo.bSortAscending ? 1 : 0;
		m_lcResults.GetHeaderCtrl()->SetItem(nCol, &hdi);
	}

	m_sortInfo.nSortColumn = nCol;
}

int CALLBACK CVssreporterDlg::ResultsSortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	SortInfo* pSortInfo = (SortInfo*)lParamSort;

	switch (pSortInfo->nSortColumn)
	{
	case TYPE:
		{
			int nType1 = (int)pSortInfo->aResults[lParam1].nModType;
			int nType2 = (int)pSortInfo->aResults[lParam2].nModType;

			if (nType1 < nType2)
				return pSortInfo->bSortAscending ? -1 : 1;

			else if (nType1 > nType2)
				return pSortInfo->bSortAscending ? 1 : -1;
			else
				return 0;
		}
		
	case FILENAME:
		if (pSortInfo->bSortAscending)
			return lstrcmpi(pSortInfo->aResults[lParam1].sFile, pSortInfo->aResults[lParam2].sFile);
		else
			return lstrcmpi(pSortInfo->aResults[lParam2].sFile, pSortInfo->aResults[lParam1].sFile);
		
	case FILEPATH:
		if (pSortInfo->bSortAscending)
			return lstrcmpi(pSortInfo->aResults[lParam1].sPath, pSortInfo->aResults[lParam2].sPath);
		else
			return lstrcmpi(pSortInfo->aResults[lParam2].sPath, pSortInfo->aResults[lParam1].sPath);
		
	case VERSION:
		if (pSortInfo->aResults[lParam1].nVersion < pSortInfo->aResults[lParam2].nVersion)
			return pSortInfo->bSortAscending ? -1 : 1;

		else if (pSortInfo->aResults[lParam1].nVersion > pSortInfo->aResults[lParam2].nVersion)
			return pSortInfo->bSortAscending ? 1 : -1;
		else
			return 0;
		
	case USER:
		if (pSortInfo->bSortAscending)
			return lstrcmpi(pSortInfo->aResults[lParam1].sUser, pSortInfo->aResults[lParam2].sUser);
		else
			return lstrcmpi(pSortInfo->aResults[lParam2].sUser, pSortInfo->aResults[lParam1].sUser);
		
	case MODDATE:
		if (pSortInfo->aResults[lParam1].date < pSortInfo->aResults[lParam2].date)
			return pSortInfo->bSortAscending ? -1 : 1;

		else if (pSortInfo->aResults[lParam1].date > pSortInfo->aResults[lParam2].date)
			return pSortInfo->bSortAscending ? 1 : -1;
		else
			return 0;

	case COMMENT:
		if (pSortInfo->bSortAscending)
			return lstrcmpi(pSortInfo->aResults[lParam1].sComment, pSortInfo->aResults[lParam2].sComment);
		else
			return lstrcmpi(pSortInfo->aResults[lParam2].sComment, pSortInfo->aResults[lParam1].sComment);

	case LABEL:
		if (pSortInfo->bSortAscending)
			return lstrcmpi(pSortInfo->aResults[lParam1].sLabel, pSortInfo->aResults[lParam2].sLabel);
		else
			return lstrcmpi(pSortInfo->aResults[lParam2].sLabel, pSortInfo->aResults[lParam1].sLabel);
	}

	return 0;
}

void CVssreporterDlg::OnCopyTexttoclipboardTabbed() 
{
	CopyTexttoclipboard(GetResultsAsText(TRUE));
}

void CVssreporterDlg::OnCopyTexttoclipboardFormatted() 
{
	CopyTexttoclipboard(GetResultsAsText(FALSE));
}

void CVssreporterDlg::OnCopyTexttofileFormatted() 
{
	CopyTexttofile(GetResultsAsText(FALSE), FALSE);
}

void CVssreporterDlg::OnCopyTexttofileTabbed() 
{
	CopyTexttofile(GetResultsAsText(TRUE), FALSE);
}

void CVssreporterDlg::OnSelchangedFilelist(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	if (!m_bReporting)
	{
		GetDlgItem(IDC_COPYRESULTS)->EnableWindow(m_lcResults.GetSelectedCount() > 0);
	}
		
	*pResult = 0;
}

void CVssreporterDlg::OnResultsSelectall() 
{
	int nItem = m_lcResults.GetItemCount();

	while (nItem--)
		m_lcResults.SetItemState(nItem, LVIS_FOCUSED | LVIS_SELECTED, 0x000F);
}

CString CVssreporterDlg::GetVssFile(LPCTSTR szName, LPCTSTR szPath, int nVersion)
{
	CString sItemPath(szPath);
	sItemPath.TrimRight();

	if (sItemPath.Right(1) != "/")
		sItemPath += '/';

	sItemPath += szName;

	try
	{
		IVSSItemPtr vssi = m_vssdb->GetVSSItem(COleVariant(sItemPath).bstrVal, 0);

		if (vssi)
		{
			IVSSItemPtr vssiVer = vssi->GetVersion(_variant_t((long)nVersion));

			if (vssiVer)
			{
				CString sTempPath, sPath(szPath);

				if (!sPath.Replace("$/", ""))
					sPath.Replace("$", "");

				// make sure the folders exist
				sTempPath.Format("c:\\temp\\vssreporter\\%s", sPath);
				FileMisc::CreateFolder(sTempPath);

				// ensure we have a unique file path
				sTempPath.Format("c:\\temp\\vssreporter\\%s\\%s.%d", sPath, szName, nVersion);
				MakeValidPath(sTempPath);
				
				COleVariant sTempDir(sTempPath);
				
				if (SUCCEEDED(vssiVer->Get(&sTempDir.bstrVal, 0))) // get the file
					return sTempPath;
			}
		}
	}
	catch (...)
	{
	}

	return "";
}

void CVssreporterDlg::OnCopyTexttofileXml() 
{
	CopyTexttofile(GetResultsAsXml(), TRUE);
}

void CVssreporterDlg::OnCopyTexttoclipboardXml() 
{
	CopyTexttoclipboard(GetResultsAsXml());
}


void CVssreporterDlg::OnResultsDiff() 
{
	int nSelCount = m_lcResults.GetSelectedCount();

	if (nSelCount == 0 || nSelCount > 2)
		return;

	CString sFile1, sFile2;

	if (nSelCount == 1) // diff with local ver
	{
		CString sFile = GetSelectedResult(FILENAME);
		CString sProject = GetSelectedResult(FILEPATH);
		int nVer = atoi(GetSelectedResult(VERSION));

		sFile1 = GetVssFile(sFile, sProject, nVer);
		sFile2 = GetLocalPath(sProject, sFile);
	}
	else // diff 2 vss files
	{
		CString sFile = GetSelectedResult(FILENAME, 0);
		CString sProject = GetSelectedResult(FILEPATH, 0);
		int nVer = atoi(GetSelectedResult(VERSION, 0));

		sFile1 = GetVssFile(sFile, sProject, nVer);

		sFile = GetSelectedResult(FILENAME, 1);
		sProject = GetSelectedResult(FILEPATH, 1);
		nVer = atoi(GetSelectedResult(VERSION, 1));

		sFile2 = GetVssFile(sFile, sProject, nVer);
	}

	if (!sFile1.IsEmpty() && !sFile2.IsEmpty())
	{
		CConfigDiffToolDlg dialog;

		CString sDiffTool = dialog.GetToolPath();

		// check it exists
		while (GetFileAttributes(sDiffTool) == 0xffffffff)
		{
			if (dialog.DoModal() == IDOK)
				sDiffTool = dialog.GetToolPath();
			else
				return; // cancelled
		}

		CString sCommandLine = dialog.GetCommandLine();
		
		// replace %1 and %2 in the command line
		sCommandLine.Replace("%1", sFile1);
		sCommandLine.Replace("%2", sFile2);
		
		if ((int)ShellExecute(NULL, NULL, sDiffTool, sCommandLine, NULL, SW_SHOWNORMAL) <= 32)
		{
		}
	}

}

CString CVssreporterDlg::GetLocalPath(LPCTSTR szVssProject, LPCTSTR szFileName)
{
	CString sVSSPath;
	sVSSPath.Format("%s/%s", szVssProject, szFileName);

	COleVariant varBuf(sVSSPath);	// special overloaded variant used to talk to OLE
	IVSSItemPtr vssi;

	try
	{
		vssi = m_vssdb->GetVSSItem(varBuf.bstrVal, 0);	// Get the Item passed to us.
	}
	catch(...)
	{
		return "";
	}

	if (vssi->GetType() != 0l) // must be a file
	{
		return CString((LPCTSTR)vssi->GetLocalSpec());
	}

	return "";
}

void CVssreporterDlg::OnDiffTool() 
{
	CConfigDiffToolDlg dialog;
	
	dialog.DoModal();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer Maptek
Australia Australia
.dan.g. is a naturalised Australian and has been developing commercial windows software since 1998.

Comments and Discussions