403 lines
12 KiB
C++
403 lines
12 KiB
C++
|
/***************************************************************************/
|
||
|
/** Microsoft Windows **/
|
||
|
/** Copyright(c) Microsoft Corp., 1993 **/
|
||
|
/***************************************************************************/
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
ICONLBOX.C
|
||
|
|
||
|
Implementation for IconListBox class
|
||
|
|
||
|
May 93, JimH
|
||
|
|
||
|
See ICONLBOX.H for details on use.
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include "npcommon.h"
|
||
|
#include <windows.h>
|
||
|
#include <memory.h>
|
||
|
#include <iconlbox.h>
|
||
|
|
||
|
#if defined(DEBUG)
|
||
|
static const char szFileName[] = __FILE__;
|
||
|
#define _FILENAME_DEFINED_ONCE szFileName
|
||
|
#endif
|
||
|
#include <npassert.h>
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox constructor
|
||
|
|
||
|
Some initialization is done here, and some is done in SetHeight
|
||
|
(when window handles are known.)
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
IconListBox::IconListBox(HINSTANCE hInst, int nCtlID,
|
||
|
int iconWidth, int iconHeight) :
|
||
|
_nCtlID(nCtlID), _hInst(hInst),
|
||
|
_iconWidth(iconWidth), _iconHeight(iconHeight),
|
||
|
_hbrSelected(NULL), _hbrUnselected(NULL),
|
||
|
_fCombo(FALSE), _cIcons(0), _cTabs(0),_iCurrentMaxHorzExt(0),
|
||
|
_hwndDialog(NULL), _hwndListBox(NULL)
|
||
|
{
|
||
|
_colSel = ::GetSysColor(COLOR_HIGHLIGHT);
|
||
|
_colSelText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
|
||
|
_colUnsel = ::GetSysColor(COLOR_WINDOW);
|
||
|
_colUnselText = ::GetSysColor(COLOR_WINDOWTEXT);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox destructor
|
||
|
|
||
|
deletes any GDI objects created by IconListBox
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
IconListBox::~IconListBox()
|
||
|
{
|
||
|
for (int i = 0; i < _cIcons; i++)
|
||
|
{
|
||
|
if (_aIcons[i].hbmSelected)
|
||
|
{
|
||
|
if (_aIcons[i].hbmSelected)
|
||
|
{
|
||
|
::DeleteObject(_aIcons[i].hbmSelected);
|
||
|
::DeleteObject(_aIcons[i].hbmUnselected);
|
||
|
}
|
||
|
|
||
|
// Subsequent _aIcons may have used the same bitmap.
|
||
|
// Mark those as already deleted.
|
||
|
|
||
|
for (int j = i + 1; j < _cIcons; j++)
|
||
|
{
|
||
|
if (_aIcons[j].nResID == _aIcons[i].nResID)
|
||
|
{
|
||
|
_aIcons[j].hbmSelected = NULL;
|
||
|
_aIcons[j].hbmUnselected = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_hbrSelected)
|
||
|
::DeleteObject(_hbrSelected);
|
||
|
|
||
|
if (_hbrUnselected)
|
||
|
::DeleteObject(_hbrUnselected);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox::SetHeight
|
||
|
|
||
|
This function MUST be called in reponse to the WM_MEASUREITEM message.
|
||
|
It creates some GDI objects, and initializes class variables not known
|
||
|
at construction time.
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
void IconListBox::SetHeight(HWND hwndDlg,
|
||
|
LPMEASUREITEMSTRUCT lpm,
|
||
|
int itemHeight) // defaults to 16
|
||
|
{
|
||
|
ASSERT(hwndDlg != NULL);
|
||
|
ASSERT((int)lpm->CtlID == _nCtlID);
|
||
|
|
||
|
_hwndDialog = hwndDlg;
|
||
|
_hwndListBox = ::GetDlgItem(_hwndDialog, _nCtlID);
|
||
|
|
||
|
// Determine if this is a combo box
|
||
|
|
||
|
char szClass[32];
|
||
|
GetClassName(_hwndListBox,szClass,sizeof(szClass));
|
||
|
if (::lstrcmpi(szClass,"combobox") == 0 )
|
||
|
_fCombo = TRUE;
|
||
|
|
||
|
|
||
|
// Create the background brushes used for filling listbox entries...
|
||
|
|
||
|
_hbrSelected = ::CreateSolidBrush(_colSel);
|
||
|
_hbrUnselected = ::CreateSolidBrush(_colUnsel);
|
||
|
|
||
|
// Calculate how to centre the text vertically in the listbox item.
|
||
|
|
||
|
TEXTMETRIC tm;
|
||
|
HDC hDC = ::GetDC(hwndDlg);
|
||
|
|
||
|
GetTextMetrics(hDC, &tm);
|
||
|
|
||
|
// Set the only lpm entry that matters
|
||
|
|
||
|
// allow larger height if passed in - but at least large enough
|
||
|
// to fit font.
|
||
|
|
||
|
lpm->itemHeight = max( itemHeight, tm.tmHeight + tm.tmExternalLeading );
|
||
|
|
||
|
_nTextOffset = tm.tmExternalLeading / 2 + 1;
|
||
|
|
||
|
::ReleaseDC(hwndDlg, hDC);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox::DrawItem
|
||
|
|
||
|
This function MUST be called in response to the WM_DRAWITEM message.
|
||
|
It takes care of drawing listbox items in selected or unselected state.
|
||
|
|
||
|
Drawing and undrawing the focus rectangle takes advantage of the fact
|
||
|
that DrawFocusRect uses an XOR pen, and Windows is nice enough to assume
|
||
|
this in the order of the ODA_FOCUS messages.
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
void IconListBox::DrawItem(LPDRAWITEMSTRUCT lpd)
|
||
|
{
|
||
|
ASSERT(_hwndDialog != NULL); // make sure SetHeight has been called
|
||
|
|
||
|
char string[MAXSTRINGLEN];
|
||
|
BOOL bSelected = (lpd->itemState & ODS_SELECTED);
|
||
|
|
||
|
GetString(lpd->itemID, string);
|
||
|
|
||
|
// fill entire rectangle with background color
|
||
|
|
||
|
::FillRect(lpd->hDC, &(lpd->rcItem),
|
||
|
bSelected ? _hbrSelected : _hbrUnselected);
|
||
|
|
||
|
// Look for registered icon to display, and paint it if found
|
||
|
|
||
|
for (int id = 0; id < _cIcons; id++)
|
||
|
if (_aIcons[id].nID == (int) lpd->itemData)
|
||
|
break;
|
||
|
|
||
|
if (id != _cIcons) // if we found a bitmap to display
|
||
|
{
|
||
|
HDC hdcMem = ::CreateCompatibleDC(lpd->hDC);
|
||
|
HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hdcMem,
|
||
|
bSelected ? _aIcons[id].hbmSelected : _aIcons[id].hbmUnselected);
|
||
|
|
||
|
// draw bitmap ICONSPACE pixels from left and centred vertically
|
||
|
|
||
|
int x = lpd->rcItem.left + ICONSPACE;
|
||
|
int y = ((lpd->rcItem.bottom - lpd->rcItem.top) - _iconHeight) / 2;
|
||
|
y += lpd->rcItem.top;
|
||
|
|
||
|
::BitBlt(lpd->hDC, x, y, _iconWidth, _iconHeight, hdcMem,
|
||
|
_aIcons[id].x, _aIcons[id].y, SRCCOPY);
|
||
|
|
||
|
::SelectObject(hdcMem, hOldBitmap);
|
||
|
::DeleteDC(hdcMem);
|
||
|
}
|
||
|
|
||
|
if (lpd->itemState & ODS_FOCUS)
|
||
|
::DrawFocusRect(lpd->hDC, &(lpd->rcItem));
|
||
|
|
||
|
|
||
|
lpd->rcItem.left += (_iconWidth + (2 * ICONSPACE));
|
||
|
|
||
|
// Paint string
|
||
|
|
||
|
::SetTextColor(lpd->hDC, bSelected ? _colSelText : _colUnselText);
|
||
|
::SetBkColor(lpd->hDC, bSelected ? _colSel : _colUnsel);
|
||
|
(lpd->rcItem.top) += _nTextOffset;
|
||
|
|
||
|
if (_cTabs == 0) // if no tabs registered
|
||
|
{
|
||
|
::DrawText(lpd->hDC, string, lstrlen(string), &(lpd->rcItem),
|
||
|
DT_LEFT | DT_EXPANDTABS);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
::TabbedTextOut(lpd->hDC, lpd->rcItem.left, lpd->rcItem.top,
|
||
|
string, lstrlen(string), _cTabs, _aTabs, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox::RegisterIcons
|
||
|
|
||
|
Icons must be registered before they can be referenced in AddString.
|
||
|
|
||
|
Note that if you are using several icons from the same bitmap (with different
|
||
|
x and y offsets) they must all have the same background color.
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
void IconListBox::RegisterIcon( int nIconID, // caller's code
|
||
|
int nResID, // RC file id
|
||
|
int x, int y, // top left corner
|
||
|
COLORREF colTransparent) // def. bright green
|
||
|
{
|
||
|
ASSERT( _cIcons < MAXICONS );
|
||
|
|
||
|
_aIcons[_cIcons].nID = nIconID;
|
||
|
_aIcons[_cIcons].nResID = nResID;
|
||
|
_aIcons[_cIcons].x = x;
|
||
|
_aIcons[_cIcons].y = y;
|
||
|
|
||
|
// Check to see if we already have bitmaps for this resource ID
|
||
|
// (which may have different x and y offsets.)
|
||
|
|
||
|
for (int i = 0; i < _cIcons; i++)
|
||
|
{
|
||
|
if (_aIcons[i].nResID == nResID)
|
||
|
{
|
||
|
_aIcons[_cIcons].hbmSelected = _aIcons[i].hbmSelected;
|
||
|
_aIcons[_cIcons].hbmUnselected = _aIcons[i].hbmUnselected;
|
||
|
_cIcons++;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Otherwise, create new selected and unselected bitmaps
|
||
|
|
||
|
// Get pointer to DIB
|
||
|
|
||
|
HRSRC h = ::FindResource(_hInst, MAKEINTRESOURCE(nResID), RT_BITMAP);
|
||
|
if (h == NULL)
|
||
|
return;
|
||
|
|
||
|
HANDLE hRes = ::LoadResource(_hInst, h);
|
||
|
if (hRes == NULL)
|
||
|
return;
|
||
|
|
||
|
LPBITMAPINFOHEADER lpInfo = (LPBITMAPINFOHEADER) LockResource(hRes);
|
||
|
if (NULL == lpInfo)
|
||
|
return;
|
||
|
|
||
|
// Get pointers to start of color table, and start of actual bitmap bits
|
||
|
|
||
|
// Note that we make a copy of the bitmap header info and the color
|
||
|
// table. This is so applications that use iconlistbox can keep their
|
||
|
// resource segments read only.
|
||
|
|
||
|
LPBYTE lpBits = (LPBYTE)
|
||
|
(lpInfo + 1) + (1 << (lpInfo->biBitCount)) * sizeof(RGBQUAD);
|
||
|
|
||
|
int cbCopy = (int) (lpBits - (LPBYTE)lpInfo);
|
||
|
|
||
|
BYTE *lpCopy = new BYTE[cbCopy];
|
||
|
|
||
|
if (!lpCopy)
|
||
|
return;
|
||
|
|
||
|
memcpy(lpCopy, lpInfo, cbCopy);
|
||
|
|
||
|
RGBQUAD FAR *lpRGBQ =
|
||
|
(RGBQUAD FAR *) ((LPSTR)lpCopy + lpInfo->biSize);
|
||
|
|
||
|
// Find transparent color in color table
|
||
|
|
||
|
BOOL bFound = FALSE; // did we find a transparent match?
|
||
|
|
||
|
int nColorTableSize = (int) (lpBits - (LPBYTE)lpRGBQ);
|
||
|
nColorTableSize /= sizeof(RGBQUAD);
|
||
|
|
||
|
for (i = 0; i < nColorTableSize; i++)
|
||
|
{
|
||
|
if (colTransparent ==
|
||
|
RGB(lpRGBQ[i].rgbRed, lpRGBQ[i].rgbGreen, lpRGBQ[i].rgbBlue))
|
||
|
{
|
||
|
bFound = TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Replace the transparent color with the background for selected and
|
||
|
// unselected entries. Use these to create selected and unselected
|
||
|
// bitmaps, and restore color table.
|
||
|
|
||
|
RGBQUAD rgbqTemp; // color table entry to replace
|
||
|
HDC hDC = ::GetDC(_hwndDialog);
|
||
|
|
||
|
if (bFound)
|
||
|
{
|
||
|
rgbqTemp = lpRGBQ[i];
|
||
|
lpRGBQ[i].rgbRed = GetRValue(_colUnsel);
|
||
|
lpRGBQ[i].rgbBlue = GetBValue(_colUnsel);
|
||
|
lpRGBQ[i].rgbGreen = GetGValue(_colUnsel);
|
||
|
}
|
||
|
_aIcons[_cIcons].hbmUnselected = ::CreateDIBitmap(hDC,
|
||
|
(LPBITMAPINFOHEADER)lpCopy, CBM_INIT, lpBits,
|
||
|
(LPBITMAPINFO)lpCopy, DIB_RGB_COLORS);
|
||
|
|
||
|
if (bFound)
|
||
|
{
|
||
|
lpRGBQ[i].rgbRed = GetRValue(_colSel);
|
||
|
lpRGBQ[i].rgbBlue = GetBValue(_colSel);
|
||
|
lpRGBQ[i].rgbGreen = GetGValue(_colSel);
|
||
|
}
|
||
|
_aIcons[_cIcons].hbmSelected = ::CreateDIBitmap(hDC,
|
||
|
(LPBITMAPINFOHEADER)lpCopy, CBM_INIT, lpBits,
|
||
|
(LPBITMAPINFO)lpCopy, DIB_RGB_COLORS);
|
||
|
|
||
|
if (bFound)
|
||
|
lpRGBQ[i] = rgbqTemp; // restore original color table entry
|
||
|
|
||
|
::ReleaseDC(_hwndDialog, hDC);
|
||
|
::FreeResource(hRes);
|
||
|
delete [] lpCopy;
|
||
|
|
||
|
_cIcons++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox::SetTabStops
|
||
|
|
||
|
Since this is an owner-draw listbox, we can't rely on LB_SETTABS.
|
||
|
Instead, tabs are registered here and TabbedTextOut is used to display
|
||
|
strings. Dialogbox units have to be converted to pixels.
|
||
|
|
||
|
****************************************************************************/
|
||
|
|
||
|
void IconListBox::SetTabStops(int cTabs, const int *pTabs)
|
||
|
{
|
||
|
ASSERT(cTabs <= MAXTABS);
|
||
|
|
||
|
int nSize = (int) LOWORD(GetDialogBaseUnits());
|
||
|
|
||
|
for (int i = 0; i < cTabs; i++)
|
||
|
_aTabs[i] = ((nSize * pTabs[i]) / 4);
|
||
|
|
||
|
_cTabs = cTabs;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
|
||
|
IconListBox::UpdateHorizontalExtent
|
||
|
|
||
|
****************************************************************************/
|
||
|
int IconListBox::UpdateHorizontalExtent(int nIcon,const char *string)
|
||
|
{
|
||
|
ASSERT(_hwndDialog != NULL); // make sure SetHeight has been called
|
||
|
|
||
|
if (!string)
|
||
|
return 0;
|
||
|
// Calculate width in pixels for given string, taking into account icon, spacing and tabs
|
||
|
int iItemWidth = ICONSPACE + (_iconWidth + (2 * ICONSPACE));
|
||
|
HDC hDC = ::GetDC(_hwndDialog);
|
||
|
iItemWidth += LOWORD(GetTabbedTextExtent(hDC,string,::lstrlen(string),_cTabs, _aTabs));
|
||
|
::ReleaseDC(_hwndDialog, hDC);
|
||
|
|
||
|
// Update maximum value
|
||
|
_iCurrentMaxHorzExt = max(_iCurrentMaxHorzExt,iItemWidth);
|
||
|
|
||
|
return (int)SendDlgItemMessage(_hwndDialog,_nCtlID,
|
||
|
LB_SETHORIZONTALEXTENT,
|
||
|
(WPARAM)_iCurrentMaxHorzExt,0L);
|
||
|
|
||
|
}
|