Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C++

HexEdit - Window Binary File Editor

Rate me:
Please Sign up or sign in to vote.
4.96/5 (137 votes)
17 Oct 2012MIT45 min read 492.3K   22.3K   321  
Open-source hex editor with powerful binary templates
// Control.cpp : implements control bars and controls thereon
//
// Copyright (c) 2011 by Andrew W. Phillips.
//
// This file is distributed under the MIT license, which basically says
// you can do what you want with it but I take no responsibility for bugs.
// See http://www.opensource.org/licenses/mit-license.php for full details.
//

#include "stdafx.h"
#include <locale.h>
#include <assert.h>

#include "HexEdit.h"
#include "MainFrm.h"
#include "HexEditDoc.h"
#include "HexEditView.h"
#include "bookmark.h"
#include "SystemSound.h"
#include "misc.h"
#include "resource.hm"          // For control help IDs
#include <afxpriv.h>            // for WM_COMMANDHELP and WM_HELPHITTEST

#include <HtmlHelp.h>

extern CHexEditApp theApp;

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

/////////////////////////////////////////////////////////////////////////////
// CHexEdit - subclassed edit control that allows entry of hex bytes

IMPLEMENT_DYNAMIC(CHexEdit, CEdit)

BEGIN_MESSAGE_MAP(CHexEdit, CEdit)
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CHexEdit message handlers

void CHexEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	const char *test_str;
	if (allow_qmark_)
		test_str = "\b 0123456789abcdefABCDEF?";
	else
		test_str = "\b 0123456789abcdefABCDEF";

	if (nChar == '\b' && start > 1 && start == end && ss[start-1] == ' ')
	{
		// If deleting 1st nybble (hex mode) then also delete preceding space
		SetSel(start-2, end);
		// no return
	}
	else if (nChar == ' ')
	{
		// Just ignore spaces
		return;
	}
	else if (::isprint(nChar) && strchr(test_str, nChar) == NULL)
	{
#ifdef SYS_SOUNDS
		if (!CSystemSound::Play("Invalid Character"))
#endif
			::Beep(5000,200);
		return;
	}
	CEdit::OnChar(nChar, nRepCnt, nFlags);
	add_spaces();
}

void CHexEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	if (nChar == VK_DELETE && start == end && ss.GetLength() > start+1 &&
			 ss[start+1] == ' ')
		SetSel(start, start+2);
	else if (nChar == VK_LEFT && start == end && start > 0 && ss[start-1] == ' ')
		SetSel(start-1, start-1);

	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);

	// Tidy display if char(s) deleted or caret moved
	GetWindowText(ss);
	if ((nChar == VK_DELETE || nChar == VK_RIGHT || nChar == VK_LEFT) &&
		::GetKeyState(VK_SHIFT) >= 0 && 
		ss.GetLength() > 0)
	{
		add_spaces();
	}
}

/////////////////////////////////////////////////////////////////////////////
// CHexEdit private methods

void CHexEdit::add_spaces()
{
	CString ss;                         // Current string
	GetWindowText(ss);
	int start, end;                     // Current selection (start==end if just caret)
	GetSel(start, end);

	const char *pp;                     // Ptr into orig. (hex digit) string
	int newstart, newend;               // New caret position/selection
	newstart = newend = 0;              // In case of empty string

	// Allocate enough space (allowing for space padding)
	char *out = new char[(ss.GetLength()*(group_by_+1))/group_by_ + 2]; // Where chars are stored
	size_t ii, jj;                      // Number of hex nybbles written/read
	char *dd = out;                     // Ptr to current output char
	int ndigits;                        // Numbers of chars that are part of number

	// If right aligned we need to know how many digits are to be used
	if (right_align_)
	{
		for (ndigits = 0, pp = ss.GetBuffer(0); *pp != '\0'; ++pp)
			if (isxdigit(*pp))
				++ndigits;
		ss.ReleaseBuffer();
	}

	for (pp = ss.GetBuffer(0), ii = jj = 0; /*forever*/; ++pp, ++ii)
	{
		if (ii == start)
			newstart = dd - out;        // save new caret position
		if (ii == end)
			newend = dd - out;

		if (*pp == '\0')
			break;

		// Ignore spaces (and anything else)
		if (!isxdigit(*pp) && *pp != '?')
			continue;                   // ignore all but digits and ?

		if (theApp.hex_ucase_)
			*dd++ = toupper(*pp);       // convert all to upper case
		else
			*dd++ = tolower(*pp);       // convert all to lower case

		++jj;
		if (right_align_)
		{
			if ((ndigits-jj) % group_by_ == 0)
				*dd++ = ' ';                // pad with space at start of next group
		}
		else
		{
			if (jj > 0 && (jj % group_by_) == 0)
				*dd++ = ' ';                // pad with space after every group_by_'th hex digit
		}
	}
	if (dd > out && *(dd-1) == ' ')
//    if (jj > 0 && (jj % group_by_) == 0)
		dd--;                           // Forget last space if added
	*dd = '\0';                         // Terminate string

	SetWindowText(out);
	SetSel(newstart, newend);
	delete[] out;
}

/////////////////////////////////////////////////////////////////////////////
// CDecEdit - subclassed edit control that allows entry of a decimal number

// TBD: this is similar but does less than CDecEditControl (below) - perhaps it should just be an option in CDecEditControl

IMPLEMENT_DYNAMIC(CDecEdit, CEdit)

BEGIN_MESSAGE_MAP(CDecEdit, CEdit)
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

CDecEdit::CDecEdit() : allow_neg_(false)
{
	sep_char_ = theApp.dec_sep_char_;
	group_by_ = theApp.dec_group_;
}

/////////////////////////////////////////////////////////////////////////////
// CDecEdit message handlers

void CDecEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	CString test_str = CString("\b 0123456789") + sep_char_;

	if (nChar == '\b' && start > 1 && start == end && ss[start-1] == sep_char_)
	{
		// If deleting 1st char of group then also delete preceding sep_char
		SetSel(start-2, end);
		// no return
	}
	else if (nChar == ' ' || nChar == sep_char_)
	{
		// Just ignore these characters
		return;
	}
	else if (allow_neg_ && nChar == '-')
	{
		/*nothing*/ ;
	}
	else if (::isprint(nChar) && strchr(test_str, nChar) == NULL)
	{
#ifdef SYS_SOUNDS
		if (!CSystemSound::Play("Invalid Character"))
#endif
			::Beep(5000,200);
		return;
	}
	CEdit::OnChar(nChar, nRepCnt, nFlags);
	add_commas();
}

void CDecEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	if (nChar == VK_DELETE && start == end && ss.GetLength() > start+1 &&
			 ss[start+1] == sep_char_)
		SetSel(start, start+2);
	else if (nChar == VK_LEFT && start == end && start > 0 && ss[start-1] == sep_char_)
		SetSel(start-1, start-1);

	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);

	// Tidy display if char(s) deleted or caret moved
	GetWindowText(ss);
	if ((nChar == VK_DELETE || nChar == VK_RIGHT || nChar == VK_LEFT) &&
		::GetKeyState(VK_SHIFT) >= 0 && 
		ss.GetLength() > 0)
	{
		add_commas();
	}
}

/////////////////////////////////////////////////////////////////////////////
// CDecEdit private methods

void CDecEdit::add_commas()
{
	CString ss;                         // Current string
	GetWindowText(ss);
	int start, end;                     // Current selection (start==end if just caret)
	GetSel(start, end);

	const char *pp;                     // Ptr into orig. (decimal number) string
	int newstart, newend;               // New caret position/selection
	newstart = newend = 0;              // In case of empty string

	// Allocate enough space (allowing for comma padding)
	char *out = new char[(ss.GetLength()*(group_by_+1))/group_by_ + 2]; // Where chars are stored
	size_t ii, jj;                      // Number of digits written/read
	char *dd = out;                     // Ptr to current output char
	int ndigits;                        // Numbers of chars that are part of number
	BOOL none_yet = TRUE;               // Have we seen any non-zero digits?

	for (pp = ss.GetBuffer(0), ndigits = 0; *pp != '\0'; ++pp)
		if (isdigit(*pp))
			++ndigits;
	ss.ReleaseBuffer();

	for (pp = ss.GetBuffer(0), ii = jj = 0; /*forever*/; ++pp, ++ii)
	{
		if (ii == start)
			newstart = dd - out;        // save new caret position
		if (ii == end)
			newend = dd - out;

		if (*pp == '\0')
			break;

		if (allow_neg_ && dd == out && *pp == '-')
		{
			*dd++ = '-';
			continue;
		}

		// Ignore spaces (and anything else)
		if (!isdigit(*pp))
			continue;                   // ignore all but digits

		if (++jj < ndigits && none_yet && *pp == '0')
			continue;                   // ignore leading zeroes

		none_yet = FALSE;
		*dd++ = *pp;

		if ((ndigits - jj) % group_by_ == 0)
			*dd++ = sep_char_;          // put in thousands separator
	}
	if (dd > out && ndigits > 0)
		dd--;                           // Forget last comma if added
	*dd = '\0';                         // Terminate string

	SetWindowText(out);
	SetSel(newstart, newend);
	delete[] out;
}


//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CSearchEditControl

IMPLEMENT_DYNAMIC(CSearchEditControl, CMFCToolBarComboBoxEdit)

CSearchEditControl::CSearchEditControl(CMFCToolBarComboBoxButton & combo) : 
	CMFCToolBarComboBoxEdit(combo)
{
	mode_ = mode_none;
}

BEGIN_MESSAGE_MAP(CSearchEditControl, CMFCToolBarComboBoxEdit)
		//{{AFX_MSG_MAP(CSearchEditControl)
		ON_WM_CHAR()
		ON_WM_SETFOCUS()
		ON_WM_KEYDOWN()
		ON_WM_KILLFOCUS()
	//}}AFX_MSG_MAP
		ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
END_MESSAGE_MAP()

void CSearchEditControl::Redisplay()            // Make sure hex digits case OK etc
{
	CString ss;
	GetWindowText(ss);
	if (ss.GetLength() > 0 && ss[0] != sflag_char && ss[0] != iflag_char)
	{
		normalize_hex();
		// Make sure OnUpdate does not set search string back to what it was
		GetWindowText(ss);
//        ((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);
		((CMainFrame *)AfxGetMainWnd())->SetSearchString(ss);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CSearchEditControl message handlers

void CSearchEditControl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;
	if (process_char(nChar))
	{
		GetWindowText(ss);
		((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);
		return;
	}

	CMFCToolBarComboBoxEdit::OnChar(nChar, nRepCnt, nFlags);

	// If in hex mode make sure it looks neat
	GetWindowText(ss);
	if (ss.GetLength() > 0 && ss[0] != sflag_char && ss[0] != iflag_char)
	{
		normalize_hex();
		GetWindowText(ss);
	}
	((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);

#ifdef AUTO_COMPLETE_SEARCH
	// Now do search string completion using all strings in history
	int start, end;             // Range of current selection
	GetSel(start, end);         // (start == end means no selection just caret)

	if (::isprint(nChar) && start == ss.GetLength())
	{
		ASSERT(start == end);

		// Valid char typed at end of current text so do completion
		CMainFrame *mm = (CMainFrame *)AfxGetMainWnd();

		// Find matching string from search history
		int count = 0;
		std::vector<CString>::iterator match;
		for (std::vector<CString>::iterator ps = mm->search_hist_.begin();
			 ps != mm->search_hist_.end(); ++ps)
		{
			 if (ss.GetLength() > 0 && ss[0] != sflag_char)
			 {
				 // Case-insensitive comparisons (doing hex or case-insensitive search)
				 if (ss.CompareNoCase(ps->Left(ss.GetLength())) == 0)
				 {
					 match = ps;
					 ++count;
				 }
			 }
			 else
			 {
				 // Case-sensitive comparisons
				 if (ss.Compare(ps->Left(ss.GetLength())) == 0)
				 {
					 match = ps;
					 ++count;
				 }
			 }
		}
		if (count == 1)
		{
			// We autocomplete if exactly one match found but not if the new string is
			// bigger than the edit control can display otherwise the SetSel() call
			// below makes the end of 

			// Work out new bit to add to end
			CString strEnd = match->Mid(ss.GetLength());

			// Work out size of new string in edit control
			CDC *pDC = GetDC();
			CSize str_size = pDC->GetTextExtent(strEnd);
			ReleaseDC(pDC);

			// Work out visible area of edit control
			CRect rct;
			GetRect(&rct);

			if (str_size.cx < rct.Width())
			{
				// The new string fits so complete it
				SetWindowText(ss + strEnd);

				// Select it so user doesn't have to do anything if they don't really want this one
				SetSel(start, -1);

				// Make sure OnUpdate handler doesn't set string back the way it was
				mm->SetSearch(ss + strEnd);
			}
		}
	}
#endif
}

void CSearchEditControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;
	GetWindowText(ss);
	int start, end;
	GetSel(start, end);

	if (nChar == VK_DELETE && start == 0 && ss.GetLength() > 0 &&
		(ss[0] == sflag_char || ss[0] == iflag_char))
	{
		// Convert ASCII chars to equiv. hex digits
		if (end > 0)
		{
			SetSel(1, end);
			Clear();
		}
		convert2hex();
		GetWindowText(ss);
		((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);
		return;
	}
	else if (nChar == VK_DELETE && start == end && ss.GetLength() > start+1 &&
			 ss[0] != sflag_char && ss[0] != iflag_char && ss[start+1] == ' ')
		SetSel(start, start+2);
	else if (nChar == VK_LEFT && start == end && start > 0 &&
				ss[0] != sflag_char && ss[0] != iflag_char && ss[start-1] == ' ')
		SetSel(start-1, start-1);

	CMFCToolBarComboBoxEdit::OnKeyDown(nChar, nRepCnt, nFlags);

	// Tidy display if hex and char(s) deleted or caret moved
	GetWindowText(ss);
	if ((nChar == VK_DELETE || nChar == VK_RIGHT || nChar == VK_LEFT) &&
		::GetKeyState(VK_SHIFT) >= 0 && ss.GetLength() > 0 &&
		ss[0] != sflag_char && ss[0] != iflag_char)
	{
		normalize_hex();
		GetWindowText(ss);
	}
	((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);
}

// TBD: xxx OnSet/KillFocus could perhaps be removed and mode changes done more effectively elsewhere
void CSearchEditControl::OnSetFocus(CWnd* pOldWnd) 
{
	CString strCurr;
	CMFCToolBarComboBoxEdit::OnSetFocus(pOldWnd);
	char new_text[2] = "?";
		
	GetWindowText(strCurr);

	if (strCurr.GetLength() > 0)
	{
		if (mode_ == mode_hex && (strCurr[0] == sflag_char || strCurr[0] == iflag_char))
		{
			convert2hex();              // Convert existing chars to hex digits
			SetSel(0,-1);               // Make sure all selected
		}
		else if (mode_ == mode_char && strCurr[0] != sflag_char && strCurr[0] != iflag_char)
			convert2chars();            // Convert hex digits to chars (as many as possible)
		else if (mode_ == mode_icase && strCurr[0] != sflag_char && strCurr[0] != iflag_char)
			convert2chars(iflag_char);  // Convert hex digits to chars (icase search)
		else if (mode_ == mode_char && strCurr[0] == iflag_char)
		{
			SetSel(0,1);
			new_text[0] = sflag_char;
			ReplaceSel(new_text);
			((CMainFrame *)AfxGetMainWnd())->
				SetSearch(CString(char(sflag_char)) + strCurr.Mid(1));
		}
		else if (mode_ == mode_icase && strCurr[0] == sflag_char)
		{
			SetSel(0,1);
			new_text[0] = iflag_char;
			ReplaceSel(new_text);
			((CMainFrame *)AfxGetMainWnd())->
				SetSearch(CString(char(iflag_char)) + strCurr.Mid(1));
		}
		else if (mode_ == mode_none && strCurr[0] == sflag_char)
			mode_ = mode_char;
		else if (mode_ == mode_none && strCurr[0] == iflag_char)
			mode_ = mode_icase;
	}
	else if (mode_ == mode_char)
	{
		// No chars but char search mode so add "="
		new_text[0] = sflag_char;
		SetWindowText(new_text);
		((CMainFrame *)AfxGetMainWnd())->SetSearch(new_text);
	}
	else if (mode_ == mode_icase)
	{
		// No chars but case-insensitive search mode so add "~"
		new_text[0] = iflag_char;
		SetWindowText(new_text);
		((CMainFrame *)AfxGetMainWnd())->SetSearch(new_text);
	}

	if (mode_ == mode_char || mode_ == mode_icase)
		SetSel(1,-1);                   // select all but ' so string can easily be overtyped
}

void CSearchEditControl::OnKillFocus(CWnd* pNewWnd) 
{
	CMFCToolBarComboBoxEdit::OnKillFocus(pNewWnd);

	mode_ = mode_none;
}

LRESULT CSearchEditControl::OnCommandHelp(WPARAM, LPARAM lParam)
{
	if (!::HtmlHelp(AfxGetMainWnd()->m_hWnd, theApp.htmlhelp_file_,
					 HH_HELP_CONTEXT, 0x10000 + ID_SEARCH_COMBO))
		AfxMessageBox(AFX_IDP_FAILED_TO_LAUNCH_HELP);
	return 1;                           // Indicate help launched
}

#if 0 // not needed with BCG
UINT CSearchEditControl::OnGetDlgCode() 
{
	// Get all keys so that we see CR and Escape
	return CMFCToolBarComboBoxEdit::OnGetDlgCode() | DLGC_WANTALLKEYS;
}
#endif

/////////////////////////////////////////////////////////////////////////////
// CSearchEditControl private methods

BOOL CSearchEditControl::process_char(UINT nChar)
{
	CString ss;
	GetWindowText(ss);          // Current text in control (before key pressed)
	int len = ss.GetLength();   // Length of current text
	int start, end;             // Range of current selection
	GetSel(start, end);         // (start == end means no selection just caret)

	if (nChar == '\b' &&                                       // Backspace ...
		(start == 1 && end == 1 || start == 0 && end > 0) &&   // When after 1st char OR they are included in selection
		(ss[0] == sflag_char || ss[0] == iflag_char))          // And 1st char is '~' or '='
	{
		// Convert ASCII chars to equiv. hex digits
		if (start == 0)
		{
			SetSel(1, end);
			Clear();
		}
		convert2hex();
		return TRUE;
	}
	else if (nChar == '\b' && start > 1 && start == end && ss[start-1] == ' ' &&
			 ss[0] != sflag_char && ss[0] != iflag_char)
	{
		// If deleting 1st nybble (hex mode) then also delete preceding space
		SetSel(start-2, end);
	}
	else if (nChar == sflag_char && start == 0 && len > 0 &&        // '=' typed when at start
				(ss[0] == iflag_char || ss[0] == sflag_char))       // and doing char search
	{
		// Make search case-sensitive
		char new_text[2] = "?";
		new_text[0] = sflag_char;
		if (end == 0) SetSel(0,1);
		ReplaceSel(new_text);
		return TRUE;
	}
	else if (nChar == iflag_char && start == 0 && len > 0 &&        // '~' typed when at start
				(ss[0] == iflag_char || ss[0] == sflag_char))       // and doing char search
	{
		// Make search case-insensitive
		char new_text[2] = "?";
		new_text[0] = iflag_char;
		if (end == 0) SetSel(0,1);
		ReplaceSel(new_text);
		return TRUE;
	}
	else if ((nChar == sflag_char || nChar == iflag_char) &&
			 start == 0 && len > 0 &&
			 ss[0] != sflag_char && ss[0] != iflag_char )
	{
		// =/~ at start and not one there already
		Clear();
		convert2chars(nChar);
		SetSel(1,1);
		return TRUE;
	}
	else if (nChar == ' ' && len > 0 &&
			 ss[0] != sflag_char && ss[0] != iflag_char )
	{
		// Ignore spaces if in hex mode (tend to type them because of display)
		return TRUE;
	}
	else if (::isprint(nChar))
	{
		// Chars allowed in empty edit (ie start of text search or a hex digit)
		char valid_empty[] = "??0123456789ABCDEFabcdef";
		valid_empty[0] = sflag_char;
		valid_empty[1] = iflag_char;

		// Disallow the following (where ASCII mode is where ss[0] == iflag or sflag)
		// - any char at start (except sflag/iflag/DEL, handled above) if ASCII mode
		// - any char except sflag/iflag/hex-digit if field empty
		// - non-hexdigit (except BS) if not ASCII mode
		if ((start == 0 && len > 0 && (ss[0]==sflag_char||ss[0]==iflag_char)) ||
			(len == 0 && strchr(valid_empty, nChar) == NULL) ||
			(len > 0 && ss[0] != sflag_char && ss[0] != iflag_char &&
						strchr("\b0123456789ABCDEFabcdef", nChar) == NULL) )
		{
#ifdef SYS_SOUNDS
			if (!CSystemSound::Play("Invalid Character"))
#endif
				::Beep(5000,200);
			return TRUE;
		}
	}

	return FALSE;
}

void CSearchEditControl::normalize_hex()
{
	CString ss;
	GetWindowText(ss);
	int start, end;
	GetSel(start, end);

	const char *pp;                     // Ptr into orig. (hex digit) string
	int newstart, newend;               // New caret position/selection
	newstart = newend = 0;              // In case of empty text string

	// Allocate enough space (allowing for space padding)
	char *out = new char[(ss.GetLength()*3)/2+2]; // Where chars are stored
	size_t ii, jj;                      // Number of hex nybbles written/read
	char *dd = out;                     // Ptr to current output char

	CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());

	for (pp = ss.GetBuffer(0), ii = jj = 0; /*forever*/; ++pp, ++ii)
	{
		if (ii == start)
			newstart = dd - out;        // save new caret position
		if (ii == end)
			newend = dd - out;

		if (*pp == '\0')
			break;

		// Ignore spaces (and anything else)
		if (!isxdigit(*pp))
			continue;                   // ignore all but hex digits

		if (aa->hex_ucase_)
			*dd++ = toupper(*pp);       // convert all to upper case
		else
			*dd++ = tolower(*pp);       // convert all to lower case

		if ((++jj % 2) == 0)
			*dd++ = ' ';                // pad with space after 2 nybbles
	}
	if ((jj % 2) == 0 && jj > 0)
		dd--;                   // Remove trailing space
	*dd = '\0';                 // Terminate string

	SetWindowText(out);
	SetSel(newstart, newend);
	delete[] out;
}

void CSearchEditControl::convert2hex()
{
	BOOL bad_chars = FALSE;
	BOOL ebcdic = FALSE;
	CString ss;

	GetWindowText(ss);
	if (ss.GetLength() == 0)
		return;

	if (ss[0] != sflag_char && ss[0] != iflag_char)
	{
		convert2chars();                // -> chars then -> hex to normalize
		GetWindowText(ss);
	}

	const char *pp;                     // Ptr into input (ASCII char) string
	char *out = new char[ss.GetLength()*3+1]; // Where output chars are stored
	char *dd = out;                     // Ptr to current output char

	// Display hex in upper or lower case?
	CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());
	const char *hex_fmt;
	if (aa->hex_ucase_)
		hex_fmt = "%02X ";
	else
		hex_fmt = "%02x ";

	// If view is displaying EBCDIC chars then convert to hex assuming EBCDIC chars
	ASSERT(GetView() != NULL);
	if (GetView()->EbcdicMode())
		ebcdic = TRUE;

	for (pp = ss.GetBuffer(0) + 1; *pp != '\0'; ++pp)
	{
		if (ebcdic && *pp < 128 && *pp >= 0 && a2e_tab[*pp] != '\0')
		{
			sprintf(dd, hex_fmt, a2e_tab[(unsigned char)*pp]);
			dd += 3;
		}
		else if (ebcdic)
			bad_chars = TRUE;
		else
		{
			sprintf(dd, hex_fmt, (unsigned char)*pp);
			dd += 3;
		}
	}
	if (dd > out) dd--;                 // Forget trailing space
	*dd = '\0';                         // Terminate string

	SetWindowText(out);
	((CMainFrame *)AfxGetMainWnd())->SetSearch(out);
	delete[] out;

	if (bad_chars)
	{
		((CMainFrame *)AfxGetMainWnd())->
			StatusBarText("Hex search: invalid EBCDIC chars ignored");
	}
}

void CSearchEditControl::convert2chars(char c1)
{
	BOOL bad_chars = FALSE;
	BOOL ebcdic = FALSE;
	CString ss;

	GetWindowText(ss);
	if (ss.GetLength() == 0)
	{
		char new_text[2] = "?";
		new_text[0] = c1;
		SetWindowText(new_text);
		((CMainFrame *)AfxGetMainWnd())->SetSearch(new_text);
		return;
	}

	if (ss[0] == sflag_char || ss[0] == iflag_char)
	{
		convert2hex();                  // -> hex then -> chars to normalize
		GetWindowText(ss);
	}

	const char *pp;                     // Ptr to input (hex digit) string

	char *out = new char[ss.GetLength()/2+3]; // Where output chars are stored
	size_t length;                      // Number of hex output nybbles generated
	char *dd = out;                     // Ptr to current output char

	// If view is displaying EBCDIC chars then convert hex to EBCDIC
	ASSERT(GetView() != NULL);
	if (GetView()->EbcdicMode())
		ebcdic = TRUE;

	*(dd++) = c1;                       // Indicate char string (1st char == ' OR *)
	for (pp = ss.GetBuffer(0), length = 0, *dd = '\0'; *pp != '\0'; ++pp)
	{
		// Ignore spaces (and anything else)
		if (!isxdigit(*pp))
			continue;

		*dd = (*dd<<4) + (isdigit(*pp) ? *pp - '0' : toupper(*pp) - 'A' + 10);

		if ((++length % 2) == 0)
		{
			if (ebcdic && (*dd = e2a_tab[(unsigned char)*dd]) != '\0')
				++dd;                   // Valid EBCDIC so move to next byte
			else if (!ebcdic && (unsigned char)*dd > '\r')
				++dd;                   // Printable ASCII so move to next byte
			else
			{
				length -= 2;            // Forget byte (2 hex digits) just seen
				bad_chars = TRUE;
			}
			*(dd) = '\0';               // Zero next byte
		}
	}
	if ((length % 2) != 0  && *dd == '\0')  // WAS ... && !isprint(*dd))
		length--;                       // Solo last hex digit -> not printable char
	if (length > 0)
		length = (length-1)/2 + 1;      // Convert nybble count to byte count
	out[length+1] = '\0';

	SetWindowText(out);
	((CMainFrame *)AfxGetMainWnd())->SetSearch(out);
	delete[] out;

	if (ebcdic && bad_chars)
		((CMainFrame *)AfxGetMainWnd())->StatusBarText("EBCDIC search: invalid characters ignored");
	else if (bad_chars)
		((CMainFrame *)AfxGetMainWnd())->StatusBarText("ASCII search: control characters ignored");
}

/////////////////////////////////////////////////////////////////////////////
// CSearchEditControl class (static methods)

// This finds the first visible search tool (in toolbars), 
// changes to specified search mode and sets focus to it.
void CSearchEditControl::BeginSearch(enum mode_t mode)
{
	CObList listButtons;

	// Search all toolbars and jump to the first visible search tool found (can be more than one)
	if (CMFCToolBar::GetCommandButtons(ID_SEARCH_COMBO, listButtons) > 0)
	{
		for (POSITION posCombo = listButtons.GetHeadPosition(); posCombo != NULL; )
		{
			CFindComboButton* pCombo = 
				DYNAMIC_DOWNCAST(CFindComboButton, listButtons.GetNext(posCombo));
			ASSERT(pCombo != NULL);

			CSearchEditControl * pedit = dynamic_cast<CSearchEditControl *>(pCombo->GetEditCtrl());
			ASSERT(pedit != NULL);
			if (pedit->IsWindowVisible())
			{
				// We found (first) one so change its mode and set focus
				pedit->SetMode(mode);
				pedit->SetFocus();
				break;
			}
		}
	}
}

// Finds all search tools and forces them to redraw
void CSearchEditControl::RedisplayAll()
{
	CObList listButtons;
	if (CMFCToolBar::GetCommandButtons(ID_SEARCH_COMBO, listButtons) > 0)
	{
		for (POSITION posCombo = listButtons.GetHeadPosition (); 
			posCombo != NULL; )
		{
			CFindComboButton* pCombo = 
				DYNAMIC_DOWNCAST(CFindComboButton, listButtons.GetNext(posCombo));
			ASSERT(pCombo != NULL);

			CSearchEditControl * pedit = dynamic_cast<CSearchEditControl *>(pCombo->GetEditCtrl());
			ASSERT(pedit != NULL);
			pedit->Redisplay();
		}
	}
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CFindComboBox

IMPLEMENT_DYNCREATE(CFindComboBox, CComboBox)

BEGIN_MESSAGE_MAP(CFindComboBox, CComboBox)
	ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)
END_MESSAGE_MAP()

void CFindComboBox::SetSearchString() 
{
	CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());
	CString ss;
	GetWindowText(ss);

	if (ss.GetLength() > 0 && 
		ss[0] != CSearchEditControl::sflag_char && 
		ss[0] != CSearchEditControl::iflag_char)
	{
		if (aa->hex_ucase_)
			ss.MakeUpper();
		else
			ss.MakeLower();
	}
	((CMainFrame *)AfxGetMainWnd())->SetSearch(ss);
}

/////////////////////////////////////////////////////////////////////////////
// CFindComboBox message handlers

// CBN_SELCHANGE message reflected back to the control
void CFindComboBox::OnSelchange() 
{
	SetSearchString();
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CFindComboButton

IMPLEMENT_SERIAL(CFindComboButton, CMFCToolBarComboBoxButton, 1)

CComboBox *CFindComboButton::CreateCombo(CWnd* pWndParent, const CRect& rect)
{
	CFindComboBox *pcombo = new CFindComboBox;

	if (!pcombo->Create(m_dwStyle, rect, pWndParent, m_nID))
	{
		delete pcombo;
		return NULL;
	}

	//pcombo->AddString("");  // This stops BCG restoring combo since we do it ourselves in OnUpdate
	SetDropDownHeight(400);

	return pcombo;
}

CSearchEditControl * CFindComboButton::CreateEdit(CWnd* pWndParent, const CRect& rect, DWORD dwEditStyle)
{
	CSearchEditControl * pWndEdit = new CSearchEditControl(*this);

	if (!pWndEdit->Create(dwEditStyle, rect, pWndParent, m_nID))
	{
		delete pWndEdit;
		return NULL;
	}

	return pWndEdit;
}

BOOL CFindComboButton::NotifyCommand(int iNotifyCode)
{
	if (m_pWndCombo->GetSafeHwnd() == 0)
		return FALSE;

	if (iNotifyCode == CBN_EDITCHANGE)
		return TRUE;
	else
		return CMFCToolBarComboBoxButton::NotifyCommand(iNotifyCode);
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CHexEditControl

IMPLEMENT_DYNAMIC(CHexEditControl, CMFCToolBarComboBoxEdit)

CHexEditControl::CHexEditControl(CMFCToolBarComboBoxButton & combo) : 
	CMFCToolBarComboBoxEdit(combo)
{
	m_brush.CreateSolidBrush(RGB(255,255,255));
}

BEGIN_MESSAGE_MAP(CHexEditControl, CMFCToolBarComboBoxEdit)
		//{{AFX_MSG_MAP(CHexEditControl)
		ON_WM_CHAR()
		ON_WM_KEYDOWN()
	ON_WM_CTLCOLOR_REFLECT()
	//}}AFX_MSG_MAP
		ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
END_MESSAGE_MAP()

void CHexEditControl::Redisplay()            // Make sure hex digits case OK etc
{
	CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());
	CString ss;
	GetWindowText(ss);
	DWORD sel = GetSel();
	if (aa->hex_ucase_)
		ss.MakeUpper();
	else
		ss.MakeLower();
	SetWindowText(ss);
	SetSel(sel);
}

/////////////////////////////////////////////////////////////////////////////
// CHexEditControl message handlers

void CHexEditControl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	bool is_num(false);             // Is this a simple number or an expression?
	const char *test_str = "\b 0123456789abcdefABCDEF";

	// Check if text (current text and char just entered) is just an hex int
	if (strspn(ss, test_str) == ss.GetLength() && strchr(test_str, nChar) != NULL)
	{
		// Just a hex number (possibly with spaces)
		is_num = true;
		if (nChar == '\b' && start > 1 && start == end && ss[start-1] == ' ')
		{
			// If deleting 1st nybble (hex mode) then also delete preceding space
			SetSel(start-2, end);
			// no return
		}
		else if (nChar == ' ')
		{
			// Just ignore spaces
			return;
		}
		else if (::isprint(nChar) && strchr(test_str, nChar) == NULL)
		{
#ifdef SYS_SOUNDS
			if (!CSystemSound::Play("Invalid Character"))
#endif
				::Beep(5000,200);
			return;
		}
	}
	CMFCToolBarComboBoxEdit::OnChar(nChar, nRepCnt, nFlags);
	if (is_num)
		add_spaces();

	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetHexAddress(ss);
}

void CHexEditControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	bool is_num(false);             // Is this a simple number or an expression?
	const char *test_str = " 0123456789abcdefABCDEF";

	// Check if text (current text and char just entered) is just an hex int
	if (strspn(ss, test_str) == ss.GetLength())
	{
		// Just a hex number (possibly with spaces)
		is_num = true;

		if (nChar == VK_DELETE && start == end && ss.GetLength() > start+1 &&
				 ss[start+1] == ' ')
			SetSel(start, start+2);
		else if (nChar == VK_LEFT && start == end && start > 0 && ss[start-1] == ' ')
			SetSel(start-1, start-1);
	}

	CMFCToolBarComboBoxEdit::OnKeyDown(nChar, nRepCnt, nFlags);

	// Tidy display if char(s) deleted or caret moved
	GetWindowText(ss);
	if (is_num &&
		(nChar == VK_DELETE || nChar == VK_RIGHT || nChar == VK_LEFT) &&
		::GetKeyState(VK_SHIFT) >= 0 && 
		ss.GetLength() > 0)
	{
		add_spaces();
		GetWindowText(ss);
	}
	((CMainFrame *)AfxGetMainWnd())->SetHexAddress(ss);
}

HBRUSH CHexEditControl::CtlColor(CDC* pDC, UINT nCtlColor) 
{
	if (nCtlColor == CTLCOLOR_EDIT)
		pDC->SetTextColor(::GetHexAddrCol());

	return m_brush;
}

LRESULT CHexEditControl::OnCommandHelp(WPARAM, LPARAM lParam)
{
	// Since there is only one control of this type just call help with its help ID
	if (!::HtmlHelp(AfxGetMainWnd()->m_hWnd, theApp.htmlhelp_file_,
					 HH_HELP_CONTEXT, 0x10000 + ID_JUMP_HEX_COMBO))
		AfxMessageBox(AFX_IDP_FAILED_TO_LAUNCH_HELP);
	return 1;                           // Indicate help launched
}

/////////////////////////////////////////////////////////////////////////////
// CHexEditControl private methods

void CHexEditControl::add_spaces()
{
	CString ss;                         // Current string
	GetWindowText(ss);
	int start, end;                     // Current selection (start==end if just caret)
	GetSel(start, end);

	const char *pp;                     // Ptr into orig. (hex digit) string
	int newstart, newend;               // New caret position/selection
	newstart = newend = 0;              // In case of empty string

	// Allocate enough space (allowing for space padding)
	char *out = new char[(ss.GetLength()*3)/2+1]; // Where chars are stored
	size_t ii, jj;                      // Number of hex nybbles written/read
	char *dd = out;                     // Ptr to current output char
	int ndigits;                        // Numbers of chars that are part of number

	for (pp = ss.GetBuffer(0), ndigits = 0; *pp != '\0'; ++pp)
		if (isxdigit(*pp))
			++ndigits;
	ss.ReleaseBuffer();

	CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());

	for (pp = ss.GetBuffer(0), ii = jj = 0; /*forever*/; ++pp, ++ii)
	{
		if (ii == start)
			newstart = dd - out;        // save new caret position
		if (ii == end)
			newend = dd - out;

		if (*pp == '\0')
			break;

		// Ignore spaces (and anything else)
		if (!isxdigit(*pp))
			continue;                   // ignore all but digits

		if (aa->hex_ucase_)
			*dd++ = toupper(*pp);       // convert all to upper case
		else
			*dd++ = tolower(*pp);       // convert all to lower case

		++jj;
		if ((ndigits - jj) % 4 == 0)
			*dd++ = ' ';                // pad with space after every 4th hex digit
	}
	if (dd > out)
		dd--;                           // Forget last comma if added
	*dd = '\0';                         // Terminate string

	SetWindowText(out);
	SetSel(newstart, newend);
	delete[] out;
}

/////////////////////////////////////////////////////////////////////////////
// CHexEditControl class (static) methods

void CHexEditControl::BeginJump()
{
	CObList listButtons;

	// Search all toolbars and jump to the first visible hex address tool found (can be more than one)
	if (CMFCToolBar::GetCommandButtons(ID_JUMP_HEX_COMBO, listButtons) > 0)
	{
		for (POSITION posCombo = listButtons.GetHeadPosition();
			posCombo != NULL; )
		{
			CHexComboButton* pCombo =
				DYNAMIC_DOWNCAST(CHexComboButton, listButtons.GetNext(posCombo));
			ASSERT(pCombo != NULL);
			CHexEditControl * pedit = dynamic_cast<CHexEditControl *>(pCombo->GetEditCtrl());
			//CWnd *pwnd = pedit->GetParent();
			//ASSERT(pwnd != NULL);
			//pedit = DYNAMIC_DOWNCAST(CHexEditControl, pwnd->GetWindow(GW_HWNDNEXT));
			ASSERT(pedit != NULL);
			if (pedit->IsWindowVisible())
			{
				pedit->SetFocus();
				break;
			}
		}
	}
}

void CHexEditControl::RedisplayAll()
{
	CObList listButtons;
	if (CMFCToolBar::GetCommandButtons(ID_JUMP_HEX_COMBO, listButtons) > 0)
	{
		for (POSITION posCombo = listButtons.GetHeadPosition ();
			posCombo != NULL; )
		{
			CHexComboButton* pCombo = 
				DYNAMIC_DOWNCAST(CHexComboButton, listButtons.GetNext(posCombo));
			ASSERT(pCombo != NULL);

			CHexEditControl * pedit = dynamic_cast<CHexEditControl *>(pCombo->GetEditCtrl());
			ASSERT(pedit != NULL);
			pedit->Redisplay();
		}
	}
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CHexComboBox

IMPLEMENT_DYNCREATE(CHexComboBox, CComboBox)

BEGIN_MESSAGE_MAP(CHexComboBox, CComboBox)
	ON_CONTROL_REFLECT(CBN_SELENDOK, OnSelendok)
	ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CHexComboBox message handlers

// CBN_SELCHANGE message reflected back to the control
void CHexComboBox::OnSelchange() 
{
	CString ss;
	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetHexAddress(ss);
}

// xxx remove this?
// For some reason CBN_SELCHANGE does not seem to be reflected
// so also do it for CBN_SELENDOK
void CHexComboBox::OnSelendok() 
{
	CString ss;
	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetHexAddress(ss);
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CHexComboButton

IMPLEMENT_SERIAL(CHexComboButton, CMFCToolBarComboBoxButton, 1)

CComboBox *CHexComboButton::CreateCombo(CWnd* pWndParent, const CRect& rect)
{
	CHexComboBox *pcombo = new CHexComboBox;

	if (!pcombo->Create(m_dwStyle, rect, pWndParent, m_nID))
	{
		delete pcombo;
		return NULL;
	}

	SetDropDownHeight(400);

	return pcombo;
}

CHexEditControl * CHexComboButton::CreateEdit(CWnd* pWndParent, const CRect& rect, DWORD dwEditStyle)
{
	CHexEditControl * pWndEdit = new CHexEditControl(*this);

	if (!pWndEdit->Create(dwEditStyle, rect, pWndParent, m_nID))
	{
		delete pWndEdit;
		return NULL;
	}

	return pWndEdit;
}

BOOL CHexComboButton::NotifyCommand(int iNotifyCode)
{
	if (m_pWndCombo->GetSafeHwnd() == NULL)
		return FALSE;

	if (iNotifyCode == CBN_EDITCHANGE)
		return TRUE;
	else
		return CMFCToolBarComboBoxButton::NotifyCommand(iNotifyCode);
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CDecEditControl - similar to CDecEdit but if the string is not a number it does nothing

IMPLEMENT_DYNAMIC(CDecEditControl, CMFCToolBarComboBoxEdit)

CDecEditControl::CDecEditControl(CMFCToolBarComboBoxButton& combo) :
	CMFCToolBarComboBoxEdit(combo)
{
	sep_char_ = theApp.dec_sep_char_;
	group_ = theApp.dec_group_;

	allow_neg_ = false;

	m_brush.CreateSolidBrush(RGB(255,255,255));
}

BEGIN_MESSAGE_MAP(CDecEditControl, CMFCToolBarComboBoxEdit)
		//{{AFX_MSG_MAP(CDecEditControl)
		ON_WM_CHAR()
		ON_WM_KEYDOWN()
	ON_WM_CTLCOLOR_REFLECT()
	//}}AFX_MSG_MAP
		ON_MESSAGE(WM_COMMANDHELP, OnCommandHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDecEditControl message handlers

void CDecEditControl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	bool is_num(false);             // Is this a simple number or an expression?
	CString test_str = CString("\b 0123456789") + sep_char_;

	// Check if text (current text and char just entered) is just a number
	if (strspn(ss, test_str) == ss.GetLength() && strchr(test_str, nChar) != NULL)
	{
		// Just a (decimal) number possibly with separators (commas)
		is_num = true;
		if (nChar == '\b' && start > 1 && start == end && ss[start-1] == sep_char_)
		{
			// If deleting 1st char of group then also delete preceding sep_char
			SetSel(start-2, end);
			// no return
		}
		else if (nChar == ' ' || nChar == sep_char_)
		{
			// Just ignore these characters
			return;
		}
		else if (allow_neg_ && nChar == '-')
		{
			/*nothing*/ ;
		}
		else if (::isprint(nChar) && strchr(test_str, nChar) == NULL)
		{
#ifdef SYS_SOUNDS
			 if (!CSystemSound::Play("Invalid Character"))
#endif
				::Beep(5000,200);
			return;
		}
	}
	CMFCToolBarComboBoxEdit::OnChar(nChar, nRepCnt, nFlags);
	if (is_num)
		add_commas();

	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetDecAddress(ss);
}

void CDecEditControl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	CString ss;                     // Current text in control
	GetWindowText(ss);
	int start, end;                 // Range of selection of text in control
	GetSel(start, end);

	bool is_num(false);             // Is this a simple number or an expression?
	CString test_str = CString(" 0123456789") + sep_char_;

	// Check if text (current text and char just entered) is entirely a decimal int
	if (strspn(ss, test_str) == ss.GetLength())
	{
		is_num = true;
		if (nChar == VK_DELETE && start == end && ss.GetLength() > start+1 &&
				 ss[start+1] == sep_char_)
			SetSel(start, start+2);
		else if (nChar == VK_LEFT && start == end && start > 0 && ss[start-1] == sep_char_)
			SetSel(start-1, start-1);
	}

	CMFCToolBarComboBoxEdit::OnKeyDown(nChar, nRepCnt, nFlags);

	// Tidy display if char(s) deleted or caret moved
	GetWindowText(ss);
	if (is_num &&
		(nChar == VK_DELETE || nChar == VK_RIGHT || nChar == VK_LEFT) &&
		::GetKeyState(VK_SHIFT) >= 0 && 
		ss.GetLength() > 0)
	{
		add_commas();
		GetWindowText(ss);
	}
	((CMainFrame *)AfxGetMainWnd())->SetDecAddress(ss);
}

HBRUSH CDecEditControl::CtlColor(CDC* pDC, UINT nCtlColor) 
{
	if (nCtlColor == CTLCOLOR_EDIT)
		pDC->SetTextColor(::GetDecAddrCol());

	return m_brush;
}

LRESULT CDecEditControl::OnCommandHelp(WPARAM, LPARAM lParam)
{
	// Since there is only one control of this type just call help with its help ID
	if (!::HtmlHelp(AfxGetMainWnd()->m_hWnd, theApp.htmlhelp_file_, HH_HELP_CONTEXT,
					 0x10000 + ID_JUMP_DEC_COMBO))
		AfxMessageBox(AFX_IDP_FAILED_TO_LAUNCH_HELP);
	return 1;                           // Indicate help launched
}

/////////////////////////////////////////////////////////////////////////////
// CDecEditControl private methods

void CDecEditControl::add_commas()
{
	CString ss;                         // Current string
	GetWindowText(ss);
	int start, end;                     // Current selection (start==end if just caret)
	GetSel(start, end);

	const char *pp;                     // Ptr into orig. (decimal number) string
	int newstart, newend;               // New caret position/selection
	newstart = newend = 0;              // In case of empty string

	// Allocate enough space (allowing for comma padding)
	char *out = new char[(ss.GetLength()*(group_+1))/group_ + 2]; // Where chars are stored
	size_t ii, jj;                      // Number of digits written/read
	char *dd = out;                     // Ptr to current output char
	int ndigits;                        // Numbers of chars that are part of number
	BOOL none_yet = TRUE;               // Have we seen any non-zero digits?

	for (pp = ss.GetBuffer(0), ndigits = 0; *pp != '\0'; ++pp)
		if (isdigit(*pp))
			++ndigits;
	ss.ReleaseBuffer();

	for (pp = ss.GetBuffer(0), ii = jj = 0; /*forever*/; ++pp, ++ii)
	{
		if (ii == start)
			newstart = dd - out;        // save new caret position
		if (ii == end)
			newend = dd - out;

		if (*pp == '\0')
			break;

		if (allow_neg_ && dd == out && *pp == '-')
		{
			*dd++ = '-';
			continue;
		}

		// Ignore spaces (and anything else)
		if (!isdigit(*pp))
			continue;                   // ignore all but digits

		if (++jj < ndigits && none_yet && *pp == '0')
			continue;                   // ignore leading zeroes

		none_yet = FALSE;
		*dd++ = *pp;

		if ((ndigits - jj) % group_ == 0)
			*dd++ = sep_char_;          // put in thousands separator
	}
	if (dd > out && ndigits > 0)
		dd--;                           // Forget last comma if added
	*dd = '\0';                         // Terminate string

	SetWindowText(out);
	SetSel(newstart, newend);
	delete[] out;
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CDecComboBox

IMPLEMENT_DYNCREATE(CDecComboBox, CComboBox)

BEGIN_MESSAGE_MAP(CDecComboBox, CComboBox)
	ON_CONTROL_REFLECT(CBN_SELENDOK, OnSelendok)
	ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDecComboBox message handlers

// CBN_SELCHANGE message reflected back to the control
void CDecComboBox::OnSelchange() 
{
	CString ss;
	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetDecAddress(ss);
}

// xxx remove this?
// For some reason CBN_SELCHANGE does not seem to be reflected
// so also do it for CBN_SELENDOK
void CDecComboBox::OnSelendok() 
{
	CString ss;
	GetWindowText(ss);
	((CMainFrame *)AfxGetMainWnd())->SetDecAddress(ss);
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CDecComboButton

IMPLEMENT_SERIAL(CDecComboButton, CMFCToolBarComboBoxButton, 1)

CComboBox *CDecComboButton::CreateCombo(CWnd* pWndParent, const CRect& rect)
{
	CDecComboBox *pcombo = new CDecComboBox;

	if (!pcombo->Create(m_dwStyle, rect, pWndParent, m_nID))
	{
		delete pcombo;
		return NULL;
	}
//    pcombo->AddString("");  // This stops BCG restoring combo since we do it ourselves in OnUpdate
	SetDropDownHeight(400);

	return pcombo;
}

CDecEditControl * CDecComboButton::CreateEdit(CWnd* pWndParent, const CRect& rect, DWORD dwEditStyle)
{
	CDecEditControl * pWndEdit = new CDecEditControl(*this);

	if (!pWndEdit->Create(dwEditStyle, rect, pWndParent, m_nID))
	{
		delete pWndEdit;
		return NULL;
	}

	return pWndEdit;
}

BOOL CDecComboButton::NotifyCommand(int iNotifyCode)
{
	if (m_pWndCombo->GetSafeHwnd() == NULL)
		return FALSE;

	if (iNotifyCode == CBN_EDITCHANGE)
		return TRUE;
	else
		return CMFCToolBarComboBoxButton::NotifyCommand(iNotifyCode);
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CBookmarksComboBox

IMPLEMENT_DYNCREATE(CBookmarksComboBox, CComboBox)

BEGIN_MESSAGE_MAP(CBookmarksComboBox, CComboBox)
	ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)
//	ON_WM_DRAWITEM()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CBookmarksComboBox message handlers

void CBookmarksComboBox::OnSelchange() 
{
	CHexEditView *pview = GetView();
	if (pview == NULL)
		return;

	//pbl->GoTo(GetItemData(GetCurSel()));  // no longer storing bookmark index in the list item data

	// Get the name of the selected bookmark
	CString ss;
	GetLBText(GetCurSel(), ss);

	// Now we need to search the bookmarks of the active file to get the absolute bookmark index
	CHexEditDoc * pdoc = (CHexEditDoc *)pview->GetDocument();
	CBookmarkList * pbl = theApp.GetBookmarkList();
	ASSERT(pdoc != NULL && pbl != NULL);
	for (int ii = 0; ii < (int)pdoc->bm_index_.size(); ++ii)
		if (pbl->name_[pdoc->bm_index_[ii]] == ss)
		{
			// Found - so jump to it
			pbl->GoTo(pdoc->bm_index_[ii]);
			pview->SetFocus();
			break;
		}
}

#if 0
void CBookmarksComboBox::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	// TODO: Add your message handler code here and/or call default
	
	CComboBox::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
#endif

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
IMPLEMENT_SERIAL(CBookmarksComboButton, CMFCToolBarComboBoxButton, 1)

CComboBox *CBookmarksComboButton::CreateCombo(CWnd* pWndParent, const CRect& rect)
{
	CBookmarksComboBox *pcombo = new CBookmarksComboBox;

	if (!pcombo->Create(m_dwStyle, rect, pWndParent, m_nID))
	{
		delete pcombo;
		return NULL;
	}

	pcombo->AddString("");  // This stops BCG restoring combo since we do it ourselves in OnUpdate
	SetDropDownHeight(400);

	return pcombo;
}

//===========================================================================
/////////////////////////////////////////////////////////////////////////////
// CStatBar

#if 0
BOOL CStatBar::PreTranslateMessage(MSG* pMsg)
{
	if (ttip_.m_hWnd != NULL)
	{
		switch(pMsg->message)
		{
		case WM_MOUSEMOVE:
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MBUTTONDOWN:
		case WM_MBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
			// this will reactivate the tooltip
			ttip_.Activate(TRUE);
			ttip_.RelayEvent(pMsg);
			break;
		}
	}

	return CMFCStatusBar::PreTranslateMessage(pMsg);
}
#endif

BEGIN_MESSAGE_MAP(CStatBar, CMFCStatusBar)
		//{{AFX_MSG_MAP(CStatBar)
		ON_WM_LBUTTONDBLCLK()
		ON_WM_RBUTTONDOWN()
		ON_WM_CONTEXTMENU()
		ON_WM_CREATE()
		ON_WM_SIZE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

#if 0
void CStatBar::add_tooltip(UINT id, const char *ss)
{
	int index;                  // Index of status bar pane
	CRect rect;                 // Rectangle (within status bar) of pane

	index = CommandToIndex(id);
	ASSERT(index > 0);
	GetItemRect(index, &rect);

	// rect may be empty if the window is too small to show this pane but
	// AddTool seems to handle this OK (ie. never shows the tip).
	ttip_.AddTool(this, ss, rect, id);
}

void CStatBar::move_tooltip(UINT id)
{
	int index;                  // Index of status bar pane
	CRect rect;                 // Rectangle (within status bar) of pane

	index = CommandToIndex(id);
	ASSERT(index > 0);
	GetItemRect(index, &rect);
	ttip_.SetToolRect(this, id, rect);
}
#endif

void CStatBar::SetToolTips()
{
	ASSERT(m_hWnd != 0);
	SetTipText(CommandToIndex(ID_INDICATOR_OCCURRENCES), "Occurrences of search item (or search progress)");
	SetTipText(CommandToIndex(ID_INDICATOR_VALUES), "Hex,Dec,Octal,Binary,Char values of byte at cursor");
	SetTipText(CommandToIndex(ID_INDICATOR_HEX_ADDR), "Distance from mark to cursor (in hex)");
	SetTipText(CommandToIndex(ID_INDICATOR_DEC_ADDR), "Distance from mark to cursor (in decimal)");
	SetTipText(CommandToIndex(ID_INDICATOR_FILE_LENGTH), "File length [+ bytes inserted]");
	SetTipText(CommandToIndex(ID_INDICATOR_BIG_ENDIAN), "Little/Big Endian mode");
	SetTipText(CommandToIndex(ID_INDICATOR_READONLY), "Read-Only/Read-Write mode");
	SetTipText(CommandToIndex(ID_INDICATOR_OVR), "Overtype/Insert mode");
	SetTipText(CommandToIndex(ID_INDICATOR_REC), "Macro Recording");
	SetTipText(CommandToIndex(ID_INDICATOR_CAPS), "Caps Lock");
	SetTipText(CommandToIndex(ID_INDICATOR_NUM), "Num Lock");
}

/////////////////////////////////////////////////////////////////////////////
// CStatBar message handlers

int CStatBar::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CMFCStatusBar::OnCreate(lpCreateStruct) == -1)
		return -1;

	return 0;
}

void CStatBar::OnSize(UINT nType, int cx, int cy) 
{
	CMFCStatusBar::OnSize(nType, cx, cy);

#if 0
	if (cx > 0 && cy > 0 && ttip_.m_hWnd == NULL)
	{
		VERIFY(ttip_.Create(this));

		add_tooltip(ID_INDICATOR_OCCURRENCES, "Occurrences of search item");
		add_tooltip(ID_INDICATOR_VALUES, "Hex,Dec,Octal,Binary,char values");
		add_tooltip(ID_INDICATOR_HEX_ADDR, "Distance from mark (hex)");
		add_tooltip(ID_INDICATOR_DEC_ADDR, "Distance from mark (decimal)");
		add_tooltip(ID_INDICATOR_READONLY, "Read-Only/Read-Write");
		add_tooltip(ID_INDICATOR_OVR, "Overtype/Insert");
		add_tooltip(ID_INDICATOR_REC, "Macro Recording");

		ttip_.Activate(TRUE);
		ttip_.SetDelayTime(1000);
	}
	else if (cx > 0 && cy > 0)
	{
		move_tooltip(ID_INDICATOR_OCCURRENCES);
		move_tooltip(ID_INDICATOR_VALUES);
		move_tooltip(ID_INDICATOR_HEX_ADDR);
		move_tooltip(ID_INDICATOR_DEC_ADDR);
		move_tooltip(ID_INDICATOR_READONLY);
		move_tooltip(ID_INDICATOR_OVR);
		move_tooltip(ID_INDICATOR_REC);
	}
#endif
}

void CStatBar::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	int index;                   // Index (0 based) of current pane we're checking
	CRect pane_rect;             // Bounds (device coords) of pane
	CMainFrame *mm = (CMainFrame *)AfxGetMainWnd();

	// Check if mouse clicked on hex/dec/octal/binary/char pane
	index = CommandToIndex(ID_INDICATOR_VALUES);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		// If there is currently a view and no properties dlg exists create it
		if (GetView() != NULL)
		{
			CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());
			mm->m_paneProp.ShowAndUnroll();
			// If prev prop page was 0 (file info) change to 1 (character info)
			mm->m_wndProp.SetActivePage(aa->prop_page_ == 0 ? 1 : aa->prop_page_);
		}
		else
		{
			mm->m_paneProp.ShowAndUnroll();
		}
		return;
	}

	// Check if mouse clicked in "search occurrences" pane
	index = CommandToIndex(ID_INDICATOR_OCCURRENCES);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		// Display the find dialog
		mm->OnEditFind();
		return;
	}

	// Check if mouse clicked in a "distance to" pane
	index = CommandToIndex(ID_INDICATOR_HEX_ADDR);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		// Display the calculator in hex mode
		mm->OnEditGoto(2);
		return;
	}

	index = CommandToIndex(ID_INDICATOR_DEC_ADDR);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		// Display the calculator in decimal mode
		mm->OnEditGoto(1);
		return;
	}

	// Check if mouse was clicked in big-endian pane
	index = CommandToIndex(ID_INDICATOR_BIG_ENDIAN);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		CHexEditView *pview = GetView();
		if (pview != NULL)
		{
			if (pview->BigEndian())
				pview->OnLittleEndian();
			else
				pview->OnBigEndian();
			return;
		}
	}

	// Check if mouse clicked on READONLY pane
	index = CommandToIndex(ID_INDICATOR_READONLY);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		CHexEditView *pview = GetView();
		if (pview != NULL)
			GetView()->AllowMods();
		return;
	}

	// Check if mouse clicked on OVR pane
	index = CommandToIndex(ID_INDICATOR_OVR);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		CHexEditView *pview = GetView();
		if (pview != NULL && !pview->ReadOnly())
			pview->ToggleInsert();
		return;
	}
		
	// Check if mouse clicked on OVR pane
	index = CommandToIndex(ID_INDICATOR_REC);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		CHexEditApp *aa = dynamic_cast<CHexEditApp *>(AfxGetApp());
		aa->OnMacroRecord();
		return;
	}

	index = CommandToIndex(ID_INDICATOR_CAPS);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
		// Simulate Caps Lock key being depressed then released
		keybd_event(VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
		keybd_event(VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
		return;
	}

	// This doesn't work under Windows 95 (indicator changes but not light/kb behaviour)
	// but we no longer support Windows 9X
	index = CommandToIndex(ID_INDICATOR_NUM);
	ASSERT(index > 0);
	GetItemRect(index, &pane_rect);
	if (pane_rect.PtInRect(point))
	{
	  // Simulate Num Lock key being depressed then released
	  keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
	  keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
	  return;
	}

	CMFCStatusBar::OnLButtonDblClk(nFlags, point);
}

void CStatBar::OnRButtonDown(UINT nFlags, CPoint point) 
{
	CPoint scr_point(point);
	ClientToScreen(&scr_point);
	((CMainFrame *)AfxGetMainWnd())->bar_context(scr_point);
//    CMFCStatusBar::OnRButtonDown(nFlags, point);
}

void CStatBar::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	((CMainFrame *)AfxGetMainWnd())->bar_context(point);
}

//===========================================================================


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 MIT License


Written By
Australia Australia
Andrew has a BSc (1983) from Sydney University in Computer Science and Mathematics. Andrew began programming professionally in C in 1984 and has since used many languages but mainly C, C++, and C#.

Andrew has a particular interest in STL, .Net, and Agile Development. He has written articles on STL for technical journals such as the C/C++ User's Journal.

In 1997 Andrew began using MFC and released the source code for a Windows binary file editor called HexEdit, which was downloaded more than 1 million times. From 2001 there was a shareware version of HexEdit (later called HexEdit Pro). HexEdit has been updated to uses the new MFC (based on BCG) and is once more open source.

Comments and Discussions