Click here to Skip to main content
15,888,908 members
Articles / Desktop Programming / ATL

Increment File and Product Version Number - Multiple IDE

Rate me:
Please Sign up or sign in to vote.
4.58/5 (32 votes)
21 Oct 2008CPOL12 min read 292.1K   1.2K   134  
An add-in to automatically increment the FileVersion and ProductVersion fields in your application's resource file. Works in VC6 and VS2005, and probably all versions in between.
/**
 * \file DTEAddIn.cpp
 *
 * \brief Implementation file for class CDTEAddIn
 *
 * $Id: DTEAddIn.cpp, v1.2 2006/09/26 15:52:40 mgh Exp $
 *
 *
 * Copyright (C) 2006 Michael G. Herstine <sp1ff@pobox.com>
 *
 * Permission to use, copy, or modify this source code is hereby granted
 * free of charge, provided that this copyright notice appear on all
 * copies and on all source code derived from this code.  No
 * representation is made regarding the suitability of this software for
 * any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 *
 */
 
////////////////////////////////////////////////////////////////////////////
// Modified by Jordan Walters, 01.03.2008 for multi-IDE version increment //
////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"								// Pre-compiled header
#include "AddIn.h"				                // For CreateInstanceWithParamItf
#include "DTEAddIn.h"							// For class CDTEAddIn
#include "IncVersionUI/resource.h"				// Resource definitions for our

#include <time.h>

#include <sstream>
#include <algorithm>

_ATL_FUNC_INFO OnClickButtonInfo = {
	CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BYREF | VT_BOOL }
};

////////////////////////////////////////////////////////////////////////
// Class CWindowEventsSink

CWindowEventsSink::CWindowEventsSink(CDTEAddIn *pParent) :
	m_pParent(pParent)
{ }

void __stdcall
CWindowEventsSink::WindowClosing(EnvDTE::Window * /*pWindow*/)
{
	ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowClosing\n");
}

void __stdcall
CWindowEventsSink::WindowMoved(EnvDTE::Window * /*pWindow*/,
							   long /*nTop*/,
							   long /*nLeft*/,
							   long /*nWidth*/,
							   long /*nHeight*/)
{
	ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowMoved\n");
}

void __stdcall
CWindowEventsSink::WindowActivated(EnvDTE::Window * /*pGotFocus*/,
								   EnvDTE::Window * /*pLostFocus*/)
{
	ATLTRACE2(atlTraceHosting, 4, "CWindowEventSink::WindowActivated\n");
}

void __stdcall
CWindowEventsSink::WindowCreated(EnvDTE::Window * /*pWindow*/)
{
	ATLTRACE2(atlTraceHosting, 4, "CWindowEventsSink::WindowCreated\n");
}


////////////////////////////////////////////////////////////////////////
// Class CWindowEventsSink

CBuildEventsSink::CBuildEventsSink(CDTEAddIn *pParent) :
m_pParent(pParent)
{ }

void __stdcall CBuildEventsSink::BeforeBuildStart(EnvDTE::vsBuildScope /*Scope*/, EnvDTE::vsBuildAction /*Action*/)
{
	g_mapListResourceName.clear();
	g_mapListResourceContent.clear();
	g_mapListOrigResourceContent.clear();
	m_listIncWarningMessages.clear();
}

void __stdcall CBuildEventsSink::AfterBuildFinish(EnvDTE::vsBuildScope /*Scope*/, EnvDTE::vsBuildAction /*Action*/)
{
	// Print/Display any error messages here.
	if(m_listIncWarningMessages.size() > 0)
	{
		CComPtr<EnvDTE::Windows> pWindows = NULL;
		if(SUCCEEDED(m_pParent->GetApplication()->get_Windows(&pWindows)))
		{
			CComPtr<EnvDTE::Window> pWindow = NULL;
			if(SUCCEEDED(pWindows->Item(CComVariant(CComBSTR(EnvDTE::vsWindowKindOutput)), &pWindow)))
			{
				CComPtr<IDispatch> pDisp = NULL;
				if(SUCCEEDED(pWindow->get_Object(&pDisp)))
				{
					CComQIPtr<EnvDTE::OutputWindow> pOutputWindow = pDisp;
					CComPtr<EnvDTE::OutputWindowPanes> pOutputWindowPanes = NULL;
					if(SUCCEEDED(pOutputWindow->get_OutputWindowPanes(&pOutputWindowPanes)))
					{
						CComPtr<EnvDTE::OutputWindowPane> pIncVersionWindowPane;
						if(FAILED(pOutputWindowPanes->Item(CComVariant("IncVersion"), &pIncVersionWindowPane)))
						{
							pOutputWindowPanes->Add(CComBSTR("IncVersion"), &pIncVersionWindowPane);
						}
						pIncVersionWindowPane->Activate();
						// Finally print the stuff out
						pIncVersionWindowPane->Clear();

						time_t ltime;
						time(&ltime);
						struct tm today;
						_localtime64_s(&today, &ltime);
						char timeBuf[128];
						strftime(timeBuf, 128, "%#c", &today);
						pIncVersionWindowPane->OutputString(CComBSTR("\n"));
						pIncVersionWindowPane->OutputString(CComBSTR("IncVersion - ") + (timeBuf));
						pIncVersionWindowPane->OutputString(CComBSTR("\n----------------------------------------------------------------\n"));

						std::string strWarning;
						std::list<std::string>::iterator it = m_listIncWarningMessages.begin();
						std::list<std::string>::iterator itEnd = m_listIncWarningMessages.end();
						for(; it != itEnd; ++it)
						{
							strWarning = *it + "\n";
							CComBSTR strOutStr(strWarning.c_str());
							pIncVersionWindowPane->OutputString(strOutStr);
						}
					}
				}
			}
		}
	}
}

void __stdcall CBuildEventsSink::BeforeProjConfBuildStart(BSTR sProject,
														  BSTR /*sProjectConfig*/,
														  BSTR /*sPlatform*/,
														  BSTR /*sSolutionConfig*/)
{
	USES_CONVERSION;

	ATLTRACE2(atlTraceHosting, 4, "CBuildEventsSink::BeforeProjConfBuildStart\n");

	// We must call the version increment stuff BEFORE the build actually happens
	// so that the rc/rc2 files get saved with the new versions.
	// An (unfortunate) side effect is that the version gets updated every time you
	// do a build, even if there have been no code changes.
	// This function is called for each project built in the active configuration.
	std::string strProjectName = m_pParent->GetSolutionPath() + "\\" + OLE2T(sProject);

	// We can sometimes receive two of these notifications per project.
	// So if we have already serviced an event for a project, ignore any subsequent
	// events for that project.
	if(g_mapListResourceName.find(strProjectName) == g_mapListResourceName.end())
	{
		std::list<std::string> strListResourceName;
		std::list<std::string> strListResourceContent;
		std::list<std::string> strListOrigResourceContent;

		m_pParent->GetParent()->IncVersion(strProjectName,
			es_IDE_BuildStart,
			strListResourceName,
			strListResourceContent,
			strListOrigResourceContent,
			m_listIncWarningMessages);

		g_mapListResourceName[strProjectName] = strListResourceName;
		g_mapListResourceContent[strProjectName] = strListResourceContent;
		g_mapListOrigResourceContent[strProjectName] = strListOrigResourceContent;
	}
}

void __stdcall CBuildEventsSink::BuildProjConfFinish(BSTR sProject,
													 BSTR /*sProjectConfig*/,
													 BSTR /*sPlatform*/,
													 BSTR /*sSolutionConfig*/,
													 bool bSuccess)
{
	USES_CONVERSION;

	ATLTRACE2(atlTraceHosting, 4, "CBuildEventsSink::BuildProjConfFinish\n");

	std::string strProjectName = m_pParent->GetSolutionPath() + "\\" + OLE2T(sProject);

	std::map<std::string, std::list<std::string>>::iterator rProject = g_mapListResourceName.find(strProjectName);
	// We can sometimes receive two of these notifications per project.
	// So if we have already serviced an event for a project, ignore any subsequent
	// events for that project.
	if(rProject != g_mapListResourceName.end())
	{
		if(!bSuccess)
		{
			// If the build failed, reset the resource version numbers to the original content
			m_pParent->GetParent()->updateResource(g_mapListResourceName[strProjectName],
				g_mapListOrigResourceContent[strProjectName]);
		}
		else if(g_mapListResourceContent.size() > 0)
		{
			// We incremented a digit.  If it was not the build number, but the 
			// major/minor version/revision, and if the user has configured to revert
			// to incrementing the build number, do this here.
			DWORD dwDigit = DEF_DIGIT;
			bool bResetLowerVersions = DEF_RESET;
			bool bRevertToBuildNumber = DEF_REVERT;
			getOptionsFromReg(dwDigit, bResetLowerVersions, bRevertToBuildNumber);
			if(bRevertToBuildNumber)
			{
				dwDigit = BUILD_NUMBER_DIGIT;
				setOptionsToReg(dwDigit, bResetLowerVersions, bRevertToBuildNumber);
			}
		}

		// And remove the map entries.
		g_mapListResourceName.erase(rProject);
	}
}


////////////////////////////////////////////////////////////////////////
// Class CDTEAddIn

const wchar_t * CDTEAddIn::CMD_BAR_NAME = L"IncVersion";

const CComBSTR CDTEAddIn::CMD_INCREMENT(_T("IncVersion"));

const CComBSTR CDTEAddIn::CMD_DSC_INCREMENT(_T("Increment Version"));

const wchar_t * CDTEAddIn::CMD_FULL_INCREMENT = L"IncVersion.CoAddIn.IncVersion";

const CComBSTR CDTEAddIn::CMD_CONFIGURE(_T("Configure"));

const CComBSTR CDTEAddIn::CMD_DSC_CONFIGURE(_T("Configure the IncVersion AddIn"));

const wchar_t * CDTEAddIn::CMD_FULL_CONFIGURE = L"IncVersion.CoAddIn.Configure";

CDTEAddIn::CDTEAddIn() :
	m_nHost(Host_Unknown),
	m_objWindowEvents(this),
	m_objBuildEvents(this)
{
}

HRESULT CDTEAddIn::FinalConstruct()
{
	ATLTRACE2(atlTraceCOM, 2, "CDTEAddIn::FinalConstruct\n");
	return S_OK;
}

void CDTEAddIn::FinalRelease()
{
	ATLTRACE2(atlTraceCOM, 2, "CDTEAddIn::FinalRelease\n");
}

void CDTEAddIn::SetParam(CAddIn *pParent)
{
	ATLASSERT(NULL != pParent);

	m_pParent = pParent;
}

////////////////////////////////////////////////////////////////////////
// Interface ISupportsErrorInfo

STDMETHODIMP CDTEAddIn::InterfaceSupportsErrorInfo(/*[in]*/ REFIID riid)
{
	static const IID* arr[] = {
		&EnvDTE::IID_IDTCommandTarget,
		&AddInDesignerObjects::IID__IDTExtensibility2,
	};

	for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
	{
		if (InlineIsEqualGUID(*arr[i],riid)) return S_OK;
	}

	return S_FALSE;
}

////////////////////////////////////////////////////////////////////////
// IDTCommandTarget

/**
 * \brief Returns the current status of the specified named command
 *
 *
 * \param bszCmdName The name of the command to check
 *
 * \param nNeededText A vsCommandStatusTextWanted constant specifying if
 * information is returned from the check, and if so, what type of
 * information is returned (Name, None, or Status)
 *
 * \param pnStatus A vsCommandStatus specifying the current status of
 * the command (Enabled, Invisible, Latched, Supported, or Unsupported)
 *
 * \param pvtCommandText The text to return if
 * vsCommandStatusTextWantedStatus is specified
 *
 *
 */

STDMETHODIMP CDTEAddIn::QueryStatus(BSTR                              bszCmdName,
									EnvDTE::vsCommandStatusTextWanted nNeededText,
									EnvDTE::vsCommandStatus          *pnStatusOption,
									VARIANT                          * /*pvtCommandText*/)
{
	const EnvDTE::vsCommandStatus STATUS_ON =
		(EnvDTE::vsCommandStatus)(EnvDTE::vsCommandStatusEnabled +
		EnvDTE::vsCommandStatusSupported);

	if (EnvDTE::vsCommandStatusTextWantedNone == nNeededText)
	{
		if (!_wcsicmp(bszCmdName, CMD_FULL_INCREMENT) ||
			!_wcsicmp(bszCmdName, CMD_FULL_CONFIGURE))
		{
			*pnStatusOption = STATUS_ON;
		}
	}

	return S_OK;
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Get active project. In VS.NET, return the first startup project.
// INPUT: None
// OUTPUT: Active project object.
// NOTE: DTE::Solution::solutionBuild::StartupProjects[0] 
//  StartupProjects contains a list of projects that "start" when the Run command is issued.
///////////////////////////////////////////////////////////////////////////////////////////
CComPtr<EnvDTE::Project> CDTEAddIn::GetActiveProject()
{
	CComPtr<EnvDTE::Project> prj = GetFirstStartupProject(); 

	if (prj) return prj;

	// IMPORTANT NOTE: Startup project may be empty.
	// ATLASSERT(prj!=NULL);

	// Temp solution: use ActiveSolutionProjects[0] instead. 

	VARIANT actPrjs;

	HRESULT re = m_pApp->get_ActiveSolutionProjects(&actPrjs);
	ATLASSERT(SUCCEEDED(re));
#ifndef _DEBUG
	UNREFERENCED_PARAMETER(re);
#endif

	SAFEARRAY *a=actPrjs.parray;
	VARIANT tempVariant;

	LONG i[1]={a->rgsabound[0].lLbound};
	for(LONG p = i[0];p<static_cast<long>(a->rgsabound[0].cElements);p++)
	{
		HRESULT re = SafeArrayGetElement(a,&p,&tempVariant);
		if(FAILED(re))break;

		IDispatch* idp = tempVariant.pdispVal;
		CComPtr<EnvDTE::Project> tempPrj;
		idp->QueryInterface(__uuidof(EnvDTE::Project), (LPVOID*)&tempPrj);
		if(tempPrj)
		{
			return tempPrj;
		}
	}

	return NULL;
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Get active project. In VS.NET, return the first startup project.
// INPUT: None
// OUTPUT: First startup project object.
// NOTE: DTE::Solution::solutionBuild::StartupProjects[0] 
//  StartupProjects contains a list of projects that "start" when the Run command is issued.
//  Return value may be empty, for example App.vcproj, the project name is App1.
///////////////////////////////////////////////////////////////////////////////////////////
CComPtr<EnvDTE::Project> CDTEAddIn::GetFirstStartupProject()
{
	CComPtr<EnvDTE::_Solution> so;
	HRESULT re = m_pApp->get_Solution(&so);
	ATLASSERT(SUCCEEDED(re));

	CComPtr<EnvDTE::SolutionBuild> sb;
	re = so->get_SolutionBuild(&sb);
	ATLASSERT(SUCCEEDED(re));

	BSTR s;
	so->get_FileName(&s);

	CComVariant variant;
	sb->get_StartupProjects(&variant);
	if(variant.vt == VT_EMPTY)
		return NULL;
	SAFEARRAY* array = variant.parray;
	long ix = 0;
	VARIANT var;
	if(SafeArrayGetElement(array, &ix, &var) != S_OK)
		return NULL;
	//_bstr_t startUpRrjName(var.bstrVal,false);

	// The return startup project is subdir\file.vcproj for example: App\App.vcproj. Remove the file name.
	/*
	CString sFile(var.bstrVal);
	int nBegin = sFile.ReverseFind('\\');
	nBegin ++; 
	int nEnd = sFile.ReverseFind('.');
	if (nEnd==-1) nBegin = 999;
	CString sPrj = sFile.Mid(nBegin,nEnd-nBegin);
	*/
	_bstr_t startUpRrjName(var.bstrVal);

	CComPtr<EnvDTE::Project> prj = GetProjectByFileName(startUpRrjName);
	return prj;

	// IMPORTANT NOTE: Startup project may be empty.
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Get active project name
// INPUT: None
// OUTPUT: The active project name.
///////////////////////////////////////////////////////////////////////////////////////////
_bstr_t CDTEAddIn::GetActiveProjectName()
{
	BSTR bstr = NULL;

	// if don't get the active project then return NULL
	if(!GetActiveProject())
	{
		return _bstr_t(NULL, false);
	}
	else
	{
		GetActiveProject()->get_FullName(&bstr);   /* here don't alloc memory to bstr */
	}
	_bstr_t bstrRet = _bstr_t(bstr, false);
	return bstrRet;
}

/*******************************************************************************************
DESCRIPTION: Get project object in workspace/solution. 
INPUT: The project name 
OUTPUT: The project object.
*******************************************************************************************/
CComPtr<EnvDTE::Project> CDTEAddIn::GetProjectByFileName(const _bstr_t &sPrjFileName)
{
	return GetProject(sPrjFileName, TRUE);
}

/*******************************************************************************************
DESCRIPTION: Get project object in workspace/solution. 
INPUT: Project name if bIsFileName is FALSE by default 
or the relative file name if bIsFileName is TRUE.
OUTPUT: The project object.
*******************************************************************************************/
CComPtr<EnvDTE::Project> CDTEAddIn::GetProject(const _bstr_t &sProject, BOOL bIsFileName)
{
	USES_CONVERSION;

	CComPtr<EnvDTE::_Solution> m_pSolution;
	CComPtr<EnvDTE::Projects> m_pProjects; 
	ATLASSERT(m_pApp != NULL);
	HRESULT re = m_pApp->get_Solution(&m_pSolution);
	ATLASSERT(SUCCEEDED(re));
	re = m_pSolution->get_Projects(&m_pProjects);
	ATLASSERT(SUCCEEDED(re));

	CComPtr<EnvDTE::Project> spFoundProj = NULL;
	ATLASSERT(m_pProjects != NULL);

	long count = GetProjectCount();
	VARIANT va;
	VariantInit(&va);
	V_VT(&va)= VT_UINT;

	for(long i = 1; i <= count && !spFoundProj; i++)
	{
		V_UINT(&va) = i;
		CComPtr<EnvDTE::Project> spTempProj;
		m_pSolution->Item(va, &spTempProj);
		BSTR pname = NULL;

		if (bIsFileName)
		{
			spTempProj->get_FullName(&pname);

			std::string sFullFileName(_bstr_t(pname, false)), sFile(sProject);
			std::transform(sFullFileName.begin(), sFullFileName.end(), sFullFileName.begin(), tolower);
			std::transform(sFile.begin(), sFile.end(), sFile.begin(), tolower);
			std::string::size_type nLen = sProject.length();
			if (sFullFileName.length() > nLen && sFullFileName.substr(sFullFileName.length() - nLen) == sFile)
			{
				spFoundProj = spTempProj;
			}
		}
		else 
		{
			spTempProj->get_FullName(&pname);
			_bstr_t s(pname, false);
			if(0 == _wcsicmp(s, sProject)) // not case sensitive
			{
				spFoundProj = spTempProj;
			}
		}
	}
	VariantClear(&va);
	return spFoundProj;
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Get project count in workspace/solution. 
// INPUT: None 
// OUTPUT: The number of projects.
///////////////////////////////////////////////////////////////////////////////////////////
long CDTEAddIn::GetProjectCount()
{
	ATLASSERT(m_pApp != NULL);

	CComPtr<EnvDTE::_Solution> m_pSolution;
	CComPtr<EnvDTE::Projects> m_pProjects; 
	HRESULT re = m_pApp->get_Solution(&m_pSolution);
	ATLASSERT(SUCCEEDED(re));
	re = m_pSolution->get_Projects(&m_pProjects);
	ATLASSERT(SUCCEEDED(re));

	long lCount = 0;
	re = m_pProjects->get_Count(&lCount);
	ATLASSERT(SUCCEEDED(re));
	return lCount;
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Get the full path to the solution - minus the actual solution name itself
// INPUT: None
// OUTPUT: The full solution path.
///////////////////////////////////////////////////////////////////////////////////////////
std::string CDTEAddIn::GetSolutionPath()
{
	BSTR bstr = NULL;

	CComPtr<EnvDTE::_Solution> m_pSolution;
	HRESULT re = m_pApp->get_Solution(&m_pSolution);
	ATLASSERT(SUCCEEDED(re));

	re = m_pSolution->get_FullName(&bstr);
	ATLASSERT(SUCCEEDED(re));

	std::string strFullPathAndFilename = _bstr_t(bstr, false);

	// Strip off the actual project name to leave just the full path.
	std::string::size_type nBackslash = strFullPathAndFilename.find_last_of('\\');
	std::string strFullPath = strFullPathAndFilename.substr(0, nBackslash);

	return strFullPath;
}

/**
 * \brief Execute a given named command
 *
 *
 * \param bszCmdName The name of the command to execute
 *
 * \param nExecuteOption A vsCommandExecOption constant specifying the
 * execution options
 *
 * \param pvtVariantIn A value passed to the command
 *
 * \param pvtVariantOut A value passed back to the invoker Exec method
 * after the command executes
 *
 * \param pvfHandled true indicates that the command was
 * implemented. false indicates that it was not
 *
 *
 */

STDMETHODIMP CDTEAddIn::Exec(BSTR                        bszCmdName,
							 EnvDTE::vsCommandExecOption nExecuteOption,
							 VARIANT                    * /*pvtVariantIn*/,
							 VARIANT                    * /*pvtVariantOut*/,
							 VARIANT_BOOL               *pvfHandled)
{
	USES_CONVERSION;

	*pvfHandled = VARIANT_FALSE;

	if (EnvDTE::vsCommandExecOptionDoDefault == nExecuteOption)
	{
		if (!_wcsicmp(bszCmdName, CMD_FULL_INCREMENT))
		{
			// User has pressed the IncVersion button.
			std::string strProjectName = _bstr_t(GetActiveProjectName().GetBSTR(), false);
			std::list<std::string> strListResourceName;
			std::list<std::string> strListResourceContent;
			std::list<std::string> strListOrigResourceContent;
			std::list<std::string> listIncWarningMessages;

			m_pParent->IncVersion(strProjectName,
				es_User_ButtonPress,
				strListResourceName,
				strListResourceContent,
				strListOrigResourceContent,
				listIncWarningMessages);

			if(listIncWarningMessages.size())
			{
				std::stringstream ssWarningText;
				std::list<std::string>::iterator it = listIncWarningMessages.begin();
				std::list<std::string>::iterator itEnd = listIncWarningMessages.end();
				for(; it != itEnd; ++it)
				{
					ssWarningText << *it << "\n";
				}
				MessageBox(NULL, ssWarningText.str().c_str(), INCVERSION, MB_OK | MB_ICONWARNING);
			}

			g_mapListResourceName[strProjectName] = strListResourceName;
			g_mapListResourceContent[strProjectName] = strListResourceContent;
			g_mapListOrigResourceContent[strProjectName] = strListOrigResourceContent;

			*pvfHandled = VARIANT_TRUE;
		}
		else if (!_wcsicmp(bszCmdName, CMD_FULL_CONFIGURE))
		{
			ConfigureInternal();
			*pvfHandled = VARIANT_TRUE;
		}
	}

	return S_OK;
}

////////////////////////////////////////////////////////////////////////
// IDTExtensibility2

/**
 * \brief Called by our host to setup a connection to this AddIn
 *
 *
 * \param pApplication An IDispatch interface on the object representing
 * our host; we'll need to QI to find out exactly what interfaces it
 * supports
 *
 * \param nMode An enumerated value indicating the circumstances of the
 * connection:
 *
 * <ol>
 *
 * <li>ext_cm_AfterStartup (0) Add-in was loaded after the application
 * started, or by setting the Connect property of the corresponding
 * AddIn object to True.
 *
 * <li>ext_cm_Startup (1) Add-in was loaded at startup.
 *
 * <li>ext_cm_External (2) Add-in was loaded externally by another
 * program or component.
 *
 * <li>ext_cm_CommandLine (3) Add-in was loaded through the Host's
 * command line.
 *
 * <li>ext_cm_Solution (4) The Add-in was loaded when a user loaded a
 * solution that required the Add-in.
 *
 * <li>ext_cm_UISetup (5) The Add-in was started for the first time
 * since being installed.
 *
 * </ol>
 *
 * \param pAddInInst An IDispatch reference on an AddIn instance
 * representing this AddIn
 *
 * \return S_OK on success, or a stock HRESULT else
 *
 *
 * This method will check to be sure that our commands are there (adding
 * them if they're  not).  Depending on the value of nMode &
 * INCVERSION_COMMAND_BAR_STYLE, we may also setup our command bars.
 * Finally, here is also where we sink any events we're interested in.
 *
 *
 */
 
STDMETHODIMP CDTEAddIn::OnConnection(IDispatch               *pApplication,
									 AddInDO::ext_ConnectMode nMode,
									 IDispatch               *pAddInInst,
									 SAFEARRAY              ** /*ppCustom*/)
{
	HRESULT hr = S_OK;            // Eventual return value

	try
	{
		// Validate our parameters...
		if (NULL == pApplication) throw _com_error(E_INVALIDARG);
		if (NULL == pAddInInst)   throw _com_error(E_INVALIDARG);

		// take a reference on the AddIn object representing us,
		m_pAddIn = com_cast<EnvDTE::AddIn>(pAddInInst);

		// & try to figure out what DTE-compatible host we're currently
		// loaded into:
		m_nHost = GuessHostType(pApplication);
		m_pParent->SetApplicationObject((IApplication *)pApplication);

		ATLTRACE2(atlTraceHosting, 2, "CoDTEAddIn has been loaded with a connect mode of %d (our host is %d).\n", nMode, m_nHost);

		// Ok -- that done, we can actually carry on this method's actual work:
		AddCommands(nMode);  // adding our commands (and maybe toolbars),
		SinkEvents(nMode);   // & sink any events we care about.

		// That's it -- we're done...

	}
	HANDLE_COM_ERROR(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)
	HANDLE_EXCEPTION(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)

	return hr;
} // End CDTEAddIn::OnConnection.

/**
 * \brief Called by our host when this AddIn is unloaded
 *
 *
 * \param nMode An ext_DisconnectMode enumeration value that informs an
 * add-in why it was unloaded
 *
 * \param ppCustom An empty array that you can use to pass host-specific
 * data for use after the add-in unloads
 *
 *
 * This method un-advise any event sinks we've setup.  Depending on the
 * value of nMode & INCVERSION_COMMAND_BAR_STYLE, we may also remove our
 * command bars.
 *
 *
 */

STDMETHODIMP CDTEAddIn::OnDisconnection(AddInDO::ext_DisconnectMode nMode,
										SAFEARRAY           ** /*ppCustom*/)
{
	HRESULT hr = S_OK;            // Eventual return value

	try
	{
		ATLTRACE2(atlTraceHosting, 2, "CoDTEAddIn is being unloaded with"
			" a removal code of %d.\n", nMode);

		// Cleanup in the reverse order:
		UnSinkEvents(nMode);        // Un-advise any event sinks we've setup,
		RemoveCommands(nMode);      // & cleanup our command bars.

		// That done, we just Release any outstanding references we may have
		// on our host (or host-provided objects):
		m_pAddIn = NULL;
		m_pApp   = NULL;

	}
	HANDLE_COM_ERROR(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)
	HANDLE_EXCEPTION(CLSID_CoDTEAddIn, AddInDO::IID__IDTExtensibility2, hr)

	return hr;
} // End CDTEAddIn::OnDisconnection.

STDMETHODIMP CDTEAddIn::OnAddInsUpdate(SAFEARRAY ** /*custom*/)
{
	return S_OK;
}

STDMETHODIMP CDTEAddIn::OnStartupComplete(SAFEARRAY ** /*custom*/)
{
	return S_OK;
}

STDMETHODIMP CDTEAddIn::OnBeginShutdown(SAFEARRAY ** /*custom*/)
{
	return S_OK;
}

void /*__stdcall*/ CDTEAddIn::OnClickInc(
	IDispatch * /*Office::_CommandBarButton**/ /*pCtrl*/,
	VARIANT_BOOL * /*pCancelDefault*/)
{
	// User has pressed the IncVersion button.
	std::string strProjectName = _bstr_t(GetActiveProjectName().GetBSTR(), false);
	std::list<std::string> strListResourceName;
	std::list<std::string> strListResourceContent;
	std::list<std::string> strListOrigResourceContent;
	std::list<std::string> listIncWarningMessages;

	m_pParent->IncVersion(strProjectName,
		es_User_ButtonPress,
		strListResourceName,
		strListResourceContent,
		strListOrigResourceContent,
		listIncWarningMessages);

	if(listIncWarningMessages.size())
	{
		std::stringstream ssWarningText;
		std::list<std::string>::iterator it = listIncWarningMessages.begin();
		std::list<std::string>::iterator itEnd = listIncWarningMessages.end();
		for(; it != itEnd; ++it)
		{
			ssWarningText << *it << "\n";
		}
		MessageBox(NULL, ssWarningText.str().c_str(), INCVERSION, MB_OK | MB_ICONWARNING);
	}

	g_mapListResourceName[strProjectName] = strListResourceName;
	g_mapListResourceContent[strProjectName] = strListResourceContent;
	g_mapListOrigResourceContent[strProjectName] = strListOrigResourceContent;
}

void /*__stdcall*/ CDTEAddIn::OnClickCfg(
	IDispatch * /*Office::_CommandBarButton**/ /*pCtrl*/,
	VARIANT_BOOL * /*pCancelDefault*/)
{
	ConfigureInternal();
}

////////////////////////////////////////////////////////////////////////

/**
 * \brief Make sure our commands are present & setup any needed toolbars
 *
 * \sa OnConnection
 *
 *
 * \param nMode Enumerated value indicating the nature of this
 * connection
 *
 *
 * This method is only called from OnConnection; I just factored it out
 * to make that method more readable.
 *
 *
 */

void CDTEAddIn::AddCommands(AddInDO::ext_ConnectMode nMode)
{
	switch (m_nHost)
	{
	case Host_VS2003:
		AddCommandsVS2003(nMode);
		break;
	case Host_VS2005:
		AddCommandsVS2005(nMode);
		break;
	default:
		ATLTRACE2(atlTraceHosting, 0, "CoDTEAddIn does not (yet) support"
			" host %d!\n", m_nHost);
		break;
	} // End switch on host application.

} // End CDTEAddIn::AddCommands.

/**
 * \brief Add our commands to Visual Studio .Net 2003
 *
 * \sa OnConnection, AddCommands, cmdbar_defns
 *
 *
 * \param nMode Reason the host is connecting to us
 *
 * \throw _com_error On non-recoverable COM method failure
 *
 * \pre m_nHost is Host_VS2003
 *
 *
 * Here is where we add commands & command bars to Visual Studio 2003.
 * Each time this method is invoked, it will check the host app for our
 * commands & add them, if they're not there.
 *
 * It may also add a command bar -- see \ref cmdbar_defns "here" for a
 * discussion of how command bars are handled.
 *
 *
 */
 
void CDTEAddIn::AddCommandsVS2003(AddInDO::ext_ConnectMode nMode)
{
	HRESULT hr = S_OK;
	CComPtr<EnvDTE::Commands> pCmds;
	CComPtr<IDispatch> pDisp, pDisp1, pDisp2;
	CComPtr<EnvDTE::Command> pCmdInc, pCmdCfg;

	ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::AddCommandsVS2003: %d\n",
		nMode);

	ATLASSERT(Host_VS2003 == m_nHost);

	// Ok -- the first thing we do is check to see if our commands are
	// defined.  If they're not, we define 'em, no matter what 'nMode'
	// is.  We can find that out through the EnvDTE::Commands interface.
	// We can get that interface from our host:

	if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

	// We can check to see if a given command exists by calling the 'Item'
	// method on the Commands interface with the fully-qualified command
	// name (e.g. "IncVersion.CoAddIn.IncVersion").

	hr = pCmds->Item(CComVariant(CMD_FULL_INCREMENT), -1L, &pCmdInc);

	// If there is no such command, 'Item' will return E_INVALIDARG:

	if (E_INVALIDARG == hr)
	{
		// It's not; this could be because it's the first time we've been
		// loaded, or because the command was removed for some reason.
		// Either way, add it now:
		hr = pCmds->AddNamedCommand(
			m_pAddIn,                           // Us -- this AddIn
			CMD_INCREMENT,                      // Name
			CMD_INCREMENT,						// Button text
			CMD_DSC_INCREMENT,                  // Tooltip
			VARIANT_FALSE,                      // *Not* an MSO button
			IDB_INC16,                          // res ID
			NULL,                               // Context GUIDs
			EnvDTE::vsCommandStatusSupported +  // Disabled flags
			EnvDTE::vsCommandStatusEnabled,
			&pCmdInc);                          // [out] param
	} // End if on no IncVersion command.

	if (FAILED(hr)) throw _com_error(hr);

	// Ok, now do the same for the "Configure" command:
	hr = pCmds->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmdCfg);
	if (E_INVALIDARG == hr)
	{
		// It's not; this could be because it's the first time we've been
		// loaded, or because the command was removed for some reason.
		// Either way, add it now:
		hr = pCmds->AddNamedCommand(
			m_pAddIn,                           // Us-- this AddIn
			CMD_CONFIGURE,                      // Name
			CMD_CONFIGURE,                      // Button text
			CMD_DSC_CONFIGURE,                  // Tooltip
			VARIANT_FALSE,                      // *Not* an MSO button
			IDB_CFG16,                          // Satellite DLL res ID
			NULL,                               // Context GUIDs
			EnvDTE::vsCommandStatusSupported +  // Disabled flags
			EnvDTE::vsCommandStatusEnabled,
			&pCmdCfg);                          // [out] param
	} // End if on no Configure command.

	if (FAILED(hr)) throw _com_error(hr);

	// At this point, we know that our commands are there. We next deal
	// with our command bars...

#if(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_TEMPORARY)

	// We're using temporary command bars.  In this case, we ignore the
	// one time only "UISetup" invocation; we only create our command bar
	// when the AddIn is loaded normally:

	if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
		AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
	{
		ATLTRACE(atlTraceHosting, 2, "Creating a temporary command bar.\n");

		// We'll create a temporary command bar.  Visual Studio 2003 uses
		// the Office Command Bars model.  Specifically, we'll need
		// Office::_CommandBars interface.  We can get one by asking our
		// host...
		hr = m_pApp->get_CommandBars(&pDisp);
		if (FAILED(hr))
		{
			ATLTRACE2(atlTraceHosting, 0, "WARNING: _DTE::get_CommandBars fa"
				"iled with HRESULT 0x%08x! Skipping command bar creati"
				"on.\n", hr);
			return;
		}

		// & QI'ing for the interface we want:
		CComQIPtr<Office::_CommandBars> pBars = pDisp;
		if (NULL == pDisp) throw _com_error(hr); // Something's *really* wrong

		CComVariant vtCmdBarName(CMD_BAR_NAME);

		// At this point, we can add the Command Bar.  First, however, let's
		// be paranoid & see if it already exists:
		CComQIPtr<Office::CommandBar> pBar; // Will hold a reference on our
		// command bar, one way or
		// the other...
		// Just like our commands, we call the 'Item' method,
		hr = pBars->get_Item(vtCmdBarName, &pBar);
		// which will return E_INALIDARG if no such item exists:
		if (E_INVALIDARG == hr)
		{
			hr = pBars->Add(
				vtCmdBarName,                         // Name
				CComVariant((LONG)Office::msoBarTop), // Position
				CComVariant(),                        // Parent
				CComVariant(VARIANT_TRUE),            // Temporary
				&m_pCommandBar);                      // [out] param
			if (FAILED(hr)) throw _com_error(hr);

			// If we're here, we have a brand new Command Bar.  Now we need to
			// add some controls to it; specifically, buttons invoking our
			// commands.
			hr = pCmdInc->AddControl(m_pCommandBar, 1L, &pDisp1);
			if (FAILED(hr)) throw _com_error(hr);
			hr = pCmdCfg->AddControl(m_pCommandBar, 2L, &pDisp2);
			if (FAILED(hr)) throw _com_error(hr);

			m_pCommandBar->put_Visible(VARIANT_TRUE);

		} // End if on non-existent command bar.

		if (FAILED(hr)) throw _com_error(hr);

	} // End if on connect mode

#else // INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_PERMANENT ||
	// INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_SEMIPERMANENT

	// Ok -- we're using permanent command bars.  In this scheme, we'll
	// create them once, when we get the one-time-only startup code
	// "UISetup":
	if (5/*AddInDO::ext_cm_UISetup*/ == nMode)
	{
		pDisp = NULL;
		CComBSTR bstrCmdBarName(CMD_BAR_NAME);

		// Note that this time, we're calling EnvDTE::Commands::AddCommandBar,
		// rather than Office::_CommandBars->Add.
		hr = pCmds->AddCommandBar(
			bstrCmdBarName,                  // Name
			EnvDTE::vsCommandBarTypeToolbar, // Type
			NULL,                            // Parent
			0L,                              // Position
			&pDisp);                         // [out] parameter
		if (FAILED(hr)) throw _com_error(hr);

		// This is irritating: the new Command Bar reference comes back as
		// an IDispatch, but to do anything usefull, we have to QI to
		// Office::CommandBar.
		CComQIPtr<Office::CommandBar> pBar = pDisp;
		if (NULL == pBar) throw _com_error(hr);

		// Ok -- now we've got an Office::CommandBar reference on a new
		// Command Bar, just like in the temporary case.  Just the same, we
		// need to add buttons:
		hr = pCmdInc->AddControl(pBar, 1L, &pDisp1);
		if (FAILED(hr)) throw _com_error(hr);
		hr = pCmdCfg->AddControl(pBar, 2L, &pDisp2);
		if (FAILED(hr)) throw _com_error(hr);

		pBar->put_Visible(VARIANT_TRUE);
	}

#endif // INCVERSION_COMMAND_BAR_STYLE

} // End AddCommandsVS2003.

/**
 * \brief Add our commands to Visual Studio 2005
 *
 * \sa OnConnection, AddCommands, cmdbar_defns
 *
 *
 * \param nMode Reason the host is connecting to us
 *
 * \throw _com_error On non-recoverable COM method failure
 *
 * \pre m_nHost is Host_VS2005
 *
 *
 * Here is where we add commands & command bars to Visual Studio 2005.
 * Each time this method is invoked, it will check the host app for our
 * commands & add them, if they're not there.
 *
 * It may also add a command bar -- see \ref cmdbar_defns "here" for a
 * discussion of how command bars are handled.
 *
 *
 */
 
void CDTEAddIn::AddCommandsVS2005(AddInDO::ext_ConnectMode nMode)
{
	HRESULT hr = S_OK;
	CComPtr<IDispatch> pDisp, pDisp1, pDisp2;
	CComPtr<EnvDTE::Command> pCmdInc, pCmdCfg;

	ATLTRACE2(atlTraceHosting, 4, "CDTEAddIn::AddCommandsVS2005: %d\n",
		nMode);

	ATLASSERT(Host_VS2005 == m_nHost);

	// Ok -- the first thing we do is check to see if our commands are
	// defined.  If they're not, we define 'em, no matter what 'nMode' is.
	// Now, EnvDTE::get_Commands still returns an EnvDTE::Commands
	// interface, but I see that VS 2005 introduced a *new* interface,
	// EnvDTE80::Commands.  I'm not sure which one to use, but better to
	// use the latest & greatest, I guess:

	CComPtr<EnvDTE::Commands> pCmds;
	if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

	CComQIPtr<EnvDTE80::Commands2> pCmds80 = pCmds;
	if (NULL == pCmds80) throw _com_error(E_NOINTERFACE);

	// We can check to see if a given command exists by calling the 'Item'
	// method on the Commands interface with the fully-qualified command
	// name (e.g. "IncVersion.CoAddIn.IncVersion").

	hr = pCmds80->Item(CComVariant(CMD_FULL_INCREMENT),-1L, &pCmdInc);

	// If there is no such command, 'Item' will return E_INVALIDARG:

	if (E_INVALIDARG == hr)
	{
		// It's not; this could be because it's the first time we've been
		// loaded, or because the command was removed for some reason.
		// Either way, add it now:
		hr = pCmds80->AddNamedCommand2(
			m_pAddIn,                            // Us -- the AddIn
			CMD_INCREMENT,                       // Name
			CMD_INCREMENT,                       // Button text
			CMD_DSC_INCREMENT,                   // Tooltip
			VARIANT_FALSE,                       // *Not* an MSO button
			CComVariant(IDB_INC16),              // Satellite DLL res ID
			NULL,                                // Context GUIDs
			EnvDTE::vsCommandStatusSupported +   // Disabled flags
			EnvDTE::vsCommandStatusEnabled,
			EnvDTE80::vsCommandStylePict,		 // Style flags
			EnvDTE80::vsCommandControlTypeButton,// Control Type
			&pCmdInc);                           // [out] param
	} // End if on no Increment command.

	if (FAILED(hr)) throw _com_error(hr);

	// Ok, now do the same for the "Configure" command:
	hr = pCmds80->Item(CComVariant(CMD_FULL_CONFIGURE),-1L, &pCmdCfg);
	if (E_INVALIDARG == hr)
	{
		// It's not; this could be because it's the first time we've been
		// loaded, or because the command was removed for some reason.
		// Either way, add it now:
		hr = pCmds80->AddNamedCommand2(
			m_pAddIn,                            // Us-- the AddIn
			CMD_CONFIGURE,                       // Name
			CMD_CONFIGURE,                       // Button text
			CMD_DSC_CONFIGURE,                   // Tooltip
			VARIANT_FALSE,                       // *Not* an MSO button
			CComVariant(IDB_CFG16),              // Satellite DLL res ID
			NULL,                                // Context GUIDs
			EnvDTE::vsCommandStatusSupported +   // Disabled flags
			EnvDTE::vsCommandStatusEnabled,
			EnvDTE80::vsCommandStylePict,		 // Style flags
			EnvDTE80::vsCommandControlTypeButton,// Control Type
			&pCmdCfg);                           // [out] param
	} // End if on no Configure command.

	if (FAILED(hr)) throw _com_error(hr);

	// At this point, we know that our commands are there. We next deal
	// with our command bars...

#if(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_TEMPORARY)

	// We're using temporary command bars.  In this case, we ignore the
	// one time only "UISetup" invocation; we only create our command bar
	// when the AddIn is loaded normally:

	if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
		AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
	{
		ATLTRACE(atlTraceHosting, 2, "Creating a temporary command bar.\n");

		// We'll create a temporary command bar.  Just like Visual Studio
		// 2003, we begin by asking the app for it's Command Bars
		// collection:
		hr = m_pApp->get_CommandBars(&pDisp);
		if (FAILED(hr))
		{
			ATLTRACE2(atlTraceHosting, 0, "WARNING: _DTE::get_CommandBars fa"
				"iled with HRESULT 0x%08x! Skipping command bar creati"
				"on.\n", hr);
			return;
		}

		// Visual Studio 2005 introduced its own set of new Command Bar
		// interfaces.  Again, I'm not sure if we can just use the old ones.
		CComQIPtr<MSVSCBs::_CommandBars> pBars = pDisp;
		if (NULL == pDisp) throw _com_error(E_NOINTERFACE);

		CComVariant vtCmdBarName(CMD_BAR_NAME);

		// At this point, we can add the Command Bar.  First, however, let's
		// be paranoid & see if it already exists:
		CComQIPtr<MSVSCBs::CommandBar> pBar;
		// Just like our commands, we call the 'Item' method,
		hr = pBars->get_Item(vtCmdBarName, &pBar);
		// which will return E_INALIDARG if no such item exists:
		if (E_INVALIDARG == hr)
		{
			CComVariant vtPosition((LONG)MsoBarPosition::msoBarFloating);
			CComVariant vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);

			hr = pBars->Add(vtCmdBarName,               // Name
				vtPosition,                 // Position
				CComVariant(VARIANT_FALSE), // Menu bar
				CComVariant(VARIANT_TRUE),  // Temporary
				&m_pCommandBar80);          // [out] param
			if (FAILED(hr)) throw _com_error(hr);

			// If we're here, we have a brand new Command Bar.  Now we need to
			// add some controls to it; specifically, buttons invoking our
			// commands.
			hr = pCmdInc->AddControl(m_pCommandBar, 1L, &pDisp1);
			if (FAILED(hr)) throw _com_error(hr);
			hr = pCmdCfg->AddControl(m_pCommandBar, 2L, &pDisp2);
			if (FAILED(hr)) throw _com_error(hr);

			m_pCommandBar80->put_Visible(VARIANT_TRUE);

		} // End if on non-existent command bar.

		if (FAILED(hr)) throw _com_error(hr);

	} // End if on connect mode

#else // INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_PERMANENT ||
	// INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_SEMIPERMANENT

	// If this is the first time the AddIn's being loaded, create a
	// Toolbar, as well.
	if (5/*AddInDO::ext_cm_UISetup*/ == nMode)
	{
		CComBSTR bstrCmdBarName(CMD_BAR_NAME);
		pDisp = NULL;
		hr = pCmds80->AddCommandBar(bstrCmdBarName,
			EnvDTE::vsCommandBarTypeToolbar,
			NULL,
			1L/*MSVSCBs::MsoBarPosition::msoBarTop*/,
			&pDisp);
		if (FAILED(hr)) throw _com_error(hr);

		CComQIPtr<MSVSCBs::CommandBar> pBar = pDisp;
		if (NULL == pBar) throw _com_error(hr);

		hr = pCmdInc->AddControl(pBar, 1L, &pDisp1);
		if (FAILED(hr)) throw _com_error(hr);
		hr = pCmdCfg->AddControl(pBar, 2L, &pDisp2);
		if (FAILED(hr)) throw _com_error(hr);

		pBar->put_Visible(VARIANT_TRUE);
	}

#endif // INCVERSION_COMMAND_BAR_STYLE

} // End AddCommandsVS2005.

void CDTEAddIn::ConfigureInternal()
{
	ATLASSERT(Host_Unknown != m_nHost);

	switch (m_nHost)
	{
	case Host_VS2003:
	case Host_VS2005:
		{
			HRESULT hr = S_OK;
			hr = m_pApp->ExecuteCommand(CComBSTR("Tools.Options"), CComBSTR(""));
			hr;
			break;
		}
	default:                      // Should never be here...
		ATLASSERT(false);
		throw std::logic_error("You didn't keep CDTEAddIn::ConfigureInternal up-to-date with the Host enumeration!");
	}

} // End CDTEAddIn::ConfigureInternal.

Host CDTEAddIn::GuessHostType(IDispatch *pApp)
{
	HRESULT hr = S_OK;

	// Are we being hosted by Visual Studio 2005?  I suspect this will be
	// the most common case.  Check by asking for an ENVDTE80::DTE2
	// interface...
	EnvDTE80::DTE2 *pDTE2Raw;
	hr = pApp->QueryInterface(EnvDTE80::IID_DTE2, (void**)&pDTE2Raw);
	if (SUCCEEDED(hr))
	{
		m_pApp = com_cast<EnvDTE::_DTE>(pApp);
		pDTE2Raw->Release();

		return Host_VS2005;
	}

	// Ok -- maybe it's Visual Studio 2003...
	EnvDTE::_DTE *pDTERaw;
	hr = pApp->QueryInterface(EnvDTE::IID__DTE, (void**)&pDTERaw);
	if (SUCCEEDED(hr))
	{
		m_pApp = pDTERaw;
		return Host_VS2003;
	}

	return Host_Unknown;

} // End GuessHost.

void CDTEAddIn::RemoveCommands(AddInDO::ext_DisconnectMode nMode)
{
	switch (m_nHost)
	{
	case Host_VS2003:
		RemoveCommandsVS2003(nMode);
		break;
	case Host_VS2005:
		RemoveCommandsVS2005(nMode);
		break;
	default:
		ATLTRACE2(atlTraceHosting, 0, "I don't support this host!\n");
	} // End switch on host application.
}

/**
 * \brief Remove our commands from Visual Studio .Net 2003
 *
 *
 * \param nMode Reason indicating why we're being unloaded
 *
 *
 */

void CDTEAddIn::RemoveCommandsVS2003(AddInDO::ext_DisconnectMode nMode)
{
#if(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_TEMPORARY)

	// Temporary command bars are always deleted on unload...
	nMode; // Shutup the compiler
	if (NULL != m_pCommandBar)
	{
		HRESULT hr = m_pCommandBar->Delete();
		if (FAILED(hr))
		{
			ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the IncVersion (temporary) command bar!\n");
		}
	}

#elif(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_PERMANENT)

	// Do nothing -- the commands & command bar will be removed
	// independently at un-install time.
	nMode; // Shutup the compiler

#elif (INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_SEMIPERMANENT)

	// If the user manually unloaded us, remove the command bar as well as
	// our commands:
	if (AddInDO::ext_dm_UserClosed == nMode)
	{
		HRESULT hr = S_OK;

		CComPtr<EnvDTE::Commands> pCmds;
		if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

		// First, remove our toolbar (if it's there):
		CComPtr<IDispatch> pDisp;
		if (FAILED(hr = m_pApp->get_CommandBars(&pDisp))) throw _com_error(hr);

		CComQIPtr<Office::_CommandBars> pBars = pDisp;

		CComPtr<Office::CommandBar> pBar;
		hr = pBars->get_Item(CComVariant(CMD_BAR_NAME), &pBar);
		if (SUCCEEDED(hr))
		{
			hr = pCmds->RemoveCommandBar(pBar);
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the Increment Command Bar.\n");
			}
		}

		// Now, remove our named commands:
		CComPtr<EnvDTE::Command> pCmd;
		hr = pCmds->Item(CComVariant(CMD_FULL_INCREMENT), -1L, &pCmd);
		if (SUCCEEDED(hr))
		{
			hr = pCmd->Delete();
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the IncVersion command.\n");
			}
		}
		hr = pCmds->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmd);

		pCmd = NULL;
		if (SUCCEEDED(hr))
		{
			hr = pCmd->Delete();
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Configure command.\n");
			}
		}

	}

#endif // INCVERSION_COMMAND_BAR_STYLE

} // End RemoveCommandsVS2003.

/**
 * \brief Remove our commands from Visual Studio .Net 2005
 *
 *
 * \param nMode Reason indicating why we're being unloaded
 *
 *
 */

void CDTEAddIn::RemoveCommandsVS2005(AddInDO::ext_DisconnectMode nMode)
{
#if(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_TEMPORARY)

	// Temporary command bars are always deleted on unload...
	nMode; // Shutup the compiler
	if (NULL != m_pCommandBar80)
	{
		HRESULT hr = m_pCommandBar80->Delete();
		if (FAILED(hr))
		{
			ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the IncVersion (temporary) command bar!\n");
		}
	}

#elif(INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_PERMANENT)

	// Do nothing -- the commands & command bar will be removed
	// independently at un-install time.
	nMode; // Shutup the compiler

#elif (INCVERSION_COMMAND_BAR_STYLE == INCVERSION_COMMAND_BAR_SEMIPERMANENT)

	if (AddInDO::ext_dm_UserClosed == nMode)
	{
		HRESULT hr = S_OK;

		CComPtr<EnvDTE::Commands> pCmds;
		if (FAILED(hr = m_pApp->get_Commands(&pCmds))) throw _com_error(hr);

		CComQIPtr<EnvDTE80::Commands2> pCmds2 = pCmds;

		// First, remove our toolbar (if it's there):
		CComPtr<IDispatch> pDisp;
		if (FAILED(hr = m_pApp->get_CommandBars(&pDisp))) throw _com_error(hr);

		CComQIPtr<MSVSCBs::_CommandBars> pBars = pDisp;
		if (NULL == pBars) throw _com_error(hr);

		CComPtr<MSVSCBs::CommandBar> pBar;
		hr = pBars->get_Item(CComVariant(CMD_BAR_NAME), &pBar);
		if (SUCCEEDED(hr))
		{
			hr = pCmds2->RemoveCommandBar(pBar);
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 0, "WARNING: Failed to delete the Increment Command Bar.\n");
			}
		}

		// Now, remove our named commands:
		CComPtr<EnvDTE::Command> pCmd;
		hr = pCmds2->Item(CComVariant(CMD_FULL_INCREMENT), -1L, &pCmd);
		if (SUCCEEDED(hr))
		{
			hr = pCmd->Delete();
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the IncVersion command.\n");
			}
		}

		pCmd = NULL;
		hr = pCmds2->Item(CComVariant(CMD_FULL_CONFIGURE), -1L, &pCmd);
		if (SUCCEEDED(hr))
		{
			hr = pCmd->Delete();
			if (FAILED(hr))
			{
				ATLTRACE2(atlTraceHosting, 1, "WARNING: Failed to delete the Configure command.\n");
			}
		}
	}

#endif // INCVERSION_COMMAND_BAR_STYLE

} // End RemoveCommandsVS2005.

void CDTEAddIn::SinkEvents(AddInDO::ext_ConnectMode nMode)
{
	if (AddInDO::ext_cm_AfterStartup == nMode || // Loaded after app startup
		AddInDO::ext_cm_Startup      == nMode)   // Loaded on app startup
	{
		switch (m_nHost)
		{
		case Host_VS2003:
		case Host_VS2005:
			{
				CComPtr<EnvDTE::Events> pEvents;
				HRESULT hr = m_pApp->get_Events(&pEvents);
				if (FAILED(hr)) throw _com_error(hr);

				hr = pEvents->get_WindowEvents(NULL, &m_pWinEvents);
				if (SUCCEEDED(hr))
				{
					m_objWindowEvents.DispEventAdvise(m_pWinEvents);
				}
				hr = pEvents->get_BuildEvents(&m_pBuildEvents);
				if (SUCCEEDED(hr))
				{
					m_objBuildEvents.DispEventAdvise(m_pBuildEvents);
				}
				break;
			}
		default:
			ATLTRACE2(atlTraceHosting, 0, "CoDTEAddIn does not sink events "
				"for host %d!\n", m_nHost);
		}
	}

} // End CDTEAddIn::SinkEvents.

void CDTEAddIn::UnSinkEvents(AddInDO::ext_DisconnectMode /*nMode*/)
{
	switch (m_nHost)
	{
	case Host_VS2003:
	case Host_VS2005:
		if (m_pWinEvents)
		{
			m_objWindowEvents.DispEventUnadvise(m_pWinEvents);
			m_pWinEvents = NULL;
		}
		if (m_pBuildEvents)
		{
			m_objBuildEvents.DispEventUnadvise(m_pBuildEvents);
			m_pBuildEvents = NULL;
		}
		break;
	}
} // End CDTEAddIn::UnSinkEvents.

// Local Variables:
// fill-column: 72
// indent-tabs-mode: nil
// show-trailing-whitespace: t
// End:

// DTEAddIn.cpp ends here.

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Ok, it's about time I updated this profile. I still live near 'Beastly' Eastleigh in Hampshire, England. However I have recently been granted a permamant migration visa to Australia - so if you're a potential employer from down under and like the look of me, please get in touch.
Still married - just, still with just a son and daughter. But they are now 8 and 7 resp and when together they have the energy of a nuclear bomb.
I worked at Teleca UK for over 8.5 years (but have now moved to TikitTFB) and have done loads of different things. Heavily involved with MFC, SQL, C#, The latest is ASP.NET with C# and Javascript. Moving away from Trolltech Qt3 and 4.
Jordan.

Comments and Discussions