Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / MFC

Resource ID Organiser Add-In for Visual C++ 5.0/6.0/.NET

Rate me:
Please Sign up or sign in to vote.
4.98/5 (71 votes)
10 Jan 2005CPOL25 min read 528.2K   12.1K   201  
An application/add-in to organise and renumber resource symbol IDs
/************************************************************************
 *
 *            Resource ID Organiser Add-In for Visual C++ .NET
 *
 * (c) Copyright 2000-2005 by Riverblade Limited (UK). All rights reserved.
 *
 ************************************************************************
 *
 *  Description : CConnect class - primary interface to Visual Studio .NET
 *
 *  Compiler    : Microsoft Visual C++ .NET 2003
 *                                                                       
 *  Target                                                               
 *  Environment : Windows 2000/XP
 *
 *  NOTE:
 *
 *    This software is provided "as is". All title and copyrights in and
 *    to the software, including but not limited to any images, text etc.
 *    etc. incorporated into it, are the property of Anna-Jayne Metcalfe
 *    and Riverblade Limited, except where acknowledged otherwise.
 *
 *    Your may freely use this code in your own products, PROVIDED
 *    this notice is not removed or modified.
 *
 *    Please visit http://www.riverblade.co.uk/products/resorg or email 
 *    support@riverblade.co.uk for latest updates and product support 
 *
 ************************************************************************
 *
 *   MODIFICATION HISTORY:
 *
 *           This is a controlled document. See project configuration
 *           control tool for latest version and full version history.
 *
 *    $Archive: /Projects/AddIns/ResOrg/ResOrgNETAddIn/Connect.cpp $
 *   $Revision: 20 $
 *       $Date: 31/12/04 16:28 $
 *     $Author: Anna $
 *
 *    $History: Connect.cpp $
 * 
 * *****************  Version 20  *****************
 * User: Anna         Date: 31/12/04   Time: 16:28
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Menu commands now have the icons displayed correctly
 * 2.  Added a separator before the "About ResOrg..." command
 * 
 * *****************  Version 19  *****************
 * User: Anna         Date: 28/12/04   Time: 9:40
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Added satellite DLL
 * 2.  Added icon to toolwindow
 * 3.  Minor mods for stability
 * 4.  Updated banner
 * 
 * 
 * *****************  Version 18  *****************
 * User: Anna         Date: 20/12/04   Time: 10:13
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Changed the initialisation sequence to attempt to make it more
 * reliable
 * 2.  Added event unadvise during OnDisconnection()
 * 3.  Added icon to the toolwindow
 * 
 * *****************  Version 17  *****************
 * User: Anna         Date: 19/03/03   Time: 18:29
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * CConnect::OnConnection() now shows a warning message if it fails to
 * configure commands
 * 
 * *****************  Version 16  *****************
 * User: Anna         Date: 25/11/02   Time: 15:27
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Changed website address in banner
 * 
 * *****************  Version 15  *****************
 * User: Anna         Date: 22/10/02   Time: 14:15
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Changed name/email address (at last!)
 * 
 * *****************  Version 14  *****************
 * User: Andy         Date: 13/08/02   Time: 12:48
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * CConnect::OnConnection() no longer recreates the commands etc. in
 * release builds if the connection mode is
 * AddInDesignerObjects::ext_cm_AfterStartup.
 * 
 * This prevents a crash if the add-in is loaded as a result of invoking a
 * command installed previously. Note that to facilitate testing, the
 * debug build still does this...
 * 
 * *****************  Version 13  *****************
 * User: Andy         Date: 10/08/02   Time: 22:46
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Fixed a compiler warning
 * 
 * *****************  Version 12  *****************
 * User: Andy         Date: 10/08/02   Time: 22:21
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Fixed OnConnection() code...commands are now only setup if the
 * add-in has just been installed or manually reloaded
 * 2.  Tidied up the code
 * 
 * *****************  Version 11  *****************
 * User: Andy         Date: 10/08/02   Time: 0:16
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Added groups to the toolbar (to make it wider so the caption would
 * fit!)
 * 2.  Fixed a window parenting problem when the main window of ResOrg was
 * opened on an expired version
 * 
 * *****************  Version 10  *****************
 * User: Andy         Date: 9/08/02    Time: 17:02
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Added "Options" and "AboutBox" commands
 * 
 * *****************  Version 9  *****************
 * User: Andy         Date: 8/08/02    Time: 20:36
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Added (debug only) code to retrieve the pathname of the satellite
 * DLL...so I can be sure I'm not really going mad here
 * 
 * *****************  Version 8  *****************
 * User: Andy         Date: 7/08/02    Time: 22:15
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Improved the way menu commands are added
 * 
 * *****************  Version 7  *****************
 * User: Andy         Date: 6/08/02    Time: 10:36
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Removed unused code
 * 
 * *****************  Version 6  *****************
 * User: Andy         Date: 5/08/02    Time: 13:49
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * 1.  Moved menu entries to a submenu under the Tools menu
 * 2.  Tidied up the code a little
 * 
 * *****************  Version 5  *****************
 * User: Andy         Date: 1/08/02    Time: 16:44
 * Updated in $/Projects/AddIns/ResOrg/ResOrgNETAddIn
 * Further development of ResOrg.NET - particularly the tool window
 * 
 * *****************  Version 4  *****************
 * User: Andy         Date: 22/06/02   Time: 0:36
 * Updated in $/Projects/AddIns/ResOrg/ResOrgAddInVc7
 * Minor tidying up
 * 
 * *****************  Version 3  *****************
 * User: Andy         Date: 13/06/02   Time: 17:02
 * Updated in $/Projects/AddIns/ResOrg/ResOrgAddInVc7
 * Removed compiler warnings and changed ProgID string from
 * "ResOrgAddInVc7.Connect" to "ResOrgAddInVc7"
 * 
 * *****************  Version 2  *****************
 * User: Andy         Date: 7/06/02    Time: 22:37
 * Updated in $/Projects/AddIns/ResOrg/ResOrgAddInVc7
 * Initial integration
 * 
 * *****************  Version 1  *****************
 * User: Andy         Date: 3/06/02    Time: 16:53
 * Created in $/Projects/AddIns/ResOrg/ResOrgAddInVc7
 *
 * $Nokeywords: $
 *
 ************************************************************************/

#include "StdAfx.h"

#include "AddIn.h"
#include "ResOrgAddInApp.h"
#include "MainFrame.h"

#include "Connect.h"



/////////////////////////////////////////////////////////////////////////////
// Support functions

HPALETTE CreateDIBPalette (LPBITMAPINFO lpbmi, LPINT lpiNumColors)
{
	LPBITMAPINFOHEADER  lpbi;
	LPLOGPALETTE		lpPal;
	HANDLE				hLogPal;
	HPALETTE			hPal = NULL;
	int					i;

	lpbi = (LPBITMAPINFOHEADER)lpbmi;

	if (lpbi->biBitCount <= 8)
		*lpiNumColors = (1 << lpbi->biBitCount);
	else
		*lpiNumColors = 0;  // No palette needed for 24 BPP DIB

	if (lpbi->biClrUsed > 0)
		*lpiNumColors = lpbi->biClrUsed;  // Use biClrUsed

	if (*lpiNumColors)
	{
		hLogPal = GlobalAlloc(GHND, sizeof (LOGPALETTE) + sizeof (PALETTEENTRY) * (*lpiNumColors) );
		lpPal = (LPLOGPALETTE) GlobalLock (hLogPal);
		lpPal->palVersion    = 0x300;
		lpPal->palNumEntries = *lpiNumColors;

		for (i = 0;  i < *lpiNumColors;  i++)
		{
			lpPal->palPalEntry[i].peRed   = lpbmi->bmiColors[i].rgbRed;
			lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;
			lpPal->palPalEntry[i].peBlue  = lpbmi->bmiColors[i].rgbBlue;
			lpPal->palPalEntry[i].peFlags = 0;
		}
		hPal = CreatePalette(lpPal);

		GlobalUnlock(hLogPal);
		GlobalFree(hLogPal);

	}
	return hPal;
}


HBITMAP LoadResourceBitmap(HINSTANCE hInstance, LPSTR lpString, HPALETTE FAR* lphPalette)
{
	HRSRC  hRsrc;
	HGLOBAL hGlobal;
	HBITMAP hBitmapFinal = NULL;
	LPBITMAPINFOHEADER  lpbi;
	HDC hdc;
	int iNumColors;

	if (hRsrc = FindResource(hInstance, lpString, RT_BITMAP) )
	{
		hGlobal = LoadResource(hInstance, hRsrc);

		lpbi = (LPBITMAPINFOHEADER)LockResource(hGlobal);

		hdc = GetDC(NULL);

		*lphPalette =  CreateDIBPalette ((LPBITMAPINFO)lpbi, &iNumColors);
		if (*lphPalette)
		{
			SelectPalette(hdc,*lphPalette,FALSE);
			RealizePalette(hdc);
		}

		hBitmapFinal = CreateDIBitmap(	hdc,
										(LPBITMAPINFOHEADER)lpbi,
										(LONG)CBM_INIT,
										(LPSTR)lpbi + lpbi->biSize + iNumColors *

		sizeof(RGBQUAD), (LPBITMAPINFO)lpbi, DIB_RGB_COLORS );

		ReleaseDC(NULL,hdc);

		UnlockResource(hGlobal);
		FreeResource(hGlobal);
	}
	return (hBitmapFinal);
}


//	CVc7AutomationHelper hides away the grot of talking to the COM interfaces
//	exposed by Visual Studio...
CVc7AutomationHelper Workspace;


extern CAddInModule _AtlModule;


CConnect::CConnect(void)
{
	m_hwndResOrg = NULL;
}


CConnect::~CConnect(void)
{
}


// When run, the Add-in wizard prepared the registry for the Add-in.
// At a later time, the Add-in or its commands may become unavailable for reasons such as:
//
//   1) You moved this project to a computer other than which is was originally created on.
//   2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
//   3) You add new commands or modify commands already defined.
//
// You will need to re-register the Add-in by building the ResOrgNETAddInSetup project,
// right-clicking the project in the Solution Explorer, and then choosing install.
// Alternatively, you could execute the ReCreateCommands.reg file the Add-in Wizard generated in 
// the project directory, or run 'devenv /setup' from a command prompt.


// CConnect
STDMETHODIMP CConnect::OnConnection(IDispatch* pApplication,
									AddInDesignerObjects::ext_ConnectMode eConnectMode,
									IDispatch* pAddInInst,
									SAFEARRAY** ppCustom)
{
	UNREFERENCED_PARAMETER(ppCustom);

	pApplication->QueryInterface(__uuidof(EnvDTE::_DTE), (LPVOID*)&m_pDTE);

	pAddInInst->QueryInterface(__uuidof(EnvDTE::AddIn), (LPVOID*)&m_pAddInInstance);

	Workspace.SetDTE(m_pDTE);

	// Initialise the document manager and create the mainframe
	ConnectApp();

	if (SUCCEEDED( CreateMainToolWindow(pApplication) ) )
	{
		ASSERT(m_pwndMain != NULL);
		m_pwndMain->put_Visible(VARIANT_TRUE);
	}

	switch ( (int)eConnectMode)
	{
		case 0:		// AddInDesignerObjects::ext_cm_AfterStartup
#ifdef _DEBUG
			// AJM 13.8.2002 in the debug build we remove and reconfigure our commands when
			// the user loads the add-in explicitly in the add-in manager for ease of testing
			//
			// Since this will cause the IDE to crash if the add-in is loaded as a result of
			// activating an existing ResOrg.NET command, this is NOT done in the release version!
			if (FAILED(ConfigureCommands() ) )
			{
				::AfxMessageBox(IDP_ADDIN_CONFIGURE_CMD_FAILED, MB_OK | MB_ICONEXCLAMATION);
			}
#endif
			break;

		case 5:		// AddInDesignerObjects::ext_cm_UISetup
			if (FAILED(ConfigureCommands() ) )
			{
				::AfxMessageBox(IDP_ADDIN_CONFIGURE_CMD_FAILED, MB_OK | MB_ICONEXCLAMATION);
			}
			break;

		case 1:		// AddInDesignerObjects::ext_cm_Startup
		case 2:		// AddInDesignerObjects::ext_cm_External
		case 3:		// AddInDesignerObjects::ext_cm_CommandLine
		case 4:		// AddInDesignerObjects::ext_cm_Solution
		default:
			break;
	}

	ConfigureEvents();

	return S_OK;
}


STDMETHODIMP CConnect::OnDisconnection(AddInDesignerObjects::ext_DisconnectMode eRemoveMode,
									   SAFEARRAY** ppCustom)
{
	UNREFERENCED_PARAMETER(eRemoveMode);
	UNREFERENCED_PARAMETER(ppCustom);

	UnadviseEvents();

	Workspace.SetDTE(NULL);

	DisconnectApp();

	m_pDTE = NULL;

	return S_OK;
}


STDMETHODIMP CConnect::OnAddInsUpdate(SAFEARRAY** ppCustom)
{
	UNREFERENCED_PARAMETER(ppCustom);

	return S_OK;
}


STDMETHODIMP CConnect::OnStartupComplete(SAFEARRAY** ppCustom)
{
	UNREFERENCED_PARAMETER(ppCustom);

	return S_OK;
}


STDMETHODIMP CConnect::OnBeginShutdown(SAFEARRAY** ppCustom)
{
	UNREFERENCED_PARAMETER(ppCustom);

	return S_OK;
}


STDMETHODIMP CConnect::QueryStatus(BSTR bstrCmdName,
								   EnvDTE::vsCommandStatusTextWanted eNeededText,
								   EnvDTE::vsCommandStatus* pStatusOption,
								   VARIANT* pvarCommandText)
{
	UNREFERENCED_PARAMETER(pvarCommandText);

	if (eNeededText == EnvDTE::vsCommandStatusTextWantedNone)
	{
		if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.Open") )
		{
			*pStatusOption = (EnvDTE::vsCommandStatus)(EnvDTE::vsCommandStatusEnabled + EnvDTE::vsCommandStatusSupported);
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.Options") )
		{
			*pStatusOption = (EnvDTE::vsCommandStatus)(EnvDTE::vsCommandStatusEnabled + EnvDTE::vsCommandStatusSupported);
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.AboutBox") )
		{
			*pStatusOption = (EnvDTE::vsCommandStatus)(EnvDTE::vsCommandStatusEnabled + EnvDTE::vsCommandStatusSupported);
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.ViewToolWin") )
		{
			QueryStatusToggleViewToolWinCmd(eNeededText,
											pStatusOption,
											pvarCommandText);
		}
	}
	return S_OK;
}


void CConnect::QueryStatusToggleViewToolWinCmd(	EnvDTE::vsCommandStatusTextWanted eNeededText,
												EnvDTE::vsCommandStatus* pStatusOption,
												VARIANT* pvarCommandText)
{
	UNREFERENCED_PARAMETER(eNeededText);
	UNREFERENCED_PARAMETER(pvarCommandText);

	if (m_pwndMain != NULL)
	{
		VARIANT_BOOL bVisible = VARIANT_FALSE;
		m_pwndMain->get_Visible(&bVisible);

		if (bVisible)
		{
			*pStatusOption = (EnvDTE::vsCommandStatus)( EnvDTE::vsCommandStatusEnabled +
														EnvDTE::vsCommandStatusSupported + 
														EnvDTE::vsCommandStatusLatched);
		}
		else
		{
			*pStatusOption = (EnvDTE::vsCommandStatus)(	EnvDTE::vsCommandStatusEnabled +
														EnvDTE::vsCommandStatusSupported);
		}
	}
	else
	{
		*pStatusOption = (EnvDTE::vsCommandStatus)(	EnvDTE::vsCommandStatusUnsupported);
	}
}


STDMETHODIMP CConnect::Exec(BSTR bstrCmdName,
							EnvDTE::vsCommandExecOption eExecuteOption,
							VARIANT* pvarVariantIn,
							VARIANT* pvarVariantOut,
							VARIANT_BOOL* pvbHandled)
{
	UNREFERENCED_PARAMETER(pvarVariantIn);
	UNREFERENCED_PARAMETER(pvarVariantOut);

	*pvbHandled = VARIANT_FALSE;

	if (EnvDTE::vsCommandExecOptionDoDefault == eExecuteOption)
	{
		if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.Open") )
		{
			OpenResOrgMainWindow();

			*pvbHandled = VARIANT_TRUE;

			return S_OK;
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.ViewToolWin") )
		{
			ToggleProjectViewCmd();
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.Options") )
		{
			DoOptionsCmd();

			*pvbHandled = VARIANT_TRUE;

			return S_OK;
		}
		else if (!_wcsicmp(bstrCmdName, L"ResOrgNETAddIn.AboutBox") )
		{
			DoAboutBoxCmd();

			*pvbHandled = VARIANT_TRUE;

			return S_OK;
		}
	}
	return S_OK;
}


HRESULT CConnect::FinalConstruct(void)
{
	return S_OK;
}
	

void CConnect::FinalRelease(void)
{
}


/////////////////////////////////////////////////////////////////////////////
// CConnect implementation

void CConnect::ConnectApp(void)
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	::GetApp()->OnConnection();

	m_hwndResOrg = ::AfxGetMainWnd()->GetSafeHwnd();
}


void CConnect::DisconnectApp(void)
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	::GetApp()->OnDisconnection();
}


HRESULT CConnect::ToggleProjectViewCmd(void)
{
	VARIANT_BOOL bVisible = VARIANT_FALSE;

	m_pwndMain->get_Visible(&bVisible);

	m_pwndMain->put_Visible(!bVisible);

	return S_OK;
}


// Use with care - this will fail if Visual Studio is not the active window
HWND CConnect::FindVisualStudioMainWnd(void) const
{
	HWND hWndDevStudio	= NULL;

	HWND hWndDesktop	= ::GetDesktopWindow();
	HWND hWnd			= ::GetActiveWindow();

    while ( (NULL != hWnd) && (hWnd != hWndDesktop) )
    {
        hWndDevStudio	= hWnd;
        hWnd			= ::GetParent(hWnd);
    }
	return hWndDevStudio;
}


const UINT WM_RESORG_OPEN = ::RegisterWindowMessage( _T("ResOrgShowMainWnd") );

HRESULT CConnect::OpenResOrgMainWindow(void) 
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	if (::IsWindow(m_hwndResOrg) )
	{
		#ifdef _RESORG_EXPIRY_DATE
			// Because this add-in is implemented as a Regular DLL with a
			// mainframe window the usual trick of calling EnableModeless()
			// isn't enough to ensure that modal dialogs behave correctly -
			// we also have to ensure that any dialogs are parented by
			// DevStudio's main window rather than our own...
			HWND hWndDevStudio = FindVisualStudioMainWnd();

			CWnd* pWnd = CWnd::FromHandle(hWndDevStudio);

			::DoVersionExpiryCheck(pWnd);
		#endif

		::SendMessage(m_hwndResOrg, WM_RESORG_OPEN,	0, (LPARAM)0);

		return S_OK;
	}
	return E_FAIL;
}


HRESULT CConnect::DoOptionsCmd(void)
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	// Because this add-in is implemented as a Regular DLL with a
	// mainframe window the usual trick of calling EnableModeless()
	// isn't enough to ensure that modal dialogs behave correctly -
	// we also have to ensure that any dialogs are parented by
	// DevStudio's main window rather than our own...
	HWND hWndDevStudio = FindVisualStudioMainWnd();

	CWnd* pWnd = CWnd::FromHandle(hWndDevStudio);

	Options.Configure(pWnd);

	return S_OK;
}


HRESULT CConnect::DoAboutBoxCmd(void)
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	// Because this add-in is implemented as a Regular DLL with a
	// mainframe window the usual trick of calling EnableModeless()
	// isn't enough to ensure that modal dialogs behave correctly -
	// we also have to ensure that any dialogs are parented by
	// DevStudio's main window rather than our own...
	HWND hWndDevStudio = FindVisualStudioMainWnd();

	CWnd* pWnd = CWnd::FromHandle(hWndDevStudio);
	
	CResOrgAboutBox aboutDlg(pWnd);

	aboutDlg.SetModuleState( ::AfxGetStaticModuleState() );
	aboutDlg.DoModal();

	return S_OK;
}


HRESULT CConnect::ConfigureEvents(void)
{
	HRESULT hr = S_OK;
	{
		CComPtr<EnvDTE::Events> pEvents;
		CComPtr<EnvDTE::_SolutionEvents> pSolutionEvents;

		if (SUCCEEDED(m_pDTE->get_Events(&pEvents) ) )
		{
			if (SUCCEEDED(pEvents->get_SolutionEvents(&pSolutionEvents) ) )
			{
				m_SolutionEventsSink.DispEventAdvise( (IUnknown*)pSolutionEvents.p);

				// Tell our event sink where the main tool window lives
				m_SolutionEventsSink.Initialise(m_pwndMainCtrl);

				hr = S_OK;
			}
		}
	}
	return hr;
}


HRESULT CConnect::UnadviseEvents(void)
{
	HRESULT hr = S_OK;
	{
		CComPtr<EnvDTE::Events> pEvents;
		CComPtr<EnvDTE::_SolutionEvents> pSolutionEvents;

		if (SUCCEEDED(m_pDTE->get_Events(&pEvents) ) )
		{
			if (SUCCEEDED(pEvents->get_SolutionEvents(&pSolutionEvents) ) )
			{
				m_SolutionEventsSink.DispEventUnadvise( (IUnknown*)pSolutionEvents.p);

				//pSolutionEvents = NULL;

				hr = S_OK;
			}
		}
	}
	return hr;
}


HRESULT CConnect::RemoveCommands(void)
{
	HRESULT hr = S_OK;

    CComPtr<EnvDTE::Commands> pCommands;
	hr = m_pDTE->get_Commands(&pCommands);
	if (FAILED(hr) )
	{
		return hr;
	}

    CComPtr<Office::_CommandBars> pCommandBars;
    hr = m_pDTE->get_CommandBars(&pCommandBars);
	if (FAILED(hr) )
	{
		return hr;
	}

    // Find and remove (if found) our menu bar & toolbar
    CComPtr<Office::CommandBar> pResOrgMenuBar;
    CComPtr<Office::CommandBar> pResOrgToolBar;

	if (SUCCEEDED(pCommandBars->get_Item(CComVariant("Resource ID Organiser .NET"), &pResOrgMenuBar) ) )
	{
        pCommands->RemoveCommandBar(pResOrgMenuBar);
	}
    if (SUCCEEDED(pCommandBars->get_Item(CComVariant("ResOrg.NET"), &pResOrgToolBar) ) )
	{
        pCommands->RemoveCommandBar(pResOrgToolBar);
	}

    // Find and remove (if found) our named commands
    CComPtr<EnvDTE::Command> pCommand;
    pCommands->Item( CComVariant("ResOrgNETAddIn.Open"), 0, &pCommand);

	if (pCommand != NULL)
    {
        pCommand->Delete();
		pCommand = NULL;
       // pCommand.Release();
    }

    pCommands->Item(CComVariant("ResOrgNETAddIn.ViewToolWin"), 0, &pCommand);
    if (pCommand != NULL)
	{
		pCommand->Delete();
		pCommand = NULL;
    //    pCommand.Release();
	}

    pCommands->Item(CComVariant("ResOrgNETAddIn.Options"), 0, &pCommand);
    if (pCommand != NULL)
	{
		pCommand->Delete();
		pCommand = NULL;
  //      pCommand.Release();
	}

    pCommands->Item(CComVariant("ResOrgNETAddIn.AboutBox"), 0, &pCommand);
    if (pCommand != NULL)
	{
		pCommand->Delete();
		pCommand = NULL;
//        pCommand.Release();
	}
	return hr;
}


// Load a newline delimited specification for a command
bool CConnect::LoadCommandSpec(	UINT uResourceID,
								CString& rsCmd,
								CString& rsButtonText,
								CString& rsToolTip,
								CString& rsKeyBinding,
								BOOL& rbHasBitmap) const
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	CString sSpecification;
	sSpecification.LoadString(uResourceID);

	if (!sSpecification.IsEmpty() )
	{
		rsCmd			= ::Chomp( sSpecification,_T("\n") );
		rsButtonText	= ::Chomp( sSpecification,_T("\n") );
		rsToolTip		= ::Chomp( sSpecification,_T("\n") );
		rsKeyBinding	= ::Chomp( sSpecification,_T("\n") );

		// Work out whether this command has a bitmap icon
		CBitmap bitmap;
		rbHasBitmap = bitmap.LoadBitmap(uResourceID);

		return !rsCmd.IsEmpty();
	}
	return false;
}
  

HRESULT CConnect::AddCommand(CComPtrBase<EnvDTE::Commands>& pCommands, 
							 CComPtr<EnvDTE::Command>& rpCommand,
							 UINT uCmdSpecID,
                             bool bEnabled)
{
    CString sCmd, sButtonText, sToolTip, sKeyBinding;

	BOOL bHasBitmap = FALSE;

	if (!LoadCommandSpec(	uCmdSpecID,
							sCmd,
							sButtonText,
							sToolTip,
							sKeyBinding,
							bHasBitmap) )
	{
		return E_FAIL;
	}

	LONG lStatus = bEnabled ? EnvDTE::vsCommandStatusSupported | EnvDTE::vsCommandStatusEnabled :
							  EnvDTE::vsCommandStatusSupported;

	bHasBitmap = true;

	// Add the command...note that I haven't got the bitmaps to appear yet. No idea why
    HRESULT hr = pCommands->AddNamedCommand(m_pAddInInstance,
											CComBSTR(sCmd),
											CComBSTR(sButtonText),
											CComBSTR(sToolTip),
											bHasBitmap ? VARIANT_FALSE : VARIANT_TRUE,
											bHasBitmap ? uCmdSpecID : 0,
											NULL,
											lStatus,
											&rpCommand);
	if (FAILED(hr) )
	{
		// Failed to add command
		ASSERT(FALSE);
		return hr;
	}

    if (!sKeyBinding.IsEmpty() )
	{
		rpCommand->put_Bindings(CComVariant(sKeyBinding));
	}

	return hr;
}


HRESULT CConnect::AddCommandToCommandBar(CComPtrBase<Office::CommandBar>& pCommandBar,
										 CComPtr<EnvDTE::Command> pCommand,
										 LONG lPos,
										 bool bBeginGroupBefore /*= false*/)
{
	CComPtr<Office::CommandBarControl> pControl;
	HRESULT hr = pCommand->AddControl(pCommandBar, lPos, &pControl);

	if (FAILED(hr) )
	{
		// Failed to add command
		ASSERT(FALSE);
		return hr;
	}

	if (bBeginGroupBefore)
	{
		pControl->put_BeginGroup(VARIANT_TRUE);
	}
    CComQIPtr<Office::_CommandBarButton> pButton(pControl);
    if (pButton != NULL)
	{
		pButton->put_Style(Office::msoButtonAutomatic);
	}
    return S_OK;
}


HRESULT CConnect::ConfigureCommands(void) 
{
	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	RemoveCommands();

	HRESULT hr = E_FAIL;
	{
		CComPtr<EnvDTE::Commands> pCommands;
		CComPtr<Office::_CommandBars> pCommandBars;
		CComPtr<Office::CommandBar> pToolsMenuBar;

		IfFailGoCheck(m_pDTE->get_Commands(&pCommands),			pCommands);
		IfFailGoCheck(m_pDTE->get_CommandBars(&pCommandBars),	pCommandBars);

		// Add the commands
		CComPtr<EnvDTE::Command> pOpenCmd;
		CComPtr<EnvDTE::Command> pToggleToolwinCmd;
		CComPtr<EnvDTE::Command> pOptionsCmd;
		CComPtr<EnvDTE::Command> pAboutBoxCmd;

		HRESULT hrAddOpenCmd = AddCommand(	pCommands, 
											pOpenCmd,
											IDR_OPEN_RESORG_CMD,
											false);

		HRESULT hrAddToggleCmd = AddCommand(pCommands, 
											pToggleToolwinCmd,
											IDR_VIEW_MAIN_TOOLWIN_CMD,
											false);

		HRESULT hrOptionsCmd = AddCommand(	pCommands, 
											pOptionsCmd,
											IDR_OPTIONS_CMD,
											false);

		HRESULT hrAboutBoxCmd = AddCommand(	pCommands, 
											pAboutBoxCmd,
											IDR_ABOUTBOX_CMD,
											false);

		// Create the toolbar...
		CComQIPtr<Office::CommandBar> pResOrgToolBar;
		IfFailGoCheck(pCommands->AddCommandBar(	CComBSTR("ResOrg.NET"),
												EnvDTE::vsCommandBarTypeToolbar,
												NULL,
												0,
												(IDispatch**)&pResOrgToolBar), pResOrgToolBar);

		//...and menu bar
		CComPtr<Office::CommandBar> pResOrgMenuBar;

		// Add a submenu to the "Tools" menu
		CString sTitle;
		VERIFY( sTitle.LoadString(IDS_ADDIN_MENU_NAME) );

		IfFailGoCheck(pCommandBars->get_Item(	CComVariant(L"Tools"),
												&pToolsMenuBar), pToolsMenuBar);

		IfFailGoCheck(pCommands->AddCommandBar(	CComBSTR(sTitle),
												EnvDTE::vsCommandBarTypeMenu,
												pToolsMenuBar,
												1,
												(IDispatch**)&pResOrgMenuBar), pResOrgMenuBar);

		// Now add the commands to the toolbar & menubar
		AddCommandToCommandBar(	pResOrgToolBar,
								pOpenCmd,
								1);

		AddCommandToCommandBar(	pResOrgToolBar,
								pToggleToolwinCmd,
								2,
								true);

		AddCommandToCommandBar(	pResOrgToolBar,
								pOptionsCmd,
								3);

		AddCommandToCommandBar(	pResOrgToolBar,
								pAboutBoxCmd,
								4,
								true);

		AddCommandToCommandBar(	pResOrgMenuBar,
								pOpenCmd,
								1);

		AddCommandToCommandBar(	pResOrgMenuBar,
								pToggleToolwinCmd,
								2);

		AddCommandToCommandBar(	pResOrgMenuBar,
								pOptionsCmd,
								3);

		AddCommandToCommandBar(	pResOrgMenuBar,
								pAboutBoxCmd,
								4,
								true);

		pResOrgToolBar->put_Visible(VARIANT_TRUE);

		hr = (SUCCEEDED(hrAddOpenCmd) && SUCCEEDED(hrAddToggleCmd) ) ? S_OK : E_FAIL;
	}
Error:
	return hr;
}


HRESULT CConnect::CreateMainToolWindow(IDispatch* pApplication) 
{
	USES_CONVERSION;

	AFX_MANAGE_STATE( ::AfxGetStaticModuleState() );

	CComPtr<EnvDTE::Windows> pWindows;

	m_pDTE->get_Windows(&pWindows);

	// First create the window using Visual Studio's Automation Interface...
	HRESULT hr = pWindows->CreateToolWindow(m_pAddInInstance,
											CComBSTR("RESORGNETADDINTO.ResOrgNETAddInToCtrl.1"),
											CComBSTR("ResOrg.NET"),
											CComBSTR("{7542A9D7-23A4-4ADD-BD8B-9BF76BBD82D6}"),
											&m_pwndMainCtrl,
											&m_pwndMain);

	if (FAILED(hr) )
	{
		::AfxMessageBox(_T("Unable to create ResOrg.NET tool window. The add-in may be incorrectly installed"),
						MB_OK | MB_ICONSTOP);

		return hr;
	}
	
	CComBSTR bsSatelliteDll;
	m_pAddInInstance->get_SatelliteDllPath(&bsSatelliteDll.m_str);

	//TRACE( _T("ResOrg.NET Satellite DLL path: %s\n"), OLE2T(bsSatelliteDll.m_str) );
	HMODULE hModule = ::LoadLibrary( OLE2T(bsSatelliteDll.m_str) );

	CComVariant varPic;

	CComPtr<IPictureDisp> pPictureDisp;

	PICTDESC pd;

    pd.cbSizeofstruct = sizeof(PICTDESC);
    pd.picType = PICTYPE_BITMAP;
	
	pd.bmp.hbitmap = LoadResourceBitmap(hModule,
										MAKEINTRESOURCE(IDR_VIEW_MAIN_TOOLWIN_CMD),
										&pd.bmp.hpal ) ;

	OleCreatePictureIndirect(	&pd,
								IID_IPictureDisp,
								FALSE,
								(LPVOID*)&pPictureDisp);

    pPictureDisp->QueryInterface(IID_IUnknown, (LPVOID*)&varPic.punkVal ) ;
    varPic.vt = VT_UNKNOWN;

    m_pwndMain->SetTabPicture(varPic) ;

	::FreeLibrary(hModule);

	//...and now we need to configure it. The ActiveX Control within the window
	// has an Initialise DISP method, and all we need to do is pass the IDispatch
	// pointer to the application to it (from which it will find the DTE interface)
	//
	// Until I tried to get this working I had NO idea how much of a bodge ActiveX/IDispatch
	// is - it took me ages to get this working!
	//
	// First, obtain the DISPID of the property/method.
	OLECHAR FAR* szName = A2W("Initialise");

	DISPID dispid;

	hr = m_pwndMainCtrl->GetIDsOfNames(	IID_NULL,
										&szName,
										1,
										LOCALE_SYSTEM_DEFAULT,
										&dispid);

	if (FAILED(hr) )
	{
		ASSERT(FALSE);			// Very, very bad - and should never happen
		return hr;
	}
	// This is the bit that really got me - how much work it is to make a simple
	// method call. Roll on .NET.....
	VARIANTARG args[2];
	memset(args, 0, sizeof args);

	args[1].vt				= VT_DISPATCH;
	args[1].pdispVal		= pApplication;
	args[0].vt				= VT_UI4;
	args[0].ulVal			= (unsigned long)m_hwndResOrg;

	DISPPARAMS params;
	memset(&params, 0, sizeof params);

	params.rgvarg		= &(args[0]);
	params.cArgs		= 2;
	params.cNamedArgs	= 0;

	VARIANT varResult;

	hr = m_pwndMainCtrl->Invoke(dispid,
								IID_NULL,
								LOCALE_SYSTEM_DEFAULT,
								DISPATCH_METHOD,
								&params,
								&varResult,
								NULL,
								NULL);

	return hr;
}


HRESULT CConnect::RefreshMainToolWindow(void)
{
	return m_SolutionEventsSink.RefreshMainToolWindow();
}


// This is a bit of a bodge...since the event handler needs to call this it makes
// sense to make it a member of this class, although it really doesn't make sense!

HRESULT CConnect::SolutionEventsSink::RefreshMainToolWindow(void)
{
	USES_CONVERSION;

	ASSERT(m_pwndMainCtrl != NULL);
	if (m_pwndMainCtrl == NULL)
	{
		return E_FAIL;
	}

	//...and now we need to configure it. The ActiveX Control within the window
	// has an Initialise DISP method, and all we need to do is pass the IDispatch
	// pointer to the application to it (from which it will find the DTE interface)
	//
	// Until I tried to get this working I had NO idea how much of a bodge ActiveX/IDispatch
	// is - it took me ages to get this working!
	//
	// First, obtain the DISPID of the property/method.
	OLECHAR FAR* szName = A2W("Refresh");

	DISPID dispid;
	HRESULT hr = m_pwndMainCtrl->GetIDsOfNames(	IID_NULL,
												&szName,
												1,
												LOCALE_SYSTEM_DEFAULT,
												&dispid);

	if (FAILED(hr) )
	{
		ASSERT(FALSE);			// Very, very bad - and should never happen
		return hr;
	}

	DISPPARAMS params;
	memset(&params, 0, sizeof params);

	params.rgvarg		= NULL;
	params.cArgs		= 0;
	params.cNamedArgs	= 0;

	return m_pwndMainCtrl->Invoke(	dispid,
									IID_NULL,
									LOCALE_SYSTEM_DEFAULT,
									DISPATCH_METHOD,
									&params,
									NULL,
									NULL,
									NULL);
}



void __stdcall CConnect::SolutionEventsSink::Opened(void)
{
	TRACE( _T("SolutionEvents::Opened\n") );

	RefreshMainToolWindow();
}


void __stdcall CConnect::SolutionEventsSink::BeforeClosing(void)
{
	TRACE( _T("SolutionEvents::BeforeClosing\n") );
}


void __stdcall CConnect::SolutionEventsSink::AfterClosing(void)
{
	TRACE( _T("SolutionEvents::AfterClosing\n") );

	RefreshMainToolWindow();
}


void __stdcall CConnect::SolutionEventsSink::QueryCloseSolution(VARIANT_BOOL *fCancel)
{
	UNREFERENCED_PARAMETER(fCancel);

	TRACE( _T("SolutionEvents::QueryCloseSolution\n") );
}


void __stdcall CConnect::SolutionEventsSink::Renamed(BSTR OldName)
{
	UNREFERENCED_PARAMETER(OldName);

	TRACE( _T("SolutionEvents::Renamed\n") );
}


void __stdcall CConnect::SolutionEventsSink::ProjectAdded(EnvDTE::Project* pProject)
{
	UNREFERENCED_PARAMETER(pProject);

#ifdef _DEBUG
	TRACE( _T("SolutionEvents::ProjectAdded\n") );

	CComBSTR bsName;
	pProject->get_Name(&bsName);

	TRACE( _T("\tProject name:  %s\n"), bsName);
#endif

	RefreshMainToolWindow();
}


void __stdcall CConnect::SolutionEventsSink::ProjectRemoved(EnvDTE::Project* pProject)
{
	UNREFERENCED_PARAMETER(pProject);

#ifdef _DEBUG
	TRACE( _T("SolutionEvents::ProjectRemoved\n") );

	CComBSTR bsName;
	pProject->get_Name(&bsName);

	TRACE( _T("\tProject name:  %s\n"), bsName);
#endif

	RefreshMainToolWindow();
}


void __stdcall CConnect::SolutionEventsSink::ProjectRenamed(EnvDTE::Project* pProject, BSTR bsOldName)
{
	UNREFERENCED_PARAMETER(pProject);
	UNREFERENCED_PARAMETER(bsOldName);

	TRACE( _T("SolutionEvents::ProjectRenamed\n") );

	RefreshMainToolWindow();
}

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
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Comments and Discussions