diff --git a/HookMsg/XListBox.cpp b/HookMsg/XListBox.cpp new file mode 100644 index 0000000..3b5b00a --- /dev/null +++ b/HookMsg/XListBox.cpp @@ -0,0 +1,705 @@ +// XListBox.cpp +// +// Author: Hans Dietrich +// hdietrich2@hotmail.com +// +// This software is released into the public domain. +// You are free to use it in any way you like. +// +// This software is provided "as is" with no expressed +// or implied warranty. I accept no liability for any +// damage or loss of business that this software may cause. +// +// Notes on use: To use in an MFC project, first create +// a listbox using the standard dialog editor. +// Be sure to mark the listbox as OWNERDRAW +// FIXED, and check the HAS STRINGS box. +// Using Class Wizard, create a variable for +// the listbox. Finally, manually edit the +// dialog's .h file and replace CListBox with +// CXListBox, and #include XListBox.h. +// +/////////////////////////////////////////////////////////////////////////////// + +#include "stdafx.h" +#include "XListBox.h" +#include "Clipboard.h" + +#ifdef _DEBUG +#undef THIS_FILE +static char BASED_CODE THIS_FILE[] = __FILE__; +#endif + +// NOTE - following table must be kept in sync with ColorPickerCB.cpp + +static COLORREF ColorTable[17] = { RGB( 0, 0, 0), // Black + RGB(255, 255, 255), // White + RGB(128, 0, 0), // Maroon + RGB( 0, 128, 0), // Green + RGB(128, 128, 0), // Olive + RGB( 0, 0, 128), // Navy + RGB(128, 0, 128), // Purple + RGB( 0, 128, 128), // Teal + RGB(192, 192, 192), // Silver + RGB(128, 128, 128), // Gray + RGB(255, 0, 0), // Red + RGB( 0, 255, 0), // Lime + RGB(255, 255, 0), // Yellow + RGB( 0, 0, 255), // Blue + RGB(255, 0, 255), // Fuschia + RGB(238, 238, 238), // Bright Gray + RGB( 0, 255, 255) }; // Aqua + +BEGIN_MESSAGE_MAP(CXListBox, CListBox) + //{{AFX_MSG_MAP(CXListBox) + ON_WM_LBUTTONDBLCLK() + ON_WM_CONTEXTMENU() + ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll) + ON_COMMAND(ID_EDIT_COPY, OnEditCopy) + ON_COMMAND(ID_EDIT_CLEAR, OnEditClear) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +#define new DEBUG_NEW + +///////////////////////////////////////////////////////////////////////////// +// CXListBox + +CXListBox::CXListBox() +{ + m_ColorWindow = ::GetSysColor(COLOR_WINDOW); + m_ColorHighlight = ::GetSysColor(COLOR_HIGHLIGHT); + m_ColorWindowText = ::GetSysColor(COLOR_WINDOWTEXT); + m_ColorHighlightText = ::GetSysColor(COLOR_HIGHLIGHTTEXT); + m_bColor = TRUE; + m_cxExtent = 0; + m_nTabPosition = 8; // tab stops every 8 columns + m_nSpaceWidth = 7; + m_nContextMenuId = (UINT)-1; + for (int i = 0; i < MAXTABSTOPS; i++) + m_nTabStopPositions[i] = (i+1) * m_nTabPosition * m_nSpaceWidth; +} + +CXListBox::~CXListBox() +{ +} + +////////////////////////////////////////////////////////////////////////////// +// MeasureItem +void CXListBox::MeasureItem(LPMEASUREITEMSTRUCT) +{ +} + +////////////////////////////////////////////////////////////////////////////// +// CompareItem +int CXListBox::CompareItem(LPCOMPAREITEMSTRUCT) +{ + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// DrawItem +void CXListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS) +{ + COLORREF oldtextcolor, oldbackgroundcolor; + + CDC* pDC = CDC::FromHandle(lpDIS->hDC); + + pDC->GetCharWidth((UINT) ' ', (UINT) ' ', &m_nSpaceWidth); + pDC->GetCharWidth((UINT) 'c', (UINT) 'c', &m_nAveCharWidth); + + for (int i = 0; i < MAXTABSTOPS; i++) + m_nTabStopPositions[i] = (i+1) * m_nAveCharWidth * m_nTabPosition; + + // draw focus rectangle when no items in listbox + if (lpDIS->itemID == (UINT)-1) + { + if (lpDIS->itemAction & ODA_FOCUS) + pDC->DrawFocusRect(&lpDIS->rcItem); + return; + } + else + { + int selChange = lpDIS->itemAction & ODA_SELECT; + int focusChange = lpDIS->itemAction & ODA_FOCUS; + int drawEntire = lpDIS->itemAction & ODA_DRAWENTIRE; + + if (selChange || drawEntire) + { + BOOL sel = lpDIS->itemState & ODS_SELECTED; + + int nLen = CListBox::GetTextLen(lpDIS->itemID); + if (nLen != LB_ERR) + { + char *buf = new char [nLen + 10]; + ASSERT(buf); + if (buf && (GetTextWithColor(lpDIS->itemID, (LPSTR)buf) != LB_ERR)) + { + // set text color from first character in string - + // NOTE: 1 was added to color index to avoid asserts by CString + int itext = int (buf[0] - 1); + + // set background color from second character in string - + // NOTE: 1 was added to color index to avoid asserts by CString + int iback = int (buf[1] - 1); + buf[0] = ' '; + buf[1] = ' '; + COLORREF textcolor = sel ? m_ColorHighlightText : ColorTable[itext]; + oldtextcolor = pDC->SetTextColor(textcolor); + COLORREF backgroundcolor = sel ? m_ColorHighlight : ColorTable[iback]; + oldbackgroundcolor = pDC->SetBkColor(backgroundcolor); + + // fill the rectangle with the background color the fast way + pDC->ExtTextOut(0, 0, ETO_OPAQUE, &lpDIS->rcItem, NULL, 0, NULL); + + pDC->TabbedTextOut(lpDIS->rcItem.left, lpDIS->rcItem.top, &buf[2], + strlen(&buf[2]), MAXTABSTOPS, (LPINT)m_nTabStopPositions, 0); + + CSize size; + size = pDC->GetOutputTextExtent(&buf[2]); + int nScrollBarWidth = ::GetSystemMetrics(SM_CXVSCROLL); + size.cx += nScrollBarWidth; // in case of vertical scrollbar + + int cxExtent = (size.cx > m_cxExtent) ? size.cx : m_cxExtent; + + if (cxExtent > m_cxExtent) + { + m_cxExtent = cxExtent; + SetHorizontalExtent(m_cxExtent+(m_cxExtent/32)); + } + } + if (buf) + delete [] buf; + } + } + + if (focusChange || (drawEntire && (lpDIS->itemState & ODS_FOCUS))) + pDC->DrawFocusRect(&lpDIS->rcItem); + } +} + +////////////////////////////////////////////////////////////////////////////// +// GetTextWithColor - get text string with color bytes +int CXListBox::GetTextWithColor(int nIndex, LPTSTR lpszBuffer) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return LB_ERR; + } + + ASSERT(lpszBuffer); + lpszBuffer[0] = 0; + return CListBox::GetText(nIndex, lpszBuffer); +} + +////////////////////////////////////////////////////////////////////////////// +// GetTextWithColor - get text string with color bytes +void CXListBox::GetTextWithColor(int nIndex, CString& rString) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + rString.Empty(); + CListBox::GetText(nIndex, rString); +} + +////////////////////////////////////////////////////////////////////////////// +// GetText - for compatibility with CListBox (no color bytes) +int CXListBox::GetText(int nIndex, LPTSTR lpszBuffer) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return LB_ERR; + } + + ASSERT(lpszBuffer); + + lpszBuffer[0] = 0; + + int nRet = CListBox::GetText(nIndex, lpszBuffer); + + int n = strlen(lpszBuffer); + if (n > 2) + memcpy(&lpszBuffer[0], &lpszBuffer[2], n-1); // copy nul too + + return nRet; +} + +////////////////////////////////////////////////////////////////////////////// +// GetText - for compatibility with CListBox (no color bytes) +void CXListBox::GetText(int nIndex, CString& rString) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + CString str; + str.Empty(); + CListBox::GetText(nIndex, str); + if ((!str.IsEmpty()) && (str.GetLength() > 2)) + rString = str.Mid(2); + else + rString.Empty(); +} + +////////////////////////////////////////////////////////////////////////////// +// GetTextLen - for compatibility with CListBox (no color bytes) +int CXListBox::GetTextLen(int nIndex) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return LB_ERR; + } + + int n = CListBox::GetTextLen(nIndex); + if (n != LB_ERR && n >= 2) + n -= 2; + return n; +} + +////////////////////////////////////////////////////////////////////////////// +// SearchString +int CXListBox::SearchString(int nStartAfter, LPCTSTR lpszItem, BOOL bExact) const +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return LB_ERR; + } + + // start the search after specified index + int nIndex = nStartAfter + 1; + + int nCount = GetCount(); + if (nCount == LB_ERR) + return LB_ERR; + + // convert string to search for to lower case + CString strItem; + strItem = lpszItem; + strItem.MakeLower(); + int nItemSize = strItem.GetLength(); + + CString strText; + + // search until end + for ( ; nIndex < nCount; nIndex++) + { + GetText(nIndex, strText); + strText.MakeLower(); + if (!bExact) + strText = strText.Left(nItemSize); + if (strText == strItem) + return nIndex; + } + + // if we started at beginning there is no more to do, search failed + if (nStartAfter == -1) + return LB_ERR; + + // search until we reach beginning index + for (nIndex = 0; (nIndex <= nStartAfter) && (nIndex < nCount); nIndex++) + { + GetText(nIndex, strText); + strText.MakeLower(); + if (!bExact) + strText = strText.Left(nItemSize); + if (strText == strItem) + return nIndex; + } + + return LB_ERR; +} + +////////////////////////////////////////////////////////////////////////////// +// FindString +int CXListBox::FindString(int nStartAfter, LPCTSTR lpszItem) const +{ + return SearchString(nStartAfter, lpszItem, FALSE); +} + +////////////////////////////////////////////////////////////////////////////// +// SelectString +int CXListBox::SelectString(int nStartAfter, LPCTSTR lpszItem) +{ + int rc = SearchString(nStartAfter, lpszItem, FALSE); + if (rc != LB_ERR) + SetCurSel(rc); + return rc; +} + +////////////////////////////////////////////////////////////////////////////// +// FindStringExact +int CXListBox::FindStringExact(int nStartAfter, LPCTSTR lpszItem) const +{ + return SearchString(nStartAfter, lpszItem, TRUE); +} + +////////////////////////////////////////////////////////////////////////////// +// InsertString - override to add text color +int CXListBox::InsertString(int nIndex, LPCTSTR lpszItem, Color tc, Color bc) +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return LB_ERR; + } + + CString s; + s.Empty(); + s = lpszItem; + + UINT nColor = (UINT) tc; + ASSERT(nColor < 16); + if (nColor >= 16) + tc = Black; + + // don't display \r or \n characters + int i; + while ((i = s.FindOneOf("\r\n")) != -1) + s.SetAt(i, ' '); + + // first character in string is color -- add 1 to color + // to avoid asserts by CString class + CString t; + t .Empty(); + t += (char) (tc + 1); + t += (char) (bc + 1); + t += s; + + // try to insert the string into the listbox + i = CListBox::InsertString(nIndex, t); + + return i; +} + +////////////////////////////////////////////////////////////////////////////// +// AddString - override to add text color +void CXListBox::AddString(LPCTSTR lpszItem) +{ + AddLine(CXListBox::Black, CXListBox::White, lpszItem); +} + +////////////////////////////////////////////////////////////////////////////// +// AddLine +void CXListBox::AddLine(Color tc, Color bc, LPCTSTR lpszLine) +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + CString s; + s.Empty(); + s = lpszLine; + + if (!m_bColor) + { + tc = Black; // to force black-only text + bc = White; + } + + UINT nColor = (UINT) tc; + ASSERT(nColor < 16); + if (nColor >= 16) + tc = Black; + + // don't display \r or \n characters + int i; + while ((i = s.FindOneOf("\r\n")) != -1) + s.SetAt(i, ' '); + + // first character in string is color -- add 1 to color + // to avoid asserts by CString class + CString t; + t .Empty(); + t += (char) (tc + 1); + t += (char) (bc + 1); + t += s; + + // try to add the string to the listbox + i = CListBox::AddString(t); + if (i == LB_ERRSPACE) + { + // will get LB_ERRSPACE if listbox is out of memory + int n = GetCount(); + + if (n == LB_ERR) + return; + + if (n < 2) + return; + + // try to delete some strings to free up some room -- + // don't spend too much time deleting strings, since + // we might be getting a burst of messages + n = (n < 20) ? (n-1) : 20; + if (n <= 0) + n = 1; + + SetRedraw(FALSE); + for (i = 0; i < n; i++) + DeleteString(0); + + i = CListBox::AddString(t); + + SetRedraw(TRUE); + } + + if (i >= 0) + { + SetTopIndex(i); + } + + SetCurSel(-1); +} + +/////////////////////////////////////////////////////////////////////////////// +// Printf +void _cdecl CXListBox::Printf(Color tc, Color bc, UINT nID, LPCTSTR lpszFmt, ...) +{ + char buf[1024], fmt[1024]; + va_list marker; + + // load format string from string resource if + // a resource ID was specified + if (nID) + { + CString s; + if (!s.LoadString(nID)) + { + sprintf(s.GetBufferSetLength(80), "Failed to load string resource %u", + nID); + s.ReleaseBuffer(-1); + } + strncpy(fmt, s, sizeof(fmt)-1); + } + else + { + // format string was passed as parameter + strncpy(fmt, lpszFmt, sizeof(fmt)-1); + } + fmt[sizeof(fmt)-1] = 0; + + // combine output string and variables + va_start(marker, lpszFmt); + _vsnprintf(buf, sizeof(buf)-1, fmt, marker); + va_end(marker); + buf[sizeof(buf)-1] = 0; + + AddLine(tc, bc, buf); +} + +////////////////////////////////////////////////////////////////////////////// +// EnableColor +void CXListBox::EnableColor (BOOL bEnable) +{ + m_bColor = bEnable; +} + +////////////////////////////////////////////////////////////////////////////// +// SetTabPosition +void CXListBox::SetTabPosition(int nSpacesPerTab) +{ + ASSERT(nSpacesPerTab > 0 && nSpacesPerTab < 11); + + m_nTabPosition = nSpacesPerTab; + + CDC* pDC = GetDC(); + + if (pDC) + { + TEXTMETRIC tm; + pDC->GetTextMetrics(&tm); + + pDC->GetCharWidth((UINT) ' ', (UINT) ' ', &m_nSpaceWidth); + pDC->GetCharWidth((UINT) '9', (UINT) '9', &m_nAveCharWidth); + + for (int i = 0; i < MAXTABSTOPS; i++) + m_nTabStopPositions[i] = (i+1) * m_nAveCharWidth * m_nTabPosition; + + ReleaseDC(pDC); + } +} + +////////////////////////////////////////////////////////////////////////////// +// GetVisibleLines +int CXListBox::GetVisibleLines() +{ + int nCount = 0; + + CDC* pDC = GetDC(); + + if (pDC) + { + TEXTMETRIC tm; + pDC->GetTextMetrics(&tm); + int h = tm.tmHeight + tm.tmInternalLeading; + ReleaseDC(pDC); + + CRect rect; + GetClientRect(&rect); + nCount = rect.Height() / h; + } + return nCount; +} + +////////////////////////////////////////////////////////////////////////////// +// ResetContent +void CXListBox::ResetContent() +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + CListBox::ResetContent(); + + m_cxExtent = 0; + + SetHorizontalExtent(m_cxExtent); +} + +void CXListBox::SelRemove() +{ + int nCount = GetCount(); + int nSel = 0; + + for (int i = nCount; i >= 0; i--) + { + if (GetSel(i) > 0) + { + DeleteString( i ); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// SetFont +void CXListBox::SetFont(CFont *pFont, BOOL bRedraw) +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + CListBox::SetFont(pFont, bRedraw); + + CDC* pDC = GetDC(); + + if (pDC) + { + CFont *pOldFont = pDC->SelectObject(pFont); + + TEXTMETRIC tm; + pDC->GetTextMetrics(&tm); + int h = tm.tmHeight; + SetItemHeight(0, h); + + pDC->SelectObject(pOldFont); + + pDC->GetCharWidth((UINT) ' ', (UINT) ' ', &m_nSpaceWidth); + pDC->GetCharWidth((UINT) '9', (UINT) '9', &m_nAveCharWidth); + + for (int i = 0; i < MAXTABSTOPS; i++) + m_nTabStopPositions[i] = (i+1) * m_nAveCharWidth * m_nTabPosition; + + ReleaseDC(pDC); + } + + m_cxExtent = 0; +} + +////////////////////////////////////////////////////////////////////////////// +// OnLButtonDblClk +void CXListBox::OnLButtonDblClk(UINT nFlags, CPoint point) +{ + CListBox::OnLButtonDblClk(nFlags, point); +} + +////////////////////////////////////////////////////////////////////////////// +// OnContextMenu +void CXListBox::OnContextMenu(CWnd* /*pWnd*/, CPoint point) +{ + if (m_nContextMenuId == -1) + { + TRACE(" no context menu\n"); + return; + } + + CMenu menu; + if (!menu.LoadMenu(m_nContextMenuId)) + { + TRACE(" ERROR failed to load %d\n", m_nContextMenuId); + return; + } + + menu.GetSubMenu(0)->TrackPopupMenu(0, + point.x, point.y, this, NULL); +} + +////////////////////////////////////////////////////////////////////////////// +// OnEditCopy +void CXListBox::OnEditCopy() +{ + CString str; + str.Empty(); + + int nCount = GetCount(); + int nSel = 0; + + for (int i = 0; i < nCount; i++) + { + if (GetSel(i) > 0) + { + CString s; + s.Empty(); + GetText(i, s); + if (!s.IsEmpty()) + { + nSel++; + s.TrimLeft("\r\n"); + s.TrimRight("\r\n"); + if (s.Find('\n') == -1) + s += "\n"; + s.Replace("\t", " "); + str += s; + } + } + } + + if (!str.IsEmpty()) + CClipboard::SetText(str); +} + +////////////////////////////////////////////////////////////////////////////// +// OnEditClear +void CXListBox::OnEditClear() +{ +// ResetContent(); + SelRemove(); +} + +////////////////////////////////////////////////////////////////////////////// +// OnEditSelectAll +void CXListBox::OnEditSelectAll() +{ + if (!::IsWindow(m_hWnd)) + { + ASSERT(FALSE); + return; + } + + SelItemRange(TRUE, 0, GetCount()-1); +}