Click here to Skip to main content
15,881,882 members
Articles / Mobile Apps

The StateWizard VC++ Add-in and Engine with Source Code

Rate me:
Please Sign up or sign in to vote.
4.73/5 (24 votes)
26 Mar 2009CPOL12 min read 190.1K   2.8K   132  
A cross-platform state-oriented application framework and a ClassWizard-like round-trip UML dynamic modeling/development tool that runs in popular IDEs. Aims at providing concurrent, distributed, and real-time application development tools for Win32/Linux
/**********************************************************************
UML StateWizard provides its software under the LGPL License and 
zlib/libpng License.

Email us at info@intelliwizard.com for any information, suggestions and 
feature requestions.

Home Page: http://www.intelliwizard.com
*************************************************************************/

// AddAppDLg.cpp : implementation file
//
#include "stdafx.h"
#include "StateTree.h"
#include "AddAppDLg.h"


// AddAppDlg.cpp : implementation file
//


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

#ifdef _DUMMY_DEBUG
#define new SME_DEBUG_NEW
#define delete SME_DEBUG_DELETE
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAddAppDlg dialog

// Request to ignore 2 file item update events: .c/.cpp and .h 
extern 	int g_nIgnoreFileItemUpdateNum;


CAddAppDlg::CAddAppDlg(CWnd* pParent /*=NULL*/)
: CDialog(CAddAppDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CAddAppDlg)
	m_entryHdlName = _T("");
	m_exitHdlName = _T("");
	m_appStateName = _T("");
	m_parentStateName = _T("");
	//}}AFX_DATA_INIT
}


void CAddAppDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAddAppDlg)
	DDX_Text(pDX, IDC_ENTRY_HANDLER_EDIT, m_entryHdlName);
	DDX_Text(pDX, IDC_EXIT_HANDLER_EDIT, m_exitHdlName);
	DDX_Text(pDX, IDC_APP_STATE_NAME_EDIT, m_appStateName);
	DDX_Text(pDX, IDC_STATIC_APP_NAME, m_parentStateName);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CAddAppDlg, CDialog)
	//{{AFX_MSG_MAP(CAddAppDlg)
	ON_EN_CHANGE(IDC_APP_STATE_NAME_EDIT, OnAppNameEditTextChange)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_ENTRY_CHECK, OnEntryCheck)
	ON_BN_CLICKED(IDC_EXIT_CHECK, OnExitCheck)
	ON_WM_HELPINFO()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CAddAppDlg message handlers
///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Initialize the AddApp(state) dialog
// INPUT: None
// OUTPUT: None
// Note: 
///////////////////////////////////////////////////////////////////////////////////////////
BOOL CAddAppDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();

	// Check the check box of entry and exit functions initially
	((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->SetCheck(1);
	((CButton*)GetDlgItem(IDC_EXIT_CHECK))->SetCheck(1);

	// TODO: Add extra initialization here

	HTREEITEM hSelector = m_pStateTree->tree.GetSelectedItem();

	if (m_pStateTree->tree.GetItemData(hSelector) & STATE_TREE_APPLICATION_MASK)
	{
		CString sAppName;

		sAppName = m_pStateTree->tree.GetItemText(hSelector);

		SetWindowText("Add a New State");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_OR_STATE))->SetWindowText("State Name:");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_NAME))->SetWindowText(sAppName);
	}
	else if (m_pStateTree->tree.GetItemData(hSelector) & STATE_TREE_STATE_MASK)
	{
		CString sStateName;

		sStateName = m_pStateTree->tree.GetItemText(hSelector);
		if (m_pStateTree->tree.GetItemData(hSelector) & STATE_TREE_DEFAULT_STATE)
		{			
			int idx = sStateName.Find("  (default)");
			sStateName = sStateName.Left(idx);
		}
		SetWindowText("Add a New State");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_OR_STATE))->SetWindowText("State Name:");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_NAME))->SetWindowText(sStateName);
	}
	else
	{
		SetWindowText("Add a New Application");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_OR_STATE))->SetWindowText("Application Name:");
		((CStatic*)GetDlgItem(IDC_STATIC_APP_NAME))->SetWindowText("No Parent Application or State");
	}

	((CButton*)GetDlgItem(IDOK))->EnableWindow(FALSE);

//.	SME_TRACE("Create add application dialog!\n");

	return TRUE;  // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE
}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Automatically creating two child nodes(entry function, exit function) 
//              under new state or application node in state tree 
// INPUT: None
// OUTPUT: None
// Note: It will only be invoked by OnOk method
///////////////////////////////////////////////////////////////////////////////////////////
void CAddAppDlg::AddEntryExitHandlers()
{
	CString sEntryHdlItem = "Entry: "; 
	CString sExitHdlItem = "Exit: ";
	
	HTREEITEM hSelector = NULL;
	HTREEITEM hInsert = NULL;

	hSelector = m_pStateTree->tree.GetSelectedItem();

	/* retrieve the entry function name */
	if (((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->GetCheck() == 1)
	{
		sEntryHdlItem += m_entryHdlName;
		sEntryHdlItem += STR_HDLER_PROTOTYPE;
	}

	hInsert = m_pStateTree->tree.InsertItem((LPCTSTR)(sEntryHdlItem), 4, 4, hSelector, TVI_LAST);
	m_pStateTree->tree.SetItemData(hInsert, STATE_TREE_ENTRY_EXIT_HANDLER_MASK);
	m_pStateTree->tree.SetItemImage(hInsert, 4, 4);

	/* retrieve the exit function name */
	if (((CButton*)GetDlgItem(IDC_EXIT_CHECK))->GetCheck() == 1)
	{
		sExitHdlItem += m_exitHdlName;
		sExitHdlItem += STR_HDLER_PROTOTYPE;
	}

	hInsert = m_pStateTree->tree.InsertItem((LPCTSTR)(sExitHdlItem), 4, 4, hSelector, TVI_LAST);
	m_pStateTree->tree.SetItemData(hInsert, STATE_TREE_ENTRY_EXIT_HANDLER_MASK);
	m_pStateTree->tree.SetItemImage(hInsert, 4, 4);

}

///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Response to ok button click
// INPUT: None
// OUTPUT: None
// Note: 
///////////////////////////////////////////////////////////////////////////////////////////
void CAddAppDlg::OnOK() 
{
	// TODO: Add extra validation here
	UpdateData(TRUE);



	// Trim space at the beginning and the end of the name
	m_appStateName.TrimLeft();
	m_appStateName.TrimRight();
	m_entryHdlName.TrimLeft();
	m_entryHdlName.TrimRight();
	m_exitHdlName.TrimLeft();
	m_exitHdlName.TrimRight();

	if (m_appStateName.Find(' ') != -1 || m_appStateName.Find('\t') != -1 || m_appStateName.IsEmpty() ||
		((((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->GetCheck() == 1) && (m_entryHdlName.Find(' ') != -1 || m_entryHdlName.Find('\t') != -1 || m_entryHdlName.IsEmpty())) ||
		((((CButton*)GetDlgItem(IDC_EXIT_CHECK))->GetCheck() == 1) && (m_exitHdlName.Find(' ') != -1 || m_exitHdlName.Find('\t') != -1 || m_exitHdlName.IsEmpty())))
	{
		AfxMessageBox("Application(state, exit action, entry action) name can not contain any white space.", MB_ICONERROR|MB_OK);
		return;
	}

	int nCharSeq = 0;
	while (true)
	{
		if (nCharSeq >= m_appStateName.GetLength())
			break;

		char ch = m_appStateName.GetAt(nCharSeq);
		if (nCharSeq == 0)
		{
			// The first character must be alphabetic or underline
			if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_'))
			{
				AfxMessageBox("First letter in an application(state) name must be '[A-Za-z|_]'.", MB_ICONERROR|MB_OK);
				return;
			}
		}
		else
		{
			if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || (ch >= '0' && ch <= '9')))
			{
				AfxMessageBox("Letters in an application(state) name must be '[A-Za-z|0-9|_]'.", MB_ICONERROR|MB_OK);
				return;
			}
		}
		nCharSeq++;
	}

	CString sAddItemName;
	CString strDefault = "  (default)";
	CString sParentName;
	CString sDefaultStateName = "";
	HTREEITEM hSelector = NULL;
	HTREEITEM hInsert = NULL;
	HTREEITEM hSelApp = NULL; 	// Selected application
	DWORD lMask = 0;
	DWORD lParam = 0;
	TV_INSERTSTRUCT Inserter; 	// Define insert item structure 

	CString strHdrFullPath;
	CString strSrcFullPath;


	hSelector = m_pStateTree->tree.GetSelectedItem();
	if(hSelector)
	{
		// Get application item covering current selected tree item in state tree
		hSelApp = m_pStateTree->tree.GetSelectedApp(hSelector);

		CString sAppName = m_pStateTree->tree.GetItemText(hSelApp);
		CString sAppClass = m_pStateTree->tree.GetSelectedAppClass(hSelector);
		CString sFuncClassPrefix;
		if (!sAppClass.IsEmpty()) sFuncClassPrefix+= sAppClass + "::";

		lMask = m_pStateTree->tree.GetItemData(hSelector);
		DWORD lParentMask = lMask;
		if (lMask & STATE_TREE_PROJECT_MASK)
		{// Add a new application
			Inserter.item.lParam = STATE_TREE_APPLICATION_MASK;

			// Get parent item text(current project name)
			sParentName = m_pStateTree->tree.GetItemText(hSelector);
			sParentName.Replace(" application(s)", NULL);
			sParentName.TrimLeft();
			sParentName.TrimRight();

			// Get new application name
			sAddItemName = m_appStateName;

			hSelApp = hSelector;

			lParam = STATE_TREE_APPLICATION_MASK;	// Check only application redundancy	
		}
		else
		{// Add a new state 
			Inserter.item.lParam = STATE_TREE_STATE_MASK;

			// Get parent tree item name
			sParentName = m_parentStateName;

			// Get new state name
			//			sAddItemName = "State name:  ";		
			sAddItemName = "";
			if (!(lMask & STATE_TREE_HAS_DEFAULT_CHILD_STATE))
			{
				lMask |= STATE_TREE_HAS_DEFAULT_CHILD_STATE;
				Inserter.item.lParam |= STATE_TREE_DEFAULT_STATE;
				sAddItemName = sAddItemName+m_appStateName+strDefault;
				sDefaultStateName = m_appStateName;
			}
			else
				sAddItemName += m_appStateName;

			lParam = STATE_TREE_APPLICATION_MASK|STATE_TREE_STATE_MASK; // Check app and state redundancy
		}

		// Check redundancy		
		if (!m_pStateTree->tree.TraverseStateTree(hSelApp, m_appStateName, lParam, NULL))
		{
			MessageBox("Error!\nThe name already exists. Please choose another application(state) name.", NULL ,MB_OK|MB_ICONERROR);
			return;
		}

		m_pStateTree->tree.SetItemData(hSelector, lMask);	

		// Retrieve DSUtils object
		if (lMask & STATE_TREE_PROJECT_MASK)
		{
			if (((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->GetCheck() == 0)
				m_entryHdlName = "SME_NULL";
			if (((CButton*)GetDlgItem(IDC_EXIT_CHECK))->GetCheck() == 0)
				m_exitHdlName = "SME_NULL";

			// Check do-all-or-nothing

			// Request to ignore 2 file item update events: .c/.cpp and .h 
			// If checking is OK, it will add these 2 files and then trigger ItemAdded event.
			g_nIgnoreFileItemUpdateNum =2;

			//!!! Update Interface
			//!!! From CString To BSTR & In Lack Of 1 Parameters
			BSTR bstrParentName = _com_util::ConvertStringToBSTR(sParentName.GetBuffer());
			sParentName.ReleaseBuffer();
			BSTR bstrAppStateName= _com_util::ConvertStringToBSTR(m_appStateName.GetBuffer());
			m_appStateName.ReleaseBuffer();
			m_pStateTree->Fire_CheckAddAppAtomic(bstrParentName, bstrAppStateName);
			if(!m_pStateTree->data.isAddAppAtomic)
			{
				// If checking fail, it will not add these 2 files,roll back .
				g_nIgnoreFileItemUpdateNum =0;
				// CDialog::OnOK(); // Allow to try again.
				return;
			}
			
			//!!! Update Interface
			//!!! From CString to BSTR & In Lack Of 1 Parameter
			bstrParentName = _com_util::ConvertStringToBSTR(sParentName.GetBuffer());
			bstrAppStateName= _com_util::ConvertStringToBSTR(m_appStateName.GetBuffer());
			BSTR bstrEntryHdlName = _com_util::ConvertStringToBSTR(m_entryHdlName.GetBuffer());
			BSTR bstrExitHdlName = _com_util::ConvertStringToBSTR(m_exitHdlName.GetBuffer());
			m_pStateTree->Fire_CreateAppFiles(bstrParentName, bstrAppStateName, bstrEntryHdlName, bstrExitHdlName);
			sParentName.ReleaseBuffer();
			m_appStateName.ReleaseBuffer();
			m_entryHdlName.ReleaseBuffer();
			m_exitHdlName.ReleaseBuffer();


			if(!m_pStateTree->data.isCreateAppFilesOk)
			{
				CDialog::OnOK();
				return;
			}
		}
		else
		{
			int nTotalStateNum = 0;
			m_pStateTree->tree.CountBeforehandAppState(hSelApp, NULL, &nTotalStateNum);
			//limit hierarchy num

			/*
			#ifdef EVALUATE_VER
			if (nTotalStateNum > 16)
			{
			AfxMessageBox("Please buy official version!");
			CDialog::OnOK();
			return;
			}
			#endif
			*/

			int nSkippedLineNum = 0;
			if ((lParentMask & STATE_TREE_HAS_DEFAULT_CHILD_STATE) == 0)
			{
				m_pStateTree->tree.CountBeforehandAppState(hSelApp, hSelector, &nSkippedLineNum);
				nSkippedLineNum++;
			}
			else
			{
				HTREEITEM hChild = m_pStateTree->tree.GetChildItem(hSelector);
				HTREEITEM hClosetSibling = NULL;

				while (true)
				{
					while (true)
					{
						if (NULL == hChild)
							break;

						if (m_pStateTree->tree.GetItemData(hChild) & STATE_TREE_STATE_MASK)
						{
							hClosetSibling = hChild;
							//						CString sTemp = m_pStateTree->tree.GetItemText(hClosetSibling);
						}
						hChild = m_pStateTree->tree.GetNextSiblingItem(hChild);
					}
					if ((m_pStateTree->tree.GetItemData(hClosetSibling) & STATE_TREE_HAS_DEFAULT_CHILD_STATE) == 0)
						break;
					else
						hChild = m_pStateTree->tree.GetChildItem(hClosetSibling);
				}

				m_pStateTree->tree.CountBeforehandAppState(hSelApp, hClosetSibling, &nSkippedLineNum);
				nSkippedLineNum++;
			}
			CStringArray AppPathList;
			AppPathList.RemoveAll();
			m_pStateTree->GetAppSrcHdrPath(AppPathList, sAppName, hSelApp);
			strHdrFullPath = AppPathList.GetAt(0);
			strSrcFullPath = AppPathList.GetAt(1);

			if (((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->GetCheck() == 0)
				m_entryHdlName = "SME_NULL";
			if (((CButton*)GetDlgItem(IDC_EXIT_CHECK))->GetCheck() == 0)
				m_exitHdlName = "SME_NULL";

			//!!! Update Interface
			//!!! From CString to BSTR
			BSTR bstrSrcFullPath =_com_util::ConvertStringToBSTR(strSrcFullPath.GetBuffer());
			BSTR bstrHdrFullPath =_com_util::ConvertStringToBSTR(strHdrFullPath.GetBuffer());
			BSTR bstrAppStateName =_com_util::ConvertStringToBSTR(m_appStateName.GetBuffer());
			BSTR bstrParentName =_com_util::ConvertStringToBSTR(sParentName.GetBuffer());
			BSTR bstrAppName =_com_util::ConvertStringToBSTR(sAppName.GetBuffer());
			BSTR bstrFuncClassPrefix =_com_util::ConvertStringToBSTR(sFuncClassPrefix.GetBuffer());
			BSTR bstrDefaultStateName =_com_util::ConvertStringToBSTR(sDefaultStateName.GetBuffer());
			BSTR bstrEntryHdlName =_com_util::ConvertStringToBSTR(m_entryHdlName.GetBuffer());
			BSTR bstrExitHdlName =_com_util::ConvertStringToBSTR(m_exitHdlName.GetBuffer());


			m_pStateTree->Fire_CheckAddStateAtomic(bstrSrcFullPath, bstrHdrFullPath, bstrAppStateName, bstrParentName, bstrAppName, bstrFuncClassPrefix
				, bstrDefaultStateName,(ULONG)nSkippedLineNum, bstrEntryHdlName, bstrExitHdlName);

			strSrcFullPath.ReleaseBuffer();
			strHdrFullPath.ReleaseBuffer();
			m_appStateName.ReleaseBuffer();
			sParentName.ReleaseBuffer();
			sAppName.ReleaseBuffer();
			sDefaultStateName.ReleaseBuffer();
			m_entryHdlName.ReleaseBuffer();
			m_exitHdlName.ReleaseBuffer();

			if(!m_pStateTree->data.isAddStateAtomic){
				CDialog::OnOK();
				return;
			}
		}

		// Insert new state or application into state tree
		CString sTemp = m_pStateTree->tree.GetItemText(hSelector);
		Inserter.hParent = hSelector; 
		if (lMask & STATE_TREE_PROJECT_MASK)
			Inserter.hInsertAfter = TVI_SORT;
		else
			Inserter.hInsertAfter = TVI_LAST;// Insert position
		Inserter.item.mask = TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE;// Set mask
		Inserter.item.pszText = (LPTSTR)(LPCTSTR)(sAddItemName);

		hInsert= m_pStateTree->tree.InsertItem(&Inserter);// Do insert
		int nImage = (lMask & STATE_TREE_PROJECT_MASK)? 2: 3;
		m_pStateTree->tree.SetItemImage(hInsert, nImage, nImage);
		m_pStateTree->tree.SelectItem(hInsert);
		AddEntryExitHandlers();

		TV_SORTCB SortCB;
		SortCB.hParent = hSelector;
		SortCB.lpfnCompare = CompareFunc;
		SortCB.lParam = 0;
		m_pStateTree->tree.SortChildrenCB(&SortCB);
		BOOL bHandler = TRUE;
		
		if (nImage == 2) // I think it does not need to refresh...?????
			m_pStateTree->OnRefresh(0,0,0,bHandler);
	}

	CDialog::OnOK();

	// Acivate c file; 
	// Note: On adding app, strSrcFullPath is empty.
	if (!strSrcFullPath.IsEmpty()) 
		m_pStateTree->Fire_OpenFileAndMoveLineTo(CComBSTR(strSrcFullPath), -1);

}



///////////////////////////////////////////////////////////////////////////////////////////
// DESCRIPTION: Set the application name as prefix for entry and exit function
// INPUT: None
// OUTPUT: None
// Note: 
///////////////////////////////////////////////////////////////////////////////////////////
void CAddAppDlg::OnAppNameEditTextChange() 
{
	// TODO: If this is a RICHEDIT control, the control will not
	// send this notification unless you override the CDialog::OnInitDialog()
	// function and call CRichEditCtrl().SetEventMask()
	// with the ENM_CHANGE flag ORed into the mask.

	// TODO: Add your control notification handler code here
	UpdateData(TRUE);

	m_appStateName.TrimLeft();
	m_appStateName.TrimRight();

	if (m_appStateName.GetLength() == 0)
	{
		((CButton*)GetDlgItem(IDOK))->EnableWindow(FALSE);
		((CEdit*)GetDlgItem(IDC_ENTRY_HANDLER_EDIT))->SetWindowText(m_appStateName);
		((CEdit*)GetDlgItem(IDC_EXIT_HANDLER_EDIT))->SetWindowText(m_appStateName);
	}
	else
	{
		((CButton*)GetDlgItem(IDOK))->EnableWindow(TRUE);
		((CEdit*)GetDlgItem(IDC_ENTRY_HANDLER_EDIT))->SetWindowText(m_appStateName+"Entry");
		((CEdit*)GetDlgItem(IDC_EXIT_HANDLER_EDIT))->SetWindowText(m_appStateName+"Exit");
	}
}

void CAddAppDlg::OnDestroy() 
{
	CDialog::OnDestroy();

	// TODO: Add your message handler code here
/*	SME_TRACE("Destroy add application dialog!\n");
	SME_ALLOC_MEM_SIZE();
*/
}


void CAddAppDlg::OnEntryCheck() 
{
	// TODO: Add your control notification handler code here
	if (((CButton*)GetDlgItem(IDC_ENTRY_CHECK))->GetCheck() == 0)
		((CEdit*)GetDlgItem(IDC_ENTRY_HANDLER_EDIT))->EnableWindow(FALSE);
	else
		((CEdit*)GetDlgItem(IDC_ENTRY_HANDLER_EDIT))->EnableWindow(TRUE);
}

void CAddAppDlg::OnExitCheck() 
{
	// TODO: Add your control notification handler code here
	if (((CButton*)GetDlgItem(IDC_EXIT_CHECK))->GetCheck() == 0)
		((CEdit*)GetDlgItem(IDC_EXIT_HANDLER_EDIT))->EnableWindow(FALSE);
	else
		((CEdit*)GetDlgItem(IDC_EXIT_HANDLER_EDIT))->EnableWindow(TRUE);
}


BOOL CAddAppDlg::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	// TODO: Add your message handler code here and/or call default
	HTREEITEM hSelector;
	DWORD lMask;

	hSelector = m_pStateTree->tree.GetSelectedItem();
	lMask = m_pStateTree->tree.GetItemData(hSelector);

	CString sCmdName = GetHelpFileName();

	if (lMask & STATE_TREE_PROJECT_MASK)
	{
		//DSUtils(m_pStateTree->tree.m_pApp).GotoWinHelp(0x4F);
		sCmdName += "::/new_state.htm";
		::HtmlHelp(NULL, sCmdName, HH_DISPLAY_TOPIC, 0);
	}
	else
	{
		//DSUtils(m_pStateTree->tree.m_pApp).GotoWinHelp(0x50);
		sCmdName += "::/new_application.htm";
		::HtmlHelp(NULL, sCmdName, HH_DISPLAY_TOPIC, 0);
	}
	return TRUE;
	//	return CDialog::OnHelpInfo(pHelpInfo);
}

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 States United States
Alex "Question is more important than the answer."

Comments and Discussions