Click here to Skip to main content
15,896,269 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 711.5K   26.8K   263  
Syntax coloring, multi-level undo/redo editor control.
//
//	keywords.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

#define CS_FORE		0x1000
#define CS_BACK		0x1001

// Construction.
KeywordEdit::KeywordEdit()
{
}

// Destruction.
KeywordEdit::~KeywordEdit()
{
}

// Setup GUI.
void KeywordEdit::SetupControls()
{
	// Get list selection.
	int nSel = m_List.GetCurSel();

	// Disable/enable GUI elements.
	m_Tools.EnableButton( EditToolbar::ETID_DELETE,  ( BOOL )( nSel != LB_ERR ));
	m_Tools.EnableButton( EditToolbar::ETID_COLOR,   ( BOOL )( nSel != LB_ERR ));
	m_Tools.EnableButton( EditToolbar::ETID_BGCOLOR, ( BOOL )( nSel != LB_ERR ));
	m_Keys.EnableButton(  EditToolbar::ETID_EDIT,    ( BOOL )( nSel != LB_ERR ));
	m_Keys.EnableButton(  EditToolbar::ETID_INSERT,  ( BOOL )( nSel != LB_ERR ));
}

// WM_INITDIALOG handler.
LRESULT KeywordEdit::OnInitDialog( LPPROPSHEETPAGE pPsp )
{
	// Setup controls.
	m_Tools.Attach( GetDlgItemHandle( IDC_TOOLS ));
	m_Tools.SetupToolbar( TRUE, FALSE, FALSE, TRUE, TRUE );
	m_Keys.Attach( GetDlgItemHandle( IDC_KEYS ));
	m_Keys.SetupToolbar( FALSE, FALSE, TRUE, FALSE );
	m_List.Attach( GetDlgItemHandle( IDC_LIST ));
	m_KeyList.Attach( GetDlgItemHandle( IDC_WORDS ));
	m_Case.Attach( GetDlgItemHandle( IDC_CASE ));

	// Setup values.
	m_Case.SetCheck( m_bCase ? BST_CHECKED : BST_UNCHECKED );
	m_List.SetKeywordList( &m_KeyLst );
	
	// Setup the GUI.
	SetupControls();
	return TRUE;
}

// Windows procedure...
LRESULT KeywordEdit::WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	// What's the message?
	switch ( uMsg )
	{
		case	WM_CLOSE:
			// Abort...
			EndDialog( FALSE );
			return 0;
			
		case	CPN_SELENDOK:
		{
			// Get the current selection.
			int nSel = m_List.GetCurSel();
			_ASSERT( nSel != LB_ERR );

			// Obtain pointer to the data.
			LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( nSel );
			if ( pKW != ( LPKEYWORDS )LB_ERR )
			{
				// Setup the selection...
				if ( lParam      == CS_FORE ) pKW->crColor   = wParam;
				else if ( lParam == CS_BACK ) pKW->crBgColor = wParam;
				Invalidate();

				// Change made.
				pSettings->Changed( m_pParser );
			}
			return 0;
		}
	}
	// Baseclass...
	return ClsDialog::WindowProc( uMsg, wParam, lParam );
}

// WM_COMMAND message handler.
LRESULT KeywordEdit::OnCommand( UINT nNotifyCode, UINT nCtrlID, HWND hWndCtrl )
{
	// What's the ID?
	switch ( nCtrlID )
	{
		case	EditToolbar::ETID_EDIT:
		{
			// Valid entry?
			LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( m_List.GetCurSel());
			if ( pKW != ( LPKEYWORDS )LB_ERR )
			{
				// Edit the keywords.
				KWEdit kwe;
				kwe.Edit( *this, pKW, this );
			}
			return 0;
		}

		case	EditToolbar::ETID_INSERT:
		{
			// Popup the file dialog.
			ClsFileDialog fd;
			fd.Filters() = _T( "Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0\0" );
			fd.Caption().LoadString( IDS_INSERT_KEYWORDS );
			
			if ( fd.DoModal( this, NULL, NULL, TRUE, OFN_FILEMUSTEXIST ))
			{
				// Get the selected filename.
				ClsString name;
				fd.GetName( 0, name );

				// Read the file and add the read keywords
				// to the selected node.
				ReadKeywords( name );

				// Make sure the keywords are
				// displayed.
				SendMessage( WM_COMMAND, MAKEWPARAM( IDC_LIST, LBN_SELCHANGE ), ( LPARAM )m_List.GetSafeHWND());
			}
			return 0;
		}

			
		case	EditToolbar::ETID_COLOR:
		{
			// Get the current selection.
			int nSel = m_List.GetCurSel();

			// Only show the popup when there is
			// a valid selection.
			if ( nSel != LB_ERR )
			{
				// Get the position of the selected entry.
				ClsRect rc;
				m_Tools.GetItemRect( 6, rc );
				m_Tools.ClientToScreen( rc );

				// Obtain the entry data.
				LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( nSel );
				if ( pKW != ( LPKEYWORDS )LB_ERR )
				{
					// Create the popup. The popup will automatically destroy
					// itself.
					new ClsColorPopup( ClsPoint( rc.Left(), rc.Bottom()), 
							   pKW->crColor, 
							   this,
							   CS_FORE,
							   NULL, 
							   ClsString( MAKEINTRESOURCE( IDS_CUSTOM )), 
							   NULL, 
							   TRUE,
							   FALSE );
				}
			}
			return 0;
		}

		case	EditToolbar::ETID_BGCOLOR:
		{
			// Get the current selection.
			int nSel = m_List.GetCurSel();

			// Only show the popup when there is
			// a valid selection.
			if ( nSel != LB_ERR )
			{
				// Get the position of the selected entry.
				ClsRect rc;
				m_Tools.GetItemRect( 7, rc );
				m_Tools.ClientToScreen( rc );

				// Obtain the entry data.
				LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( nSel );
				if ( pKW != ( LPKEYWORDS )LB_ERR )
				{
					// Create the popup. The popup will automatically destroy
					// itself.
					new ClsColorPopup( ClsPoint( rc.Left(), rc.Bottom()), 
							   pKW->crBgColor, 
							   this,
							   CS_BACK,
							   ClsString( MAKEINTRESOURCE( IDS_TRANSPARENT )), 
							   ClsString( MAKEINTRESOURCE( IDS_CUSTOM )), 
							   NULL, 
							   TRUE,
							   FALSE );
				}
			}
			return 0;
		}

		case	IDC_LIST:
		{
			// Selection change?
			if ( nNotifyCode == LBN_SELCHANGE )
			{
				// Valid entry?
				LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( m_List.GetCurSel());
				if ( pKW != ( LPKEYWORDS )LB_ERR )
				{
					// Reset words content.
					m_KeyList.ResetContent();

					// Add keywords.
					m_KeyList.SetRedraw( FALSE );
					for ( int i = 0; i < ::ArrayGetSize( pKW->lpaKeywords ); i++ )
						m_KeyList.AddString(( LPCTSTR )*(( LPTSTR * )::ArrayGetAt( pKW->lpaKeywords, i )));
					m_KeyList.SetRedraw();

					// Setup UI.
					SetupControls();
				}
			}
			return 0;
		}

		case	EditToolbar::ETID_DELETE:
		{			
			// Valid entry?
			LPKEYWORDS pKW = ( LPKEYWORDS )m_List.GetItemData( m_List.GetCurSel());
			if ( pKW != ( LPKEYWORDS )LB_ERR )
			{
				ClsMessageBox mb;
				mb.Title() = ClsGetApp()->GetAppTitle();
				mb.Buttons().LoadString( IDS_YESNO );
				mb.Body().LoadString( IDS_KEYWORD_DELETE );
				mb.Flags() = ClsMessageBox::MBF_ICONEXCLAMATION;
				// Sure?
				if ( mb.MsgBox( GetSafeHWND()))
				{
					// Save current selection
					// position.
					int nSel = m_List.GetCurSel();
					int nPos = nSel;

					// Select the next one or
					// the previous one.
					if ( nSel == m_List.GetCount() - 1 ) nSel--;
					else				     nSel++;

					// Select the other item.
					m_List.SetCurSel( nSel );
					
					// Setup UI.
					SetupControls();

					// Clear the list contents
					m_KeyList.ResetContent();

					// Reset selection.
					SendMessage( WM_COMMAND, MAKEWPARAM( IDC_LIST, LBN_SELCHANGE ), ( LPARAM )m_List.GetSafeHWND());

					// Remove the selection.
					m_List.DeleteString( nPos );

					// Remove the node.
					Remove(( LPNODE )pKW );

					// Free the keyword array and the node.
					if ( pKW->lpaKeywords ) ::ArrayDelete( pKW->lpaKeywords );
					::FreePooled( pParserPool, pKW );
				}	
			}
			return 0;
		}

		case	EditToolbar::ETID_NEW:
		{
			// Allocate a new node.
			LPKEYWORDS pKW = ( LPKEYWORDS )::AllocPooled( pParserPool, sizeof( KEYWORDS ));
			if ( pKW )
			{
				// Allocate an array.
				pKW->lpaKeywords = ::ArrayCreate( 0, 500, sizeof( LPTSTR * ));
				if ( pKW->lpaKeywords )
				{
					// Set color.
					pKW->crColor   = ::GetSysColor( COLOR_WINDOWTEXT );
					pKW->crBgColor = CLR_DEFAULT;

					// Add the node to the list.
					AddTail(( LPLIST )&m_KeyLst, ( LPNODE )pKW );

					// Add it to the listview and select it.
					int nPos = m_List.AddString(( LPCTSTR )pKW );
					m_List.SetCurSel( nPos );

					// Clear the keywords list.
					m_KeyList.ResetContent();

					// Setup UI.
					SetupControls();
					return 0;
				}
				else
					::FreePooled( pParserPool, pKW );
			}
			// Error.
			MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
			return 0;
		}

		case	IDC_CASE:
			// Get value.
			m_bCase = ( BOOL )( m_Case.GetCheck() == BST_CHECKED );
			return 0;

		case	IDC_OK:
			// Return OK
			EndDialog( TRUE );
			return 0;

		case	IDC_CANCEL:
			// Cancel.
			EndDialog( FALSE );
			return 0;
	}
	// Pass onto the base class.
	return ClsDialog::OnCommand( nNotifyCode, nCtrlID, hWndCtrl );
}

// Create a string copy in the
// array memory.
LPTSTR KeywordEdit::StringArrayCopy( LPARRAY lpa, LPTSTR pszString )
{
	// Allocate memory from the
	// array.
	LPTSTR pszCopy = ( LPTSTR )::ArrayAllocMem( lpa, ( _tcslen( pszString ) + 1 ) * sizeof( TCHAR ));
	if ( pszCopy )
		// Copy the string.
		_tcscpy( pszCopy, pszString );
	return pszCopy;
}

// Free a keyword list.
void KeywordEdit::FreeKeyList( LPKEYLIST pKeyList )
{
	// Iterate the nodes.
	LPKEYWORDS	pKey;
	while (( pKey = ( LPKEYWORDS )RemHead(( LPLIST )pKeyList )) != NULL )
	{
		// Free the array.
		if ( pKey->lpaKeywords )
			::ArrayDelete( pKey->lpaKeywords );

		// And the node.
		::FreePooled( pParserPool, pKey );
	}
}

// Free keyword hashes.
void KeywordEdit::FreeKeyHashes()
{
	// Free the keyword hashes.
	for ( int i = 0; i < 256; i++ )
	{
		LPKEYHASH pNext;
		for ( LPKEYHASH pHash = m_pParser->aKeywords[ i ]; pHash; pHash = pNext )
		{
			// Save next pointer.
			pNext = pHash->lpNext;

			// Delete keyword string.
			if ( pHash->pszKeyword ) 
				::FreePooled( pParserPool, pHash->pszKeyword );

			// And the hash entry itself.
			::FreePooled( pParserPool, pHash );
		}
	}
}

// Add a keyword to a node.
BOOL KeywordEdit::AddKeyword2Node( LPKEYWORDS pKeywords, LPTSTR pszKeyword )
{
	// Add the keyword string to the
	// array of this color.
	LPTSTR	pszCopy = StringArrayCopy( pKeywords->lpaKeywords, pszKeyword );
	if ( pszCopy )
	{
		// Add it to the array.
		if ( ::ArrayAdd( pKeywords->lpaKeywords, &pszCopy, 1 ) == TRUE )
			return TRUE;
	}
	return FALSE;
}

// Convert keywords hashes to
// a keyword list.
BOOL KeywordEdit::Hash2List( LPKEYLIST pKeyList, LPPARSER pParser )
{
	BOOL		bAdded = FALSE;

	// Initialize the list.
	NewList(( LPLIST )pKeyList );

	// Iterate the hashes.
	for ( int i = 0; i < 256; i++ )
	{
		LPKEYHASH pHash, pNext;
		for ( pHash = pParser->aKeywords[ i ]; pHash; pHash = pNext )
		{
			// Save next pointer.
			pNext = pHash->lpNext;

			// See if this color has
			// already been added?
			LPKEYWORDS pKey;
			for ( pKey = pKeyList->lpFirst; pKey->lpNext; pKey = pKey->lpNext )
			{
				// Are the colors the same as the
				// new keyword colors?
				if ( pKey->crColor == pHash->crColor && pKey->crBgColor == pHash->crBkColor )
				{
					// Yes. Simply add the keyword string to the
					// array of this color.
					if ( AddKeyword2Node( pKey, pHash->pszKeyword ) == FALSE )
					{
						// Error.
						FreeKeyList( pKeyList );
						return FALSE;
					}

					// Added keyword.
					bAdded = TRUE;
					break;
				}
			}

			// Added keyword?
			if ( bAdded == FALSE )
			{
				// Allocate a new keyword node.
				if (( pKey = ( LPKEYWORDS )::AllocPooled( pParserPool, sizeof( KEYWORDS ))) != NULL )
				{
					// Create a keyword array.
					if (( pKey->lpaKeywords = ::ArrayCreate( 0, 500, sizeof( LPTSTR * ))) != NULL )
					{
						// Setup the node colors.
						pKey->crColor   = pHash->crColor;
						pKey->crBgColor = pHash->crBkColor;

						// Add to the list.
						AddTail(( LPLIST )pKeyList, ( LPNODE )pKey );

						// Add the keyword.
						if ( AddKeyword2Node( pKey, pHash->pszKeyword ) == FALSE )
						{
							// Error.
							FreeKeyList( pKeyList );
							return FALSE;
						} 
					}
					else
					{
						// Error.
						::FreePooled( pParserPool, pKey );
						FreeKeyList( pKeyList );
						return FALSE;
					}
				}
				else
				{
					// Error.
					FreeKeyList( pKeyList );
					return FALSE;
				}
			}
			// Clear added flag.
			bAdded = FALSE;
		}
	}

	// Sort the arrays.
	for ( LPKEYWORDS pKey = pKeyList->lpFirst; pKey->lpNext; pKey = pKey->lpNext )
		::ArraySort( pKey->lpaKeywords, ( COMPFUNC )CompareKeywords );

	return TRUE;
}

// Read keywords from a file.
BOOL KeywordEdit::ReadKeywords( LPCTSTR pszFileName )
{
	// Get item position and data.
	int		nPos = m_List.GetCurSel();
	_ASSERT( nPos != LB_ERR );
	LPKEYWORDS	pKey = ( LPKEYWORDS )m_List.GetItemData( nPos );
	ClsStdioFile	file;

	// Valid node?
	if ( pKey != ( LPKEYWORDS )LB_ERR )
	{
		try
		{
			// Open the file.
			file.Open( pszFileName, _T( "ra" ));

			// Read the file...
			TCHAR szBuffer[ 1024 ], *pszPtr, *pszCopy;
			while (( pszPtr = file.GetS( szBuffer, 1024 )) != NULL )
			{
				// Skip leading blanks.
				while ( _istspace( *pszPtr )) pszPtr++;

				// Also remove trailing
				// blanks.
				RemoveTrailingBlanks( pszPtr );

				// Any text left?
				if ( _tcslen( pszPtr ))
				{
					// Create a copy of the
					// input string.
					if (( pszCopy = StringArrayCopy( pKey->lpaKeywords, pszPtr )) != NULL )
					{
						 // Add it to the array.
						if ( ::ArrayAdd( pKey->lpaKeywords, &pszCopy, 1 ) == FALSE )
						 {
							// Error...
							MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
							break;
						 }
					}
					else
					{
						// Error...
						MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
						break;
					}
				}
			}

			// Close the input file.
			file.Close();
		}
		catch( ClsException& ex )
		{
			UNREFERENCED_PARAMETER( ex );
			return FALSE;
		}
	}
	// Sort the array.
	::ArraySort( pKey->lpaKeywords, ( COMPFUNC )CompareKeywords );
	return TRUE;
}

// Use the edited keyword list.
BOOL KeywordEdit::UseKeywords( LPKEYLIST pKeyList )
{
	// Default the parser.
	PARSER pParser;
	::DefaultParser( &pParser );

	// Now we are going to use this parser to 
	// add the edited keywords to. When this 
	// does not fail we free the old hashes
	// and copy these back to the parser.
	for ( LPKEYWORDS pKey = pKeyList->lpFirst; pKey->lpNext; pKey = pKey->lpNext )
	{
		// Iterate the array.
		for ( int i = 0; i < ::ArrayGetSize( pKey->lpaKeywords ); i++ )
		{
			// Get string pointer.
			LPTSTR	pszString = *(( LPTSTR * )::ArrayGetAt( pKey->lpaKeywords, i ));

			// Add it to the hash table.
			if ( ::AddKeyword( pParserPool, &pParser, pszString, pKey->crColor, pKey->crBgColor ) == FALSE )
			{
				// Free node.
				::FreeParserNode( pParserPool, &pParser);

				// Free the list and fail.
				FreeKeyList( pKeyList );
				return FALSE;
			}
		}
	}

	// Free old hashes.
	FreeKeyHashes();

	// Free the keylist.
	FreeKeyList( pKeyList );

	// Setup the new hashes.
	for ( int i = 0; i < 256; i++ )
		m_pParser->aKeywords[ i ] = pParser.aKeywords[ i ];

	return TRUE;
}

// Remove trailing blanks.
void KeywordEdit::RemoveTrailingBlanks( LPTSTR lpszString )
{
	int	nLength = _tcslen( lpszString ) - 1;

	// Replace all trailing white spaces
	// by a 0 character.
	while ( _istspace( lpszString[ nLength ] ))
		lpszString[ nLength-- ] = _T( '\0' );
}

// Keyword comparisson
// routine.
int _cdecl KeywordEdit::CompareKeywords( LPTSTR *ppStr1, LPTSTR *ppStr2 )
{
	return _tcsicmp( *ppStr1, *ppStr2 );
}

// Open the keysword editor.
void KeywordEdit::Edit( ClsWindow& Parent, LPPARSER pParser )
{
	// Save parser pointer.
	m_pParser = pParser;
	
	// Save current case setting.
	m_bCase = pParser->bCaseOn;

	// Convert the hash table into a list.
	if ( Hash2List( &m_KeyLst, pParser ))
	{
		// Open the dialog.
		if ( DoModal( Parent, IDD_KEYWORDS ) <= 0 )
			// Free the list.
			FreeKeyList( &m_KeyLst );
		else
		{
			// Use the changes.
			if ( UseKeywords( &m_KeyLst ) == FALSE )
				// Error...
				MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
			
			// Save case setting.
			pParser->bCaseOn = m_bCase;
			
			// Changes where made.
			pSettings->Changed( pParser );
		}
	}
	else
		// Error...
		MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
}

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