You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
592 lines
14 KiB
592 lines
14 KiB
//-----------------------------------------------------------------------------
|
|
// File: flexlistbox.cpp
|
|
//
|
|
// Desc: Implements a list box control that can display a list of text strings,
|
|
// each can be selected by mouse. The class CFlexListBox is derived from
|
|
// CFlexWnd. It is used by the class CFlexComboBox when it needs to
|
|
// expand to show the list of choices.
|
|
//
|
|
// Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "common.hpp"
|
|
|
|
|
|
CFlexListBox::CFlexListBox() :
|
|
m_nTextHeight(-1),
|
|
m_hWndNotify(NULL),
|
|
m_rgbText(RGB(255,255,255)),
|
|
m_rgbBk(RGB(0,0,0)),
|
|
m_rgbSelText(RGB(0,0,255)),
|
|
m_rgbSelBk(RGB(0,0,0)),
|
|
m_rgbFill(RGB(0,0,255)),
|
|
m_rgbLine(RGB(0,255,255)),
|
|
m_dwFlags(0),
|
|
m_nTopIndex(0),
|
|
m_nSBWidth(11),
|
|
m_hFont(NULL),
|
|
m_bOpenClick(FALSE),
|
|
m_bDragging(FALSE),
|
|
m_bCapture(FALSE),
|
|
m_nSelItem(-1),
|
|
m_bVertSB(FALSE)
|
|
{
|
|
}
|
|
|
|
CFlexListBox::~CFlexListBox()
|
|
{
|
|
}
|
|
|
|
CFlexListBox *CreateFlexListBox(FLEXLISTBOXCREATESTRUCT *pcs)
|
|
{
|
|
CFlexListBox *psb = new CFlexListBox;
|
|
|
|
if (psb && psb->Create(pcs))
|
|
return psb;
|
|
|
|
delete psb;
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CFlexListBox::CreateForSingleSel(FLEXLISTBOXCREATESTRUCT *pcs)
|
|
{
|
|
if (!Create(pcs))
|
|
return FALSE;
|
|
|
|
StartSel();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CFlexListBox::Create(FLEXLISTBOXCREATESTRUCT *pcs)
|
|
{
|
|
if (this == NULL)
|
|
return FALSE;
|
|
|
|
if (m_hWnd)
|
|
Destroy();
|
|
|
|
if (pcs == NULL)
|
|
return FALSE;
|
|
|
|
if (pcs->dwSize != sizeof(FLEXLISTBOXCREATESTRUCT))
|
|
return FALSE;
|
|
|
|
m_hWndNotify = pcs->hWndNotify ? pcs->hWndNotify : pcs->hWndParent;
|
|
|
|
m_dwFlags = pcs->dwFlags;
|
|
|
|
SetFont(pcs->hFont);
|
|
SetColors(pcs->rgbText, pcs->rgbBk, pcs->rgbSelText, pcs->rgbSelBk, pcs->rgbFill, pcs->rgbLine);
|
|
m_VertSB.SetColors(pcs->rgbBk, pcs->rgbFill, pcs->rgbLine);
|
|
m_nSBWidth = pcs->nSBWidth;
|
|
|
|
if (!CFlexWnd::Create(pcs->hWndParent, pcs->rect, FALSE))
|
|
return FALSE;
|
|
|
|
FLEXSCROLLBARCREATESTRUCT sbcs;
|
|
sbcs.dwSize = sizeof(FLEXSCROLLBARCREATESTRUCT);
|
|
sbcs.dwFlags = FSBF_VERT;
|
|
sbcs.min = 0;
|
|
sbcs.max = 3;
|
|
sbcs.page = 1;
|
|
sbcs.pos = 1;
|
|
sbcs.hWndParent = m_hWnd;
|
|
sbcs.hWndNotify = m_hWnd;
|
|
RECT rect = {0, 0, 1, 1};
|
|
sbcs.rect = rect;
|
|
sbcs.bVisible = FALSE;
|
|
m_VertSB.Create(&sbcs);
|
|
|
|
Calc();
|
|
|
|
// show if we want it shown
|
|
if (pcs->bVisible)
|
|
ShowWindow(m_hWnd, SW_SHOW);
|
|
if (m_bVertSB)
|
|
SetVertSB();
|
|
|
|
// TODO: make sure that creation sends no notifications.
|
|
// all initial notifications should be sent here.
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CFlexListBox::Calc()
|
|
{
|
|
// handle getting text height
|
|
if (m_nTextHeight == -1)
|
|
{
|
|
m_nTextHeight = GetTextHeight(m_hFont);
|
|
Invalidate();
|
|
assert(m_nTextHeight != -1);
|
|
}
|
|
|
|
// don't do the rest unless we've been created
|
|
if (m_hWnd == NULL)
|
|
return;
|
|
|
|
// handle integral height
|
|
int iUsedHeight = m_ItemArray.GetSize() * m_nTextHeight;
|
|
// If more than max height, use the max height
|
|
if (iUsedHeight > g_UserNamesRect.bottom - g_UserNamesRect.top)
|
|
iUsedHeight = g_UserNamesRect.bottom - g_UserNamesRect.top;
|
|
|
|
SIZE client = GetClientSize();
|
|
int fit = iUsedHeight / m_nTextHeight;
|
|
if (fit < 1)
|
|
fit = 1;
|
|
int setheight = (m_dwFlags & FLBF_INTEGRALHEIGHT) ? fit * m_nTextHeight : iUsedHeight;
|
|
if (setheight != client.cy)
|
|
SetWindowPos(m_hWnd, NULL, 0, 0, client.cx, setheight,
|
|
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
|
|
|
// handle scroll bar
|
|
SetVertSB(m_ItemArray.GetSize() > fit);
|
|
}
|
|
|
|
void CFlexListBox::SetFont(HFONT hFont)
|
|
{
|
|
m_hFont = hFont;
|
|
m_nTextHeight = -1;
|
|
Calc();
|
|
}
|
|
|
|
void CFlexListBox::OnPaint(HDC hDC)
|
|
{
|
|
HDC hBDC = NULL, hODC = NULL;
|
|
CBitmap *pbm = NULL;
|
|
|
|
if (!InRenderMode())
|
|
{
|
|
hODC = hDC;
|
|
pbm = CBitmap::Create(GetClientSize(), RGB(0,0,0), hDC);
|
|
if (pbm != NULL)
|
|
{
|
|
hBDC = pbm->BeginPaintInto();
|
|
if (hBDC != NULL)
|
|
{
|
|
hDC = hBDC;
|
|
}
|
|
}
|
|
}
|
|
|
|
InternalPaint(hDC);
|
|
|
|
if (!InRenderMode())
|
|
{
|
|
if (pbm != NULL)
|
|
{
|
|
if (hBDC != NULL)
|
|
{
|
|
pbm->EndPaintInto(hBDC);
|
|
pbm->Draw(hODC);
|
|
}
|
|
delete pbm;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CFlexListBox::InternalPaint(HDC hDC)
|
|
{
|
|
if (m_nTextHeight == -1)
|
|
return;
|
|
|
|
SIZE client = GetClientSize();
|
|
RECT rc = {0,0,0,0};
|
|
GetClientRect(&rc);
|
|
|
|
HGDIOBJ hPen, hOldPen, hBrush, hOldBrush;
|
|
hPen= (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbBk);
|
|
if (hPen != NULL)
|
|
{
|
|
hOldPen = SelectObject(hDC, hPen);
|
|
|
|
hBrush = CreateSolidBrush(m_rgbBk);
|
|
if (hBrush != NULL)
|
|
{
|
|
hOldBrush = SelectObject(hDC, hBrush);
|
|
|
|
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); // Paint entire window black first.
|
|
|
|
if (!m_bVertSB)
|
|
m_nTopIndex = 0;
|
|
|
|
int iLastY;
|
|
for (int at = 0, i = m_nTopIndex; at < client.cy; i++, at += m_nTextHeight)
|
|
{
|
|
RECT rect = {0, at, client.cx, at + m_nTextHeight};
|
|
|
|
if (i < m_ItemArray.GetSize())
|
|
{
|
|
BOOL bSel = m_ItemArray[i].bSelected;
|
|
SetTextColor(hDC, bSel ? m_rgbSelText : m_rgbText);
|
|
SetBkColor(hDC, bSel ? m_rgbSelBk : m_rgbBk);
|
|
DrawText(hDC, m_ItemArray[i].GetText(), -1, &rect, DT_NOPREFIX);
|
|
iLastY = at + m_nTextHeight;
|
|
}
|
|
}
|
|
|
|
SelectObject(hDC, hOldBrush);
|
|
DeleteObject(hBrush);
|
|
}
|
|
|
|
SelectObject(hDC, hOldPen);
|
|
DeleteObject(hPen);
|
|
}
|
|
|
|
// Draw an outline around the box
|
|
hPen = (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbLine);
|
|
if (hPen != NULL)
|
|
{
|
|
hOldPen = SelectObject(hDC, hPen);
|
|
hOldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
|
|
Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom);
|
|
|
|
SelectObject(hDC, hOldPen);
|
|
DeleteObject(hPen);
|
|
}
|
|
}
|
|
|
|
int CFlexListBox::AddString(LPCTSTR str)
|
|
{
|
|
int newIndex = m_ItemArray.GetSize();
|
|
m_ItemArray.SetSize(newIndex + 1);
|
|
FLEXLISTBOXITEM &i = m_ItemArray[newIndex];
|
|
i.SetText(str);
|
|
i.bSelected = FALSE;
|
|
|
|
SetSBValues();
|
|
Calc();
|
|
Invalidate();
|
|
|
|
return newIndex;
|
|
}
|
|
|
|
void CFlexListBox::StartSel()
|
|
{
|
|
if (m_bDragging)
|
|
return;
|
|
SetTimer(m_hWnd, 5, 200, NULL);
|
|
m_bOpenClick = TRUE; // Initial click on the combobox
|
|
m_bDragging = TRUE;
|
|
m_bCapture = TRUE;
|
|
SetCapture();
|
|
}
|
|
|
|
void CFlexListBox::OnWheel(POINT point, WPARAM wParam)
|
|
{
|
|
if (!m_bVertSB) return;
|
|
|
|
int nPage = MulDiv(m_VertSB.GetPage(), 9, 10) >> 1; // Half a page at a time
|
|
|
|
if ((int)wParam >= 0)
|
|
m_VertSB.AdjustPos(-nPage);
|
|
else
|
|
m_VertSB.AdjustPos(nPage);
|
|
|
|
m_nTopIndex = m_VertSB.GetPos();
|
|
if (m_nTopIndex < 0)
|
|
m_nTopIndex = 0;
|
|
Invalidate();
|
|
}
|
|
|
|
LRESULT CFlexListBox::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// first handle scroll bar messages
|
|
switch (msg)
|
|
{
|
|
case WM_FLEXVSCROLL:
|
|
int code = (int)wParam;
|
|
CFlexScrollBar *pSB = (CFlexScrollBar *)lParam;
|
|
if (!pSB)
|
|
return 0;
|
|
|
|
int nLine = 1;
|
|
int nPage = MulDiv(pSB->GetPage(), 9, 10);
|
|
|
|
switch (code)
|
|
{
|
|
case SB_LINEUP: pSB->AdjustPos(-nLine); break;
|
|
case SB_LINEDOWN: pSB->AdjustPos(nLine); break;
|
|
case SB_PAGEUP: pSB->AdjustPos(-nPage); break;
|
|
case SB_PAGEDOWN: pSB->AdjustPos(nPage); break;
|
|
case SB_THUMBTRACK: pSB->SetPos(pSB->GetThumbPos()); break;
|
|
case SB_ENDSCROLL:
|
|
SetCapture(); // Recapture after the scroll bar releases the capture.
|
|
break;
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_FLEXVSCROLL:
|
|
m_nTopIndex = pSB->GetPos();
|
|
if (m_nTopIndex < 0)
|
|
m_nTopIndex = 0;
|
|
break;
|
|
}
|
|
|
|
Invalidate();
|
|
return 0;
|
|
}
|
|
|
|
// now non-scrolly input
|
|
switch (msg)
|
|
{
|
|
case WM_SIZE:
|
|
SetSBValues();
|
|
Calc();
|
|
Invalidate();
|
|
return 0;
|
|
|
|
// make sure flexwnd doesn't do ANYTHING with our mouse messages
|
|
case WM_MOUSEMOVE:
|
|
case WM_LBUTTONUP:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONUP:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_RBUTTONDBLCLK:
|
|
{
|
|
POINT point = {int(signed short(LOWORD(lParam))), int(signed short(HIWORD(lParam)))};
|
|
m_point = point;
|
|
}
|
|
case WM_TIMER:
|
|
case WM_CAPTURECHANGED:
|
|
break;
|
|
default:
|
|
return CFlexWnd::WndProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
// Check if we clicked the scroll bar area. If so, send the click to the scroll bar.
|
|
RECT rc;
|
|
m_VertSB.GetClientRect(&rc);
|
|
ClientToScreen(m_VertSB.m_hWnd, &rc);
|
|
ScreenToClient(m_hWnd, &rc);
|
|
if (PtInRect(&rc, m_point))
|
|
{
|
|
POINT point = {int(signed short(LOWORD(lParam))), int(signed short(HIWORD(lParam)))};
|
|
ClientToScreen(m_hWnd, &point);
|
|
ScreenToClient(m_VertSB.m_hWnd, &point);
|
|
PostMessage(m_VertSB.m_hWnd, WM_LBUTTONDOWN, wParam, point.x + (point.y << 16)); // This will make it lose capture.
|
|
} else
|
|
StartSel();
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
case WM_LBUTTONUP:
|
|
if (!m_bDragging)
|
|
break;
|
|
if (m_nTextHeight == -1)
|
|
break;
|
|
case WM_TIMER:
|
|
if (m_bDragging || msg != WM_TIMER)
|
|
{
|
|
int adj = m_point.y < 0 ? -1 : 0;
|
|
SelectAndShowSingleItem(adj + m_point.y / m_nTextHeight + m_nTopIndex, msg != WM_MOUSEMOVE);
|
|
Notify(FLBN_SEL);
|
|
}
|
|
// Check if the mouse cursor is within the listbox rectangle. If not, don't show the tooltip.
|
|
if (msg == WM_MOUSEMOVE)
|
|
{
|
|
RECT rect;
|
|
GetClientRect(&rect);
|
|
POINT pt;
|
|
GetCursorPos(&pt);
|
|
ScreenToClient(m_hWnd, &pt);
|
|
if (!PtInRect(&rect, pt))
|
|
{
|
|
CFlexWnd::s_ToolTip.SetToolTipParent(NULL);
|
|
CFlexWnd::s_ToolTip.SetEnable(FALSE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_CAPTURECHANGED:
|
|
if ((HWND)lParam == m_VertSB.m_hWnd) // If the scroll bar is getting the capture, we do not clean up.
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
if (m_bOpenClick)
|
|
{
|
|
m_bOpenClick = FALSE; // If this is the result of clicking the combobox window, don't release capture.
|
|
break;
|
|
}
|
|
if (m_bCapture)
|
|
{
|
|
m_bCapture = FALSE;
|
|
KillTimer(m_hWnd, 5);
|
|
ReleaseCapture();
|
|
m_bDragging = FALSE;
|
|
BOOL bCancel = TRUE;
|
|
if (msg == WM_LBUTTONUP)
|
|
{
|
|
RECT wrect;
|
|
GetClientRect(&wrect);
|
|
if (PtInRect(&wrect, m_point))
|
|
bCancel = FALSE;
|
|
}
|
|
Notify(bCancel ? FLBN_CANCEL : FLBN_FINALSEL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CFlexListBox::SelectAndShowSingleItem(int i, BOOL bScroll)
|
|
{
|
|
int nItems = m_ItemArray.GetSize();
|
|
|
|
if (nItems < 1)
|
|
{
|
|
m_nSelItem = i; // We have to update m_nSelItem even if there is no items because the username combobox
|
|
// is not initialized when there is only 1 user. Selection of user 0 is assumed.
|
|
return;
|
|
}
|
|
|
|
if (i < 0)
|
|
i = 0;
|
|
if (i >= nItems)
|
|
i = nItems - 1;
|
|
|
|
if (m_nSelItem >= 0 && m_nSelItem < nItems)
|
|
m_ItemArray[m_nSelItem].bSelected = FALSE;
|
|
|
|
m_nSelItem = i;
|
|
m_ItemArray[m_nSelItem].bSelected = TRUE;
|
|
|
|
if (bScroll)
|
|
{
|
|
SIZE client = GetClientSize();
|
|
int nBottomIndex = m_nTopIndex + client.cy / m_nTextHeight - 1;
|
|
|
|
if (m_nSelItem < m_nTopIndex)
|
|
m_nTopIndex = m_nSelItem;
|
|
|
|
assert(m_nTopIndex >= 0);
|
|
|
|
if (m_nSelItem > nBottomIndex)
|
|
{
|
|
m_nTopIndex += m_nSelItem - nBottomIndex + 1;
|
|
nBottomIndex = m_nSelItem + 1;
|
|
}
|
|
|
|
if (nBottomIndex > nItems - 1)
|
|
m_nTopIndex -= nBottomIndex - nItems + 1;
|
|
|
|
if (m_nTopIndex < 0)
|
|
m_nTopIndex = 0;
|
|
|
|
if (m_nTopIndex >= nItems)
|
|
m_nTopIndex = nItems - 1;
|
|
|
|
assert(m_nTopIndex >= 0 && m_nTopIndex < nItems);
|
|
}
|
|
|
|
if (m_bVertSB)
|
|
SetSBValues();
|
|
|
|
SIZE client = GetClientSize();
|
|
int nBottomIndex = m_nTopIndex + client.cy / m_nTextHeight - 1;
|
|
int iToolTipIndex = m_nSelItem;
|
|
// Make sure that we don't display tooltip for items outside the listbox window
|
|
if (iToolTipIndex > nBottomIndex)
|
|
iToolTipIndex = nBottomIndex;
|
|
if (iToolTipIndex < m_nTopIndex)
|
|
iToolTipIndex = m_nTopIndex;
|
|
// Create and initialize a tooltip if the text is too long to fit.
|
|
RECT rect = {0, 0, client.cx, m_nTextHeight};
|
|
RECT ResultRect = rect;
|
|
HDC hDC = CreateCompatibleDC(NULL);
|
|
if (hDC)
|
|
{
|
|
DrawText(hDC, m_ItemArray[iToolTipIndex].GetText(), -1, &ResultRect, DT_NOPREFIX|DT_CALCRECT);
|
|
DeleteDC(hDC);
|
|
}
|
|
if (ResultRect.right > rect.right)
|
|
{
|
|
TOOLTIPINITPARAM ttip;
|
|
ttip.hWndParent = GetParent(m_hWnd);
|
|
ttip.iSBWidth = m_nSBWidth;
|
|
ttip.dwID = iToolTipIndex;
|
|
ttip.hWndNotify = m_hWnd;
|
|
ttip.tszCaption = GetSelText();
|
|
CFlexToolTip::UpdateToolTipParam(ttip);
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
void CFlexListBox::Notify(int code)
|
|
{
|
|
if (!m_hWndNotify)
|
|
return;
|
|
|
|
SendMessage(m_hWndNotify, WM_FLEXLISTBOX,
|
|
(WPARAM)code, (LPARAM)(LPVOID)this);
|
|
}
|
|
|
|
void CFlexListBox::SetColors(COLORREF text, COLORREF bk, COLORREF seltext, COLORREF selbk, COLORREF fill, COLORREF line)
|
|
{
|
|
m_rgbText = text;
|
|
m_rgbBk = bk;
|
|
m_rgbSelText = seltext;
|
|
m_rgbSelBk = selbk;
|
|
m_rgbFill = fill;
|
|
m_rgbLine = line;
|
|
Invalidate();
|
|
}
|
|
|
|
void CFlexListBox::SetVertSB(BOOL bSet)
|
|
{
|
|
if (bEq(bSet, m_bVertSB))
|
|
return;
|
|
|
|
m_bVertSB = bSet;
|
|
|
|
if (m_hWnd == NULL)
|
|
return;
|
|
|
|
SetVertSB();
|
|
}
|
|
|
|
void CFlexListBox::SetVertSB()
|
|
{
|
|
if (m_bVertSB)
|
|
{
|
|
SetSBValues();
|
|
SIZE client = GetClientSize();
|
|
SetWindowPos(m_VertSB.m_hWnd, NULL, client.cx - m_nSBWidth - 1, 0, m_nSBWidth, client.cy - 1, SWP_NOZORDER);
|
|
}
|
|
|
|
ShowWindow(m_VertSB.m_hWnd, m_bVertSB ? SW_SHOW : SW_HIDE);
|
|
}
|
|
|
|
void CFlexListBox::SetSBValues()
|
|
{
|
|
if (m_hWnd == NULL)
|
|
return;
|
|
|
|
SIZE client = GetClientSize();
|
|
int fit = client.cy / m_nTextHeight;
|
|
m_VertSB.SetValues(0, m_ItemArray.GetSize(), fit, m_nTopIndex);
|
|
}
|
|
|
|
LPCTSTR CFlexListBox::GetSelText()
|
|
{
|
|
if (m_nSelItem < 0 || m_nSelItem >= m_ItemArray.GetSize())
|
|
return NULL;
|
|
|
|
return m_ItemArray[m_nSelItem].GetText();
|
|
}
|
|
|
|
int CFlexListBox::GetSel()
|
|
{
|
|
return m_nSelItem;
|
|
}
|