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