Click here to Skip to main content
15,881,861 members
Articles / Desktop Programming / MFC

Brainchild, A syntax coloring edit control

Rate me:
Please Sign up or sign in to vote.
4.85/5 (64 votes)
16 Jun 2005CPOL5 min read 703.3K   26.8K   263  
Syntax coloring, multi-level undo/redo editor control.
//
//	keyboard.cpp
//
//	(C) Copyright 2000-2002 by Jan van den Baard.
//	    All Rights Reserved.
//

#include "bcc.h"

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

// Constructor.
KeyboardPage::KeyboardPage() 
{
	// Setup icon.
	m_nIcon = IDI_KEYBOARD;

	// Load bitmaps.
	m_hImages = ImageList_LoadBitmap( ClsGetResourceHandle(), MAKEINTRESOURCE( IDB_CLIST ), 16, 0, RGB( 255, 0, 255 ));
}

// Destructor.
KeyboardPage::~KeyboardPage()
{
	// Destroy image list.
	if ( m_hImages ) ImageList_Destroy( m_hImages );
}

// Refresh page contents.
void KeyboardPage::RefreshData( LPPARSER pParser )
{
	_ASSERT_VALID( pParser );

	// Save parser.
	m_pParser = pParser;

	// Reset list box.
	m_Keys.SetRedraw( FALSE );
	m_Keys.ResetContent();

	// Go through the key hashes.
	LPHASH lpHash, lpNext;
	TCHAR szKeyString[ 256 ];
	for ( int i = 0; i < HASHSIZE; i++ )
	{
                for ( lpHash = pParser->aHash[ i ]; lpHash; lpHash = lpNext )
		{
			// Pick up next hash in this chain.
			lpNext = lpHash->lpNext;

			// Convert code and qualifier into
			// a string.
			::CodeQual2Str( lpHash->cCode, lpHash->cQual, szKeyString );

			// Add the string to the list.
			m_Keys.AddString( szKeyString );
		}
	}
	m_Keys.SetRedraw();

	// Setup the GUI.
	SetupControls();
}

// Save page/parser contents.
BOOL KeyboardPage::SavePage( ClsStdioFile *pFile, LPPARSER pParser )
{
	try
	{
		// Write comment.
		pFile->PrintF( ClsString( MAKEINTRESOURCE( IDS_COMMENT_KEYS )));

		// Go through the key hashes.
		LPHASH pHash;
		LPCNODE pCNode;
		TCHAR szKeyString[ 64 ];
		for ( int i = 0; i < HASHSIZE; i++ )
		{
			for ( pHash = pParser->aHash[ i ]; pHash; pHash = pHash->lpNext )
			{
				// Convert code and qualifier into
				// a string.
				::CodeQual2Str( pHash->cCode, pHash->cQual, szKeyString );

				// Write the key identifier.
				pFile->PrintF( _T( "Key=%s\n" ), szKeyString );

				// Write the command nodes.
				for ( pCNode = pHash->lpCommands->lpFirst; pCNode->lpNext; pCNode = pCNode->lpNext )
				{
					// What is the command type?
					switch ( pCNode->nType )
					{
						case	CTYPE_HARDCODED:
							// Write command and it's ID.
							pFile->PrintF( _T( "\tHardcoded=%ld\n" ), ::FindCommandID( pCNode->lpFunc ));
							break;

						case	CTYPE_RUN:
							// Write command and it's
							// string.
							pFile->PrintF( _T( "\tRun=%s\n" ), pCNode->pcStr );
							break;

						case	CTYPE_TEXT:
							// Write command and it's
							// string.
							pFile->PrintF( _T( "\tInsertText=%s\n" ), pCNode->pcStr );
							break;

						case	CTYPE_SHELLOPEN:
							// Write the command and it's
							// string.
							pFile->PrintF( _T( "\tOpen=%s\n" ), pCNode->pcStr );
							break;

						case	CTYPE_END:
							break;
					}
				}

				// End the key description.
				pFile->PrintF( _T( "EndKey\n" ) );
			}
		}
	}
	catch ( ClsException& e )
	{
		// Error...
		UNREFERENCED_PARAMETER( e );
		return FALSE;
	}
	return TRUE;
}

// Setup the controls.
void KeyboardPage::SetupControls()
{
	// Get selections.
	int nKeySel = m_Keys.GetCurSel();
	int nComSel = m_Commands.GetCurSel();
	int nCount = m_Commands.GetCount();

	// Any key selected?
	if ( nKeySel == LB_ERR )
	{
		// Clear command list.
		m_Commands.ResetContent();
		nComSel = nCount = LB_ERR;
	}

	m_Commands.EnableWindow(( BOOL )( nKeySel != LB_ERR ));
	m_Tools.EnableButton( EditToolbar::ETID_DELETE, ( BOOL )( nKeySel != LB_ERR ));
	m_ComTools.EnableButton( EditToolbar::ETID_NEW, ( BOOL )( nKeySel != LB_ERR ));
	m_ComTools.EnableButton( EditToolbar::ETID_DELETE, ( BOOL )( nKeySel != LB_ERR && nComSel != LB_ERR ));
	m_ComTools.EnableButton( EditToolbar::ETID_UP, ( BOOL )( nKeySel != LB_ERR && nComSel != LB_ERR && nComSel > 0 ));
	m_ComTools.EnableButton( EditToolbar::ETID_DOWN, ( BOOL )( nKeySel != LB_ERR && nComSel != LB_ERR && nComSel < nCount - 1 ));
}

// WM_MEASUREITEM handler.
LRESULT KeyboardPage::OnMeasureItem( UINT nID, LPMEASUREITEMSTRUCT pMis )
{
	if ( nID == IDC_COMMANDS )
	{
		// Get a device context.
		ClsGetDC dc( this );

		// Select the font.
		ClsFont font;
		GetFont( font );
		ClsSelector sel( &dc, font );

		// Compute the height.
		pMis->itemHeight = dc.GetTextExtent( _T( " " ), 1 ).CY();
		return TRUE;
	}
	return FALSE;
}

// WM_DRAWITEM message handler.
LRESULT KeyboardPage::OnDrawItem( UINT nID, LPDRAWITEMSTRUCT pDis )
{
	if ( nID == IDC_COMMANDS )
	{
		// Render data if valid.
		LPCNODE lpCNode = ( LPCNODE )pDis->itemData;
		if ( lpCNode )
		{
			// Ontain string to render.
			LPCTSTR pszStr = ( LPCTSTR )( lpCNode->nType == CTYPE_HARDCODED ? ::FindCommandDesc( lpCNode->lpFunc ) : lpCNode->pcStr );

			// Images valid?.
			if ( m_hImages )
			{
				// Clear background.
				::FillRect( pDis->hDC, &pDis->rcItem, ( HBRUSH )( COLOR_WINDOW + 1 ));

				// Render the image and make room for
				// it.
				ImageList_Draw( m_hImages, lpCNode->nType, pDis->hDC, pDis->rcItem.left + 3, pDis->rcItem.top, ILD_NORMAL );
				pDis->rcItem.left += 20;
			}
			
			// Draw it.
			ColorList::DrawTextItem( pDis, pszStr );
			return TRUE;
		}
	}
	return FALSE;
}

// Find the current selection.
LPHASH KeyboardPage::GetSelection()
{
	// Get the key descriptor.
	TCHAR szKeyString[ 64 ];
	if ( m_Keys.GetText( m_Keys.GetCurSel(), szKeyString ) != LB_ERR )
	{
		// Convert to a code and qualifier.
		TCHAR cCode, cQual;
		if ( ::Str2CodeQual( szKeyString, &cCode, &cQual ))
		{
			// Find the hash.
			LPHASH lpHash;
			for ( lpHash = m_pParser->aHash[ cCode & HASHMASK ]; lpHash; lpHash = lpHash->lpNext )
			{
				// Is this the one?
				if ( lpHash->cCode == cCode && lpHash->cQual == cQual )
					// Return it.
					return lpHash;
			}
		}
	}
	return NULL;
}

// Create the popup menu.
BOOL KeyboardPage::CreatePopupMenu()
{
	// Load the popup menu.
	if ( m_RealMenu.Load( MAKEINTRESOURCE( IDR_POPUP )))
	{
		// Wrap the sub-menu.
		HMENU hSubMenu = ::GetSubMenu( m_RealMenu, 0 );
		if ( hSubMenu )
		{
			m_Menu.Attach( hSubMenu );

			// Setup the menu.
			m_RealMenu.IconFrame( FALSE );
			m_RealMenu.SetImageList( m_hImages );

			// Convert the menus.
			if ( m_RealMenu.ConvertMenus())
			{
				// Setup the item bitmaps.
				StrItemBitmap	sib[ 5 ] =
				{
					{ 0, IDS_HARDCODED },
					{ 1, IDS_INSERT    },
					{ 2, IDS_RUN	   },
					{ 3, IDS_OPEN	   },
					{ 0, SBM_END	   }
				};
				if ( m_RealMenu.SetItemBitmap( sib ))
					return TRUE;
			}
		}
	}
	return FALSE;
}

// Add a keyboard mapping.
void KeyboardPage::AddKeyMapping( ClsString strKey )
{
	// Convert the string to a code and qualifier.
	TCHAR cCode, cQual;
	if ( ::Str2CodeQual( strKey, &cCode, &cQual ) == FALSE )
		return;

	// Is the hash for this key already defined?
	LPHASH lpHash;
	for ( lpHash = m_pParser->aHash[ cCode & HASHMASK ]; lpHash; lpHash = lpHash->lpNext )
	{
		// Is this the one?
		if ( lpHash->cCode == cCode && lpHash->cQual == cQual )
		{
			// Find the entry in the
			// listview control.
			for ( int i = 0; ; i++ )
			{
				// Find it in the list control.
				int nKey = m_Keys.FindString( 0, strKey );
				if ( nKey != LB_ERR )
				{
					// Select the key.
					m_Keys.SetCurSel( nKey );
					SendMessage( WM_COMMAND, MAKEWPARAM( IDC_KEYS, LBN_SELCHANGE ), ( LPARAM )m_Keys.GetSafeHWND());
					return;
				}
			}
		}
	}

	// Hash not available. Allocate
	// an empty command list.
	LPCLIST lpCList = ( LPCLIST )::AllocPooled( pParserPool, sizeof( CLIST ));
	if ( lpCList )
	{
		// Initialize list.
		NewList(( LPLIST )lpCList );

		// Add the hash to the hash table.
		if (( lpHash = ::AddHash( pParserPool, m_pParser->aHash, cCode, cQual, lpCList )) != NULL )
		{
			// Add the string to the
			// listview control.
			int nAdd = m_Keys.AddString( strKey );

			// Select it.
			m_Keys.SetCurSel( nAdd );
			SendMessage( WM_COMMAND, MAKEWPARAM( IDC_KEYS, LBN_SELCHANGE ), ( LPARAM )m_Keys.GetSafeHWND());
			
			// Changes made...
			pSettings->Changed( m_pParser );
			return;
		}
		else
			// Emit an error.
			MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );

		// De-allocate command list.
		::FreePooled( pParserPool, lpCList );
	}
	else
		// Emit an error.
		MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
}

// Create a command list node.
void KeyboardPage::CreateNode( int nType, LPVOID lpvData )
{
	// Get the currently selected keyboard hash.
	LPHASH pHash = GetSelection();
	LPCNODE pCNode = NULL;
	if ( pHash )
	{
		// Allocate node.
		pCNode = ( LPCNODE )::AllocPooled( pParserPool, sizeof( CNODE ));
		if ( pCNode != NULL )
		{
			// Setup type.
			pCNode->nType = nType;

			// Hardcoded?
			if ( nType == CTYPE_HARDCODED )
				// Setup function.
				pCNode->lpFunc = ::FindCommand(( int )lpvData );
			else
			{
				// Allocate string copy.
				pCNode->pcStr = ::CopyStringPool( pParserPool, ( LPCTSTR )lpvData );
				if ( pCNode->pcStr == NULL )
				{
					// Failure.
					MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
					::FreePooled( pParserPool, pCNode );
					pCNode = NULL;
				}
			}
		}
		else
			MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
	}

	// Still there?
	if ( pCNode )
	{
		// Add the node to the list.
		AddTail(( LPLIST )pHash->lpCommands, ( LPNODE )pCNode );

		// Add it to the listview.
		int nSel = m_Commands.AddString(( LPCTSTR )pCNode );
		if ( nSel != LB_ERR )
		{
			// Select the entry.
			m_Commands.SetCurSel( nSel );

			// Edit the node.
			SendMessage( WM_COMMAND, MAKEWPARAM( IDC_COMMANDS, LBN_DBLCLK ), ( LPARAM )m_Commands.GetSafeHWND());

			// Changes have been made.
			pSettings->Changed( m_pParser );
			SetupControls();
		}
		else
		{
			// Show out of memory error. Guess this should be the
			// only reason this could fail.
			MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );

			// Free the node and
			// it's string.
			if ( nType != CTYPE_HARDCODED ) ::FreePooled( pParserPool, pCNode->pcStr );
			::FreePooled( pParserPool, pCNode );
		}
	}
}

// WM_COMMAND handler.
LRESULT KeyboardPage::OnCommand( UINT nNotifyCode, UINT nCtrlID, HWND hWndCtrl )
{
	// What's up?
	switch ( nCtrlID )
	{
		case	EditToolbar::ETID_NEW:
			// Command toolbar?
			if ( hWndCtrl == m_ComTools )
			{
				// Obtain button rectangle.
				ClsRect rc;
				m_ComTools.GetItemRect( 0, rc );

				// Convert rectangle to screen
				// coordinates.
				m_ComTools.ClientToScreen( rc );

				// Show the menu.
				::TrackPopupMenuEx( m_Menu,//*m_Menu.GetSubMenu( 0 ),
						  TPM_LEFTALIGN,
						  rc.Left(),
						  rc.Bottom(),
						  GetSafeHWND(),
						  NULL );
			}
			else
			{
				// Show the key recorder.
				KeyRec kr;
				ClsString str;
				if ( kr.KeyRecord( *GetParent(), str ))
					AddKeyMapping( str );
			}
			return 0;

		case	EditToolbar::ETID_DELETE:
			// Command toolbar?
			if ( hWndCtrl == m_ComTools )
			{
				// Get current selection.
				LPCNODE pCNode = ( LPCNODE )m_Commands.GetItemData( m_Commands.GetCurSel());
				if ( pCNode != ( LPCNODE )LB_ERR )
				{
					// Save selection position.
					int nSel = m_Commands.GetCurSel();
					int nPos = nSel;
					int nMax = m_Commands.GetCount();
					
					// Select the next or previous
					// entry.
					if ( nSel == nMax - 1 && nSel ) nSel--;
					else				nSel++;

					// Select the new item
					m_Commands.SetCurSel( nSel );

					// Remove the node from the listview.
					m_Commands.DeleteString( nPos );

					// Remove it from the command list.
					Remove(( LPNODE )pCNode );

					// Free the node
					// data.
					if ( pCNode->nType != CTYPE_HARDCODED )
						::FreePooled( pParserPool, pCNode->pcStr );

					// And the node itself.
					::FreePooled( pParserPool, pCNode );

					// Setup GUI.
					SetupControls();
					pSettings->Changed( m_pParser );
				}
				return 0;
			}
			else
			{
				// Get the key descriptor.
				TCHAR szKeyString[ 64 ];
				int nSel = m_Keys.GetCurSel(), nMax = m_Keys.GetCount();
				if ( nSel != LB_ERR )
				{
					if ( m_Keys.GetText( nSel, szKeyString ) != LB_ERR )
					{
						// Convert to a code and qualifier.
						TCHAR cCode, cQual;
						if ( ::Str2CodeQual( szKeyString, &cCode, &cQual ))
						{
							// Remove the hash. This will automatically destroy
							// the command-list aswell.
							::RemHash( pParserPool, m_pParser->aHash, cCode, cQual );

							// Remove it from the listview.
							m_Keys.DeleteString( nSel );

							// Was it the last one?
							if ( nSel == nMax - 1 ) nSel--;

							// Select the next or previous key.
							m_Keys.SetCurSel( nSel );
							SendMessage( WM_COMMAND, MAKEWPARAM( IDC_KEYS, LBN_SELCHANGE ), ( LPARAM )m_Keys.GetSafeHWND());

							// Changes made...
							pSettings->Changed( m_pParser );
						}
					}
				}
			}
			return 0;

		case	EditToolbar::ETID_UP:
		{
			// Valid node?
			LPCNODE pCNode = ( LPCNODE )m_Commands.GetItemData( m_Commands.GetCurSel());
			if ( pCNode != ( LPCNODE )LB_ERR )
			{
				// Get it's predecessor.
				LPCNODE pCPrev = pCNode->lpPrev;

				// Swap both data entries.
				CNODE cTemp;
				cTemp.nType	  = pCPrev->nType;
				cTemp.uCommand	  = pCPrev->uCommand;
				pCPrev->nType	  = pCNode->nType;
				pCPrev->uCommand  = pCNode->uCommand;
				pCNode->nType	  = cTemp.nType;
				pCNode->uCommand  = cTemp.uCommand;

				// Select the previous item which
				// in fact is the already selected
				// item.
				m_Commands.SetCurSel( m_Commands.GetCurSel() - 1 );
				SetupControls();
				pSettings->Changed( m_pParser );
				return 0;
			}
		}

		case	EditToolbar::ETID_DOWN:
		{
			// Valid node?
			LPCNODE pCNode = ( LPCNODE )m_Commands.GetItemData( m_Commands.GetCurSel());
			if ( pCNode != ( LPCNODE )LB_ERR )
			{
				// Get it's successor.
				LPCNODE pCNext = pCNode->lpNext;
				
				// Swap both data entries.
				CNODE cTemp;
				cTemp.nType	  = pCNext->nType;
				cTemp.uCommand	  = pCNext->uCommand;
				pCNext->nType	  = pCNode->nType;
				pCNext->uCommand  = pCNode->uCommand;
				pCNode->nType	  = cTemp.nType;
				pCNode->uCommand  = cTemp.uCommand;

				// Select the next item which
				// in fact is the already selected
				// item.
				m_Commands.SetCurSel( m_Commands.GetCurSel() + 1 );
				SetupControls();
				pSettings->Changed( m_pParser );
				return 0;
			}
		}

		case	IDS_INSERT:
			CreateNode( CTYPE_TEXT, ( LPVOID )( LPCTSTR )ClsString( MAKEINTRESOURCE( IDS_NEWINSERT )));
			return 0;

		case	IDS_HARDCODED:
			CreateNode( CTYPE_HARDCODED, ( LPVOID )( ::GetCommandTable()->nCommandID ));
			return 0;

		case	IDS_RUN:
			CreateNode( CTYPE_RUN, ( LPVOID )( _T( "Calc.exe" )));
			return 0;

		case	IDS_OPEN:
			CreateNode( CTYPE_SHELLOPEN, ( LPVOID )( _T( "ReadMe.txt" )));
			return 0;

		case	IDC_KEYS:
			if ( nNotifyCode == LBN_SELCHANGE )
			{
				// Get the current selection.
				LPHASH pSel = GetSelection();
				if ( pSel )
				{
					// Clear the contents of the command list.
					m_Commands.ResetContent();

					// Add commands.
					LPCNODE pNode;
					for ( pNode = pSel->lpCommands->lpFirst; pNode->lpNext; pNode = pNode->lpNext )
						m_Commands.AddString(( LPCTSTR )pNode );
				}
			}
			// Setup controls.
			SetupControls();
			return 0;

		case	IDC_COMMANDS:
			// Double-click?
			if ( nNotifyCode == LBN_DBLCLK )
			{
				// Find the command node.
				LPCNODE pCNode = ( LPCNODE )m_Commands.GetItemData( m_Commands.GetCurSel());
				if ( pCNode != ( LPCNODE )LB_ERR )
				{
					// What's the type?
					switch ( pCNode->nType )
					{
						case	CTYPE_HARDCODED:
						{
							// Open the editor.
							Hardcoded h;
							if ( h.Select( *GetParent(), pCNode ))
							{
								// Refresh list.
								m_Commands.Invalidate();
								pSettings->Changed( m_pParser );
							}
							break;
						}

						case	CTYPE_TEXT:
						{
							// Open the editor.
							TextInsert ti;
							HICON hIcon = ImageList_GetIcon( m_hImages, 1, ILD_NORMAL );
							if ( ti.Edit( *GetParent(), pCNode, hIcon ))
							{
								// Refresh list.
								m_Commands.Invalidate();
								pSettings->Changed( m_pParser );
							}
							if ( hIcon ) ::DestroyIcon( hIcon );
							return 0;
						}
						
						case	CTYPE_RUN:
						case	CTYPE_SHELLOPEN:
						{
							// Open the editor.
							Run r;
							if ( r.Edit( *GetParent(), pCNode ))
							{
								// Refresh list.
								m_Commands.Invalidate();
								pSettings->Changed( m_pParser );
							}
						}
					}
				}
			}
			else
				// Setup GUI.
				SetupControls();
			return 0;
	}
	// Pass to the base class.
	return Page::OnCommand( nNotifyCode, nCtrlID, hWndCtrl );
}

// WM_INITDIALOG handler.
LRESULT KeyboardPage::OnInitDialog( PROPSHEETPAGE * pPropSheetPage )
{
	// Wrap controls.
	m_Tools.Attach( GetDlgItemHandle( IDC_TOOLS ));
	m_Tools.SetupToolbar( TRUE, FALSE, FALSE, FALSE );
	m_ComTools.Attach( GetDlgItemHandle( IDC_COMTOOLS ));
	m_ComTools.SetupToolbar( TRUE, TRUE, FALSE, FALSE );
	m_Keys.Attach( GetDlgItemHandle( IDC_KEYS ));
	m_Commands.Attach( GetDlgItemHandle( IDC_COMMANDS ));

	// Create the menu.
	CreatePopupMenu();

	// Call the base-class.
	return Page::OnInitDialog( pPropSheetPage );
}

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)
Netherlands Netherlands
I have been programming for a hobby since 1985. I have started programming on the C= 64. After that I migrated to the C= Amiga which I traded in for a PC back in 1997 I believe. Back in 2000 I decided to lose a hobby and start developing software for a living.

Currently I am working mainly in developing software for building security and access control systems.

Comments and Discussions