Click here to Skip to main content
15,879,348 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 702.7K   26.8K   263  
Syntax coloring, multi-level undo/redo editor control.
//
//	syntax.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.
SyntaxPage::SyntaxPage() 
{
	// Setup icon.
	m_nIcon = IDI_SYNTAX;
}

// Destructor.
SyntaxPage::~SyntaxPage()
{
}

// Setup the toolbar.
void SyntaxPage::SetupToolbar()
{
	// Get current list selection and count.
	int nSel = m_Blocks.GetCurSel();
	int nCnt = m_Blocks.GetCount();

	// Do the toolbar buttons.
	m_Tools.EnableButton( EditToolbar::ETID_DELETE, ( BOOL )( nSel != LB_ERR ));
	m_Tools.EnableButton( EditToolbar::ETID_UP,     ( BOOL )( nSel != LB_ERR && nSel > 0 ));
	m_Tools.EnableButton( EditToolbar::ETID_DOWN,   ( BOOL )( nSel != LB_ERR && nSel < nCnt - 1 ));

	// Also for the common colors.
	nSel = m_Common.GetCurSel();
	m_Color.EnableButton( EditToolbar::ETID_COLOR, ( BOOL )( nSel != LB_ERR ));
}

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

	// Save parser.
	m_pParser = pParser;

	// Setup lists.
	m_Blocks.SetParser( pParser );
	m_Common.SetParser( pParser );
	m_Blocks.SetBlockList();
	m_Common.SetCommonList();

	// Setup other controls.
	m_Escape.SetWindowText( pParser->cEscape ? ( LPCTSTR )ClsString( pParser->cEscape ) : NULL );
	m_Syntax.SetCheck( pParser->bSyntaxColoring ? BST_CHECKED : BST_UNCHECKED );

	// Setup the toolbar.
	SetupToolbar();
}

// Save a color value.
void SyntaxPage::SaveColor( ClsStdioFile *pFile, LPPARSER pParser, LPCTSTR pszDesc, DWORD dwFlag, int nIndex )
{
	if ( pParser->crColors[ nIndex ] == CLR_DEFAULT )
		pFile->PrintF( "%s*\n", pszDesc );
	else
	{
		pFile->PrintF( pszDesc );
		pFile->PrintF( ( pParser->dwColorFlags & dwFlag ) ? _T( "*\n" ) : _T( "%ld,%ld,%ld\n" ), GetRValue( pParser->crColors[ nIndex ] ), 
													GetGValue( pParser->crColors[ nIndex ] ), 
													GetBValue( pParser->crColors[ nIndex ] ));
	}
}

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

		// Write colors.
		SaveColor( pFile, pParser, _T( "TextRGB=" ),			CF_DEFAULT_TEXT, CARR_TEXT );
		SaveColor( pFile, pParser, _T( "BackgroundRGB=" ),		CF_DEFAULT_BACKGROUND, CARR_BACKGROUND );
		SaveColor( pFile, pParser, _T( "BackgroundReadOnlyRGB=" ),	CF_DEFAULT_BACKGROUND_RO, CARR_BACKGROUND_READONLY );
		SaveColor( pFile, pParser, _T( "MarginRGB=" ),			CF_DEFAULT_MARGIN, CARR_SELECTION_MARGIN );
		SaveColor( pFile, pParser, _T( "BookmarkRGB=" ),		CF_DEFAULT_BOOKMARK, CARR_BOOKMARK );
		SaveColor( pFile, pParser, _T( "NumberRGB=" ),			CF_DEFAULT_NUMBER, CARR_NUMBER );
		SaveColor( pFile, pParser, _T( "DelimiterRGB=" ),		CF_DEFAULT_DELIMITER, CARR_DELIMITER );		  
		SaveColor( pFile, pParser, _T( "SelectedTextRGB=" ),		CF_DEFAULT_SELECTED_TEXT, CARR_SELECTED_TEXT );
		SaveColor( pFile, pParser, _T( "SelectedBackgroundRGB=" ),	CF_DEFAULT_SELECTED_BKGND, CARR_SELECTED_BKGND );
		SaveColor( pFile, pParser, _T( "LineNumberRGB=" ),		CF_DEFAULT_LINE_NUMBERS, CARR_LINE_NUMBERS );
		SaveColor( pFile, pParser, _T( "BkgndLineNumberRGB=" ),		CF_DEFAULT_LINE_NUMBERS_BKGND, CARR_BACKGROUND_LINE_NUMBERS );
		SaveColor( pFile, pParser, _T( "BkgndNumberRGB="),		CF_DEFAULT_BACKGROUND_NUMBER, CARR_BACKGROUND_NUMBER );
		SaveColor( pFile, pParser, _T( "BkgndDelimiterRGB="),		CF_DEFAULT_BACKGROUND_DELIMITER, CARR_BACKGROUND_DELIMITER );
		SaveColor( pFile, pParser, _T( "BracketMatchRGB="),		CF_DEFAULT_BRACKET_MATCH, CARR_BRACKET_MATCH );
		SaveColor( pFile, pParser, _T( "LineHighlightRGB="),		CF_DEFAULT_LINE_HIGHLIGHT, CARR_LINE_HIGHLIGHT );
		SaveColor( pFile, pParser, _T( "HyperlinkRGB="),		CF_DEFAULT_HYPERLINK, CARR_HYPERLINK );
		SaveColor( pFile, pParser, _T( "BkgndHyperlinkRGB="),		CF_DEFAULT_BACKGROUND_HYPERLINK, CARR_BACKGROUND_HYPERLINK );


		// Write the screen and printer
		// fonts.
		pFile->PrintF( _T( "ScreenFont=%s,%ld,%ld,%ld,%ld,%ld\n" ), pParser->lfScreenFont.lfFaceName, pParser->lfScreenFont.lfHeight, pParser->lfScreenFont.lfWeight, pParser->lfScreenFont.lfCharSet, pParser->lfScreenFont.lfItalic, pParser->lfScreenFont.lfUnderline );

		// Write general syntax coloring comment.
		pFile->PrintF( ClsString( MAKEINTRESOURCE( IDS_COMMENT_SYNTAXGENERAL )));

		// Save general syntax coloring settings.
		if ( pParser->cEscape ) pFile->PrintF( _T( "Escape=%lc\n" ), pParser->cEscape );
		pFile->PrintF( _T( "SyntaxColoring=%lc\n" ), pParser->bSyntaxColoring ? _T( 'Y' ) : _T( 'N' ));
		pFile->PrintF( _T( "Case=%lc\n" ), pParser->bCaseOn ? _T( 'Y' ) : _T( 'N' ));

		// Write blocks comment.
		pFile->PrintF( ClsString( MAKEINTRESOURCE( IDS_COMMENT_BLOCKS )));

		// Write blocks.
		for ( int i = 0; i < ::ArrayGetSize( pParser->lpaBlocks ); i++ )
		{
			// Get block.
			LPBLOCK lpBlock = ( LPBLOCK )::ArrayGetAt( pParser->lpaBlocks, i );

			// Write block opening.
			if ( lpBlock->pszName ) pFile->PrintF( _T( "Block=%s\n" ), lpBlock->pszName );
			else			pFile->PrintF( _T( "Block\n" ));

			// Write color commands.
			pFile->PrintF( _T( "\tRGB=%ld,%ld,%ld\n" ), GetRValue( lpBlock->crColor ), 
								    GetGValue( lpBlock->crColor ), 
								    GetBValue( lpBlock->crColor ));

			if ( lpBlock->crBgColor == CLR_DEFAULT )
				pFile->PrintF( _T( "\tBkRGB=*\n" ));
			else
				pFile->PrintF( _T( "\tBkRGB=%ld,%ld,%ld\n" ), GetRValue( lpBlock->crBgColor ), 
									      GetGValue( lpBlock->crBgColor ), 
									      GetBValue( lpBlock->crBgColor ));

			// Write start command.
			pFile->PrintF( _T( "\tStart=%s\n" ), lpBlock->pszStart );

			// At the start of the line?
			if ( lpBlock->bStartOfLine )
				pFile->PrintF( _T( "\tStartOfLine=Y\n" ));

			// Write end command.
			if ( lpBlock->pszEnd ) pFile->PrintF( _T( "\tEnd=%s\n" ), lpBlock->pszEnd == END_OF_LINE ? _T( "\\n" ) : lpBlock->pszEnd );
			else		       pFile->PrintF( _T( ";\tEnd=\n" ));

			// Write block terminator.
			pFile->PrintF( _T( "EndBlock\n\n" ));
		}

		// Write keywords comment.
		pFile->PrintF( ClsString( MAKEINTRESOURCE( IDS_COMMENT_KEYWORDS )));

		// Initialize buffer list.
		KEYLIST kl;
		BOOL bKeywords = FALSE;
		NewList(( LPLIST )&kl );
		
		// Convert hashes.
		if ( KeywordEdit::Hash2List( &kl, pParser ))
		{
			// Iterate nodes.
			LPKEYWORDS pKW;
			for ( pKW = kl.lpFirst; pKW->lpNext; pKW = pKW->lpNext )
			{
				// Keywords command written?
				if ( bKeywords == FALSE )
				{
					// Write keywords command.
					pFile->PrintF( _T( "Keywords\n" ));
					bKeywords = TRUE;
				}

				// Write color values.
				pFile->PrintF( _T( "RGB=%ld,%ld,%ld\n" ), GetRValue( pKW->crColor ), 
									  GetGValue( pKW->crColor ), 
									  GetBValue( pKW->crColor ));
				if ( pKW->crBgColor == CLR_DEFAULT ) pFile->PrintF( "BkRGB=*\n" );
				else
					pFile->PrintF( _T( "BkRGB=%ld,%ld,%ld\n" ), GetRValue( pKW->crBgColor ), 
										  GetGValue( pKW->crBgColor ), 
										  GetBValue( pKW->crBgColor ));

				// Write keywords from this group.
				for ( int i = 0; i < ::ArrayGetSize( pKW->lpaKeywords ); i++ )
					pFile->PrintF( _T( "%s\n" ), *(( LPCTSTR * )::ArrayGetAt( pKW->lpaKeywords, i )));
			}

			// Free the converted list.
			KeywordEdit::FreeKeyList( &kl );
		}
		else
			return FALSE;

		// Write endkeywords command.
		if ( bKeywords )
			pFile->PrintF( _T( "EndKeywords\n" ));
	}
	catch ( ClsException& e )
	{
		// Error...
		UNREFERENCED_PARAMETER( e );
		return FALSE;
	}
	return TRUE;
}

// Window procedure.
LRESULT SyntaxPage::WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	// What's the message?
	switch ( uMsg )
	{
		case	CPN_SELENDOK:
		{
			// Get the current selection.
			int nSel = m_Common.GetCurSel();
			_ASSERT( nSel != LB_ERR );

			// Default?
			if ( wParam == CLR_DEFAULT )
			{
				// Setup default.
				m_pParser->dwColorFlags |= m_Common.GetFlagArray()[ nSel ];
				m_pParser->crColors[ nSel ] = m_Common.GetDefArray()[ nSel ];
			}
			else
			{
				// Setup the color.
				m_pParser->dwColorFlags &= ~m_Common.GetFlagArray()[ nSel ];
				m_pParser->crColors[ nSel ] = wParam;
			}
			// Change made.
			m_Common.Invalidate();
			pSettings->Changed( m_pParser );
			return 0;
		}
	}
	// Baseclass...
	return Page::WindowProc( uMsg, wParam, lParam );
}

// WM_COMMAND handler.
LRESULT SyntaxPage::OnCommand( UINT nNotifyCode, UINT nCtrlID, HWND hWndCtrl )
{
	// What's the trouble...
	switch ( nCtrlID )
	{
		case	EditToolbar::ETID_COLOR:
		{
			// Get the current selection.
			int nSel = m_Common.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_Color.GetItemRect( 6, rc );
				m_Color.ClientToScreen( rc );

				// Create the popup. The popup will automatically destroy
				// itself.
				new ClsColorPopup( ClsPoint( rc.Left(), rc.Bottom()), 
						   m_pParser->dwColorFlags & m_Common.GetFlagArray()[ nSel ] ? CLR_DEFAULT : m_pParser->crColors[ nSel ], 
						   this,
						   0,
						   ClsString( MAKEINTRESOURCE( IDS_DEFAULT )), 
						   ClsString( MAKEINTRESOURCE( IDS_CUSTOM )), 
						   NULL, 
						   TRUE,
						   FALSE );
			} 
			return 0;
		}

		case	EditToolbar::ETID_UP:
			// Move entry up.
			m_Blocks.MoveSelUp();
			SetupToolbar();
			return 0;

		case	EditToolbar::ETID_DOWN:
			// Move entry down.
			m_Blocks.MoveSelDown();
			SetupToolbar();
			return 0;

		case	EditToolbar::ETID_DELETE:
			// Remove entry.
			m_Blocks.RemoveSel();
			SetupToolbar();
			return 0;

		case	EditToolbar::ETID_NEW:
		{
			// Create a block as follows:
			//
			//		Name=New Block...
			//			RGB=50,100,150
			//			Start=;
			//			End=\n
			//		EndBlock
			ClsString strName( MAKEINTRESOURCE( IDS_NEW_BLOCK ));
			BLOCK	  bBlock;
			bBlock.pszName = ( LPTSTR )::ArrayAllocMem( m_pParser->lpaBlocks, ( strName.GetStringLength() + 1 ) * sizeof( TCHAR ));
			if ( bBlock.pszName )
			{
				// Copy the name.
				_tcscpy( bBlock.pszName, strName );

				// Allocate start string.
				bBlock.pszStart = ( LPTSTR )::ArrayAllocMem( m_pParser->lpaBlocks, ( _tcslen( _T( ";" )) + 1 ) * sizeof( TCHAR ));
				if ( bBlock.pszStart )
				{
					// Copy the name.
					_tcscpy( bBlock.pszStart, _T( ";" ));

					// Setup the length.
					bBlock.nStart = _tcslen( _T( ";" ));

					// Block end is EOL.
					bBlock.pszEnd = END_OF_LINE;

					// Add it to the array.
					if ( ::ArrayAdd( m_pParser->lpaBlocks, &bBlock, 1 ))
					{
						// Get the added block.
						LPBLOCK	lpAdded = ( LPBLOCK )::ArrayGetAt( m_pParser->lpaBlocks, ::ArrayGetSize( m_pParser->lpaBlocks ) - 1 );

						// Setup color.
						lpAdded->crColor = RGB( 50, 100, 150 );
						lpAdded->crBgColor = CLR_DEFAULT;

						// We have to re-add the array contents since
						// adding items to the array may have
						// caused a re-allocation of the array
						// elements which, in turn, causes the
						// listview contents to be faulty.
						m_Blocks.ResetContent();
						for ( int i = 0; i < ::ArrayGetSize( m_pParser->lpaBlocks ); i++ )
							m_Blocks.AddString(( LPCTSTR )::ArrayGetAt( m_pParser->lpaBlocks, i ));

						// Select last added entry.
						m_Blocks.SetCurSel( ::ArrayGetSize( m_pParser->lpaBlocks ) - 1 );

						// Setup toolbar.
						SetupToolbar();
						
						// Changes made...
						pSettings->Changed( m_pParser );

						// Edit it.
						BlockEdit be;
						if ( be.EditBlock( *GetParent(), lpAdded, m_pParser ))
							// Refresh the list.
							m_Blocks.Invalidate();
						return 0;
					}
					::ArrayFreeMem( m_pParser->lpaBlocks, bBlock.pszStart );
				}
				::ArrayFreeMem( m_pParser->lpaBlocks, bBlock.pszName );
			}
			MessageBox( ClsString( MAKEINTRESOURCE( IDS_NO_MEMORY )), ClsGetApp()->GetAppTitle(), MB_ICONERROR | MB_OK );
			return 0;
		}

		case	IDC_COMMON:
			// Setup toolbar.
			SetupToolbar();
			return 0;
			
		case	IDC_BLOCKS:
			// Double-click?
			if ( nNotifyCode == LBN_DBLCLK )
			{
				// Get the current selection.
				int nSel = m_Blocks.GetCurSel();

				// Edit it.
				BlockEdit be;
				if ( be.EditBlock( *GetParent(), ( LPBLOCK )::ArrayGetAt( m_pParser->lpaBlocks, nSel ), m_pParser ))
				{
					// Refresh the list.
					m_Blocks.Invalidate();
					pSettings->Changed( m_pParser );
				}
			}
			else
				// Setup the toolbar.
				SetupToolbar();
			return 0;

		case	IDC_ESCAPE:
		{
			TCHAR	szBuf[ 2 ] = { 0 };

			// Pickup the buffer.
			m_Escape.GetWindowText( szBuf, 2 );

			// did it really change?
			if ( m_pParser->cEscape != szBuf[ 0 ] )
			{
				// Save escape character.
				m_pParser->cEscape = szBuf[ 0 ];
			
				// Changes made...
				pSettings->Changed( m_pParser );
			}
			return 0;
		}

		case	IDC_SYNTAX:
			// Get setting.
			m_pParser->bSyntaxColoring = ( BOOL )( m_Syntax.GetCheck() == BST_CHECKED );
			pSettings->Changed( m_pParser );
			return 0;

		case	IDC_FONT:
		{
			// Copy the font input.
			LOGFONT lf = m_pParser->lfScreenFont;

			// Popup font dialog.
			ClsFontDialog fd;
			if ( fd.DoModal( this, &lf, CF_BOTH | CF_FIXEDPITCHONLY | CF_INITTOLOGFONTSTRUCT ))
			{
				// Copy the contents.
				m_pParser->lfScreenFont = lf;
			
				// Changes made...
				pSettings->Changed( m_pParser );
			}
			return 0;
		}

		case	IDC_KEYWORDS:
		{
			// Open the keyword editor...
			KeywordEdit ke;
			ke.Edit( *GetParent(), m_pParser );
			return 0;
		}
	}
	// Pass to the base class.
	return Page::OnCommand( nNotifyCode, nCtrlID, hWndCtrl );
}

// WM_INITDIALOG handler.
LRESULT SyntaxPage::OnInitDialog( PROPSHEETPAGE * pPropSheetPage )
{
	// Setup toolbars.
	m_Tools.Attach( GetDlgItemHandle( IDC_TOOLS ));
	m_Tools.SetupToolbar( TRUE, TRUE, FALSE, FALSE );
	m_Color.Attach( GetDlgItemHandle( IDC_COLOR ));
	m_Color.SetupToolbar( FALSE, FALSE, FALSE, TRUE );
	
	// Setup listboxes.
	m_Blocks.Attach( GetDlgItemHandle( IDC_BLOCKS ));
	m_Common.Attach( GetDlgItemHandle( IDC_COMMON ));

	// And the rest of the controls.
	m_Escape.Attach( GetDlgItemHandle( IDC_ESCAPE ));
	m_Escape.SetLimitText( 1 );
	m_Syntax.Attach( GetDlgItemHandle( IDC_SYNTAX ));

	// 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