// IDESampleActiveX.cpp : Implementation of IDESampleActiveX

#include "stdafx.h"
#include "IDEPlugInAsActiveX.h"
#include "IDESampleActiveX.h"


/////////////////////////////////////////////////////////////////////////////
// IDESampleActiveX

STDMETHODIMP IDESampleActiveX::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] =
	{
		&IID_IIDESampleActiveX,
	};
	for ( int i = 0; i < sizeof( arr ) / sizeof( arr[ 0 ] ); i++ )
	{
		if ( InlineIsEqualGUID( *arr[ i ], riid ) )
			return S_OK;
	}
	return S_FALSE;
}

HRESULT IDESampleActiveX::OnDraw( ATL_DRAWINFO& di )
{
	RECT& rc = *(RECT*)di.prcBounds;

	// Resizing is also handled here

	if ( ::IsWindow( m_List.GetSafeHwnd() ) && ::IsWindow( m_AttrList.GetSafeHwnd() ) )
	{
		// One pixel space between our ListBoxes and the control border

		rc.left++;
		rc.right--;

		CRect	rect = rc;

		// We reserve 2/3 of the control's height for the element ListBox

		rect.top++;
		rect.bottom = ((rc.bottom / 3) * 2) - 1;

		m_List.MoveWindow( &rect );

		// The rest is for the attribute ListBox

		rect.top = rect.bottom + 2;
		rect.bottom = rc.bottom - 2;

		m_AttrList.MoveWindow( &rect );
	}

	return S_OK;
}

/**
 The OnCommand() method of the interface implementation is called each time a command added by the the IDE plug-in (menu item or toolbar button) is processed.
 nID stores the command ID defined by the ID element of the respective UIElement. pXMLSpy holds a reference to the dispatch interface of the Application object of XMLSpy.
 */
STDMETHODIMP IDESampleActiveX::raw_OnCommand(long nID, IDispatch* pXMLSpy)
{
	return S_OK;
}

/**
 The OnUpdateCommand() method is called each time the visible state of a button or menu item needs to be set.
 nID stores the command ID defined by the ID element of the respective UIElement. 
 pXMLSpy holds a reference to the dispatch interface of the Application object.

 Possible return values to set the update state are:
	spyEnable    = 1
	spyDisable   = 2
	spyCheck     = 4
	spyUncheck   = 8
 */
STDMETHODIMP IDESampleActiveX::raw_OnUpdateCommand(long nID, IDispatch* pXMLSpy, SPYUpdateAction* pAction)
{
	return S_OK;
}

/**
 OnEvent() is called each time an event is raised from XMLSpy.
 Possible values for nEventID are:
	On_BeforeStartEditing = 1
	On_EditingFinished = 2
	On_FocusChanged = 3
	On_Beforedrag = 4
	On_BeforeDrop = 5
	On_OpenProject = 6
	On_OpenDocument = 7
	On_CloseDocument = 8
	On_SaveDocument = 9
	On_DocEditDragOver = 10
	On_DocEditDrop = 11
	On_DocEditKeyDown = 12
	On_DocEditKeyUp = 13
	On_DocEditKeyPressed = 14
	On_DocEditMouseMove = 15
	On_DocEditButtonUp = 16
	On_DocEditButtonDown = 17
	On_DocEditContextMenu = 18
	On_DocEditPaste = 19
	On_DocEditCut = 20
	On_DocEditCopy = 21
	On_DocEditClear = 22
	On_DocEditSelectionChanged = 23
	On_DocEditDragOver = 10
	On_BeforeOpenProject = 25
	On_BeforeOpenDocument = 26
	On_BeforeSaveDocument = 27
	On_BeforeCloseDocument = 28
	On_ViewActivation = 29
	On_DocEditKeyboardEvent = 30
	On_DocEditMouseEvent = 31
	On_BeforeValidate = 32
	On_BeforeShowSuggestions = 33
	On_ProjectOpened = 34
	On_Char = 35
	On_Initialize = 36
	On_Running = 37
	On_Shutdown = 38
	On_AuthenticBeforeSave = 39
	On_AuthenticContextMenuActivated = 40
	On_AuthenticLoad = 41
	On_AuthenticToolbarButtonClicked = 42
	On_AuthenticToolbarButtonExecuted = 43
	On_AuthenticUserAddedXMLNode = 44

 The names of the events are the same as they appear in the Scripting Environment of XMLSpy. For IDE plug-ins the names used are immaterial. The events are identified using the ID value.
 arrayParameters is an array which is filled with the parameters of the currently raised event. Order, type, and meaning of the single parameters are available through the scripting environment of XMLSpy.
 The events module of a scripting project contains predefined functions for all events prior to version 4.4. The parameters passed to the predefined functions are identical to the array elements of the arrayParameters parameter.
 Events raised from the Authentic View of XMLSpy do not pass any parameters directly. An "event" object is used instead. The event object can be accessed through the Document object of the active document.
 pXMLSpy holds a reference to the dispatch interface of the Application object of XMLSpy.
 
 If the return value of OnEvent() is set, then neither the IDE plug-in nor an event handler inside of the scripting environment will get this event afterwards. Please note that all IDE plug-ins get/process the event before the Scripting Environment does.
 */
STDMETHODIMP IDESampleActiveX::raw_OnEvent(long nEventID, SAFEARRAY** arrayParameters, IDispatch* pXMLSpy, VARIANT* pReturnValue)
{
	// ignore some of the events
	if ( (nEventID == 36) || (nEventID == 37) || (nEventID == 38) )
		return S_OK;

	try
	{
		// use the COM wrapper to access the IApplication interface of XMLSpy. 
		// Avoids checking for HRESULT return codes and uses COM exceptions, instead.
		CComQIPtr<XMLSpyLib::IApplication> ipXMLSpy( pXMLSpy );

		if ( ipXMLSpy )
		{
			auto ipDoc = ipXMLSpy->GetActiveDocument();
			if ( ipDoc )
			{
				XMLSpyLib::SPYViewModes	nViewMode = ipDoc->GetCurrentViewMode();
				if ( (nViewMode == XMLSpyLib::spyViewGrid) || (nViewMode == XMLSpyLib::spyViewAuthentic) )
				{
					// If the current document is displayed in GridView or AuthenticView we
					// count the number of different elements and attributes.
					UINT nXMLDataCount = CountXMLData( ipDoc->RootElement );

					// An event ID of 2 is fired in GridView whenever an editing operations has finished.
					if ( (nEventID == 2) || (m_nXMLDataCount != nXMLDataCount) )
					{
						FillLists( ipDoc );
						m_nXMLDataCount = nXMLDataCount;
					}
				}
				else
				{
					// Otherwise we clear the contents of the ListBoxes
					m_List.DeleteAllItems();
					m_AttrList.DeleteAllItems();
					m_nXMLDataCount = 0;
				}
			}
		}
	}
	catch ( _com_error ex ) 
	{
		// catch COM related errors from within XMLSpy.
		// we probably did something wrong inour application
		AfxMessageBox( ex.Description() );
	}
	catch ( ... )
	{
		// catch any other errors. we can't do much about them
	}

	return S_OK;
}

// ---------------------------------------------------------------------------
// helper function
// recusrsivly count number of children
UINT IDESampleActiveX::CountXMLData( XMLSpyLib::IXMLData* ipXMLData )
{
	// use COM wrapper so we can avoid the cumbersome HRESULT values and rely on COM exceptions, instead.
	CComPtr<XMLSpyLib::IXMLData> ipNode = ipXMLData;

	// sanity check
	if ( !ipNode )
		return 0;

	UINT nCount = 0;
	try    // we stop the loop 
	{
		CComPtr<XMLSpyLib::IXMLData> ipChild = ipNode->GetFirstChild( XMLSpyLib::SPYXMLDataKind::spyXMLDataAllKinds );
		while ( ipChild )
		{
			nCount++;	// the child we retrieved with GetFirstChild or GetNextChild
			nCount += CountXMLData( ipChild );	// grand-children

			ipChild = ipNode->GetNextChild();
		}
	}
	catch ( _com_error &ex )
	{
		// 1503 - invalid iterator; 1504 - no next child
		if ( (HRESULT_CODE( ex.Error() ) != 1503) && ((HRESULT_CODE( ex.Error() ) != 1504)) )
			throw ex;		// some other error - not end of list
	}

	return nCount;
}

HRESULT IDESampleActiveX::raw_GetUIModifications(BSTR* pModificationsXML)
{
	// We don't add any modifications.
	
	CComBSTR	bstrMods = _T("<ConfigurationData><Modifications/></ConfigurationData>");
	return bstrMods.CopyTo(pModificationsXML);
}


HRESULT IDESampleActiveX::raw_GetDescription(BSTR* pDescription)
{
	// The string before the semicolon is also displayed as the title of the generated
	// docking control bar for the IDE PlugIn.
	
	CComBSTR	bstrDescr =
#ifdef BUILDAUTHENTIC
	_T("Authentic PlugIn with ActiveX")
#else
	_T("XMLSpy PlugIn with ActiveX")
#endif
	_T(";Sample PlugIn also having an ActiveX control");
	return bstrDescr.CopyTo(pDescription);
}


LRESULT IDESampleActiveX::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	AFX_MANAGE_STATE(::AfxGetStaticModuleState());

	CWnd	wnd; wnd.Attach(m_hWnd);

	CRect	rectClient;
	GetClientRect(&rectClient);

	if(m_List.Create(WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_SINGLESEL, rectClient, &wnd, 15000))
	{
		m_List.SetExtendedStyle(LVS_EX_FULLROWSELECT);

		m_List.InsertColumn(0, _T("Element"), LVCFMT_LEFT, 150);
		m_List.InsertColumn(1, _T("Count"), LVCFMT_RIGHT, 50);
	}

	if(m_AttrList.Create(WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_SINGLESEL, rectClient, &wnd, 15001))
	{
		m_AttrList.SetExtendedStyle(LVS_EX_FULLROWSELECT);

		m_AttrList.InsertColumn(0, _T("Attribute"), LVCFMT_LEFT, 150);
		m_AttrList.InsertColumn(1, _T("Count"), LVCFMT_RIGHT, 50);
	}

	wnd.Detach();

	return 0;
}


void IDESampleActiveX::FillLists(XMLSpyLib::IDocument* ipDoc)
{
	CComPtr<XMLSpyLib::IXMLData> ipRoot = ipDoc->RootElement;
	if (ipRoot)
	{
		std::vector<TNamesMap>					vecNamesMap;
		std::vector<XMLSpyLib::SPYXMLDataKind>	vecXMLKind;

		vecXMLKind.push_back(XMLSpyLib::spyXMLDataElement);
		vecXMLKind.push_back(XMLSpyLib::spyXMLDataAttr);

		vecNamesMap.push_back(TNamesMap());
		vecNamesMap.push_back(TNamesMap());

		FillNamesMap(vecNamesMap, ipRoot, vecXMLKind);

		FillListCtrlFromMap(&m_List, vecNamesMap[0]);
		FillListCtrlFromMap(&m_AttrList, vecNamesMap[1]);
	}
}


void IDESampleActiveX::FillNamesMap( std::vector<TNamesMap>& vecNamesMap, XMLSpyLib::IXMLData* ipXMLData, std::vector<XMLSpyLib::SPYXMLDataKind>& vecKind )
{
	// use the COM wrapper to avoid checking for HRESULT but be aware of COM exception. They get handled in onEvent.
	CComPtr<XMLSpyLib::IXMLData> ipNode = ipXMLData;

	// sanity check
	if ( !ipNode )
		return;

	if ( ipNode->GetHasChildren() == VARIANT_TRUE )
	{
		try		// the loop will be ended by an exception
		{
			CComPtr<XMLSpyLib::IXMLData> ipChild = ipNode->GetFirstChild( XMLSpyLib::SPYXMLDataKind::spyXMLDataAllKinds );
			while ( true )
			{
				FillNamesMap( vecNamesMap, ipChild.p, vecKind );
				ipChild.Release();
				ipChild = ipNode->GetNextChild();
			}
		}
		catch ( _com_error &ex )
		{
			// 1503 - invalid iterator; 1504 - no next child
			if ( (HRESULT_CODE( ex.Error() ) != 1503) && ((HRESULT_CODE( ex.Error() ) != 1504)) )
				throw ex;		// some other error - not end of list
		}
	}

	bool	bFound = false;
	size_t	idx = 0;
	for (; idx < vecKind.size(); idx++)
	{
		if (ipNode->GetKind() == vecKind[idx])
		{
			bFound = true;
			break;
		}
	}

	if (bFound)
	{
		CString		strName((char *)ipNode->GetName());
		TNamesMap::iterator	iter = vecNamesMap[idx].find(strName);

		if (iter != vecNamesMap[idx].end())
			(*iter).second++;
		else
			vecNamesMap[idx].insert(TNamesMap::value_type(strName, 1));
	}
}


void IDESampleActiveX::FillListCtrlFromMap(CListCtrl* pList, TNamesMap& namesMap)
{
	pList->DeleteAllItems();
	
	if(!namesMap.empty())
	{
		int					nRow = 0;
		TNamesMap::iterator	iter = namesMap.begin();

		while(iter != namesMap.end())
		{
			pList->InsertItem(nRow, (*iter).first);

			CString	strCount; strCount.Format(_T("%d"), (*iter).second);
			pList->SetItemText(nRow, 1, strCount);

			nRow++;
			iter++;
		}
	}
}
