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.
4187 lines
113 KiB
4187 lines
113 KiB
#include "ctlspriv.h"
|
|
#pragma hdrstop
|
|
#include <limits.h>
|
|
#include "usrctl32.h"
|
|
#include "listbox.h"
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// Defines and common macros
|
|
//
|
|
|
|
#define LB_KEYDOWN WM_USER+1
|
|
|
|
#define NOMODIFIER 0 // No modifier is down
|
|
#define SHIFTDOWN 1 // Shift alone
|
|
#define CTLDOWN 2 // Ctl alone
|
|
#define SHCTLDOWN (SHIFTDOWN + CTLDOWN) // Ctrl + Shift
|
|
|
|
//
|
|
// Variables for incremental type search support
|
|
//
|
|
#define MAX_TYPESEARCH 256
|
|
|
|
//
|
|
// LATER IanJa: these vary by country! For US they are VK_OEM_2 VK_OEM_5.
|
|
// Change lboxctl2.c MapVirtualKey to character - and fix the spelling?
|
|
//
|
|
#define VERKEY_SLASH 0xBF // Vertual key for '/' character
|
|
#define VERKEY_BACKSLASH 0xDC // Vertual key for '\' character
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// Forwards
|
|
//
|
|
VOID ListBox_NewITopEx(PLBIV, INT, DWORD);
|
|
VOID ListBox_FillDrawItem(PLBIV, INT, UINT, UINT, LPRECT);
|
|
VOID ListBox_BlockHilite(PLBIV, INT, BOOL);
|
|
VOID ListBox_AlterHilite(PLBIV, INT, INT, BOOL, INT, BOOL);
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_TermDC
|
|
//
|
|
// Cleans up when done with listbox dc.
|
|
//
|
|
__inline void ListBox_TermDC(PLBIV plb)
|
|
{
|
|
if (plb->hFont)
|
|
{
|
|
SelectObject(plb->hdc, GetStockObject(SYSTEM_FONT));
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_InitDC
|
|
//
|
|
// Initializes dc for listbox
|
|
//
|
|
void ListBox_InitDC(PLBIV plb)
|
|
{
|
|
RECT rc;
|
|
|
|
//
|
|
// Set font
|
|
//
|
|
if (plb->hFont)
|
|
{
|
|
SelectObject(plb->hdc, plb->hFont);
|
|
}
|
|
|
|
//
|
|
// Set clipping area
|
|
//
|
|
GetClientRect(plb->hwnd, &rc);
|
|
IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom);
|
|
|
|
OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_GetDC
|
|
//
|
|
// Returns a DC which can be used by a list box even if parentDC is in effect
|
|
//
|
|
BOOL ListBox_GetDC(PLBIV plb)
|
|
{
|
|
if (plb->hdc)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
plb->hdc = GetDC(plb->hwnd);
|
|
|
|
ListBox_InitDC(plb);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_ReleaseDC(PLBIV plb)
|
|
{
|
|
ListBox_TermDC(plb);
|
|
ReleaseDC(plb->hwnd, plb->hdc);
|
|
plb->hdc = NULL;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_InvalidateRect()
|
|
//
|
|
// If the listbox is visible, invalidates a rectangle in the listbox.
|
|
// If the listbox is not visible, sets the defer update flag for the listbox
|
|
//
|
|
BOOL ListBox_InvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase)
|
|
{
|
|
if (IsLBoxVisible(plb))
|
|
{
|
|
InvalidateRect(plb->hwnd, lprc, fErase);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (!plb->fRedraw)
|
|
{
|
|
plb->fDeferUpdate = TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_GetBrush
|
|
//
|
|
// Gets background brush & colors for listbox.
|
|
//
|
|
HBRUSH ListBox_GetBrush(PLBIV plb, HBRUSH *phbrOld)
|
|
{
|
|
HBRUSH hbr;
|
|
HBRUSH hbrOld;
|
|
HWND hwndParent = plb->hwndParent;
|
|
|
|
SetBkMode(plb->hdc, OPAQUE);
|
|
|
|
//
|
|
// Get brush & colors
|
|
//
|
|
|
|
// copied from windows\core\ntuser\kernel\random.c
|
|
if (hwndParent == NULL || hwndParent == GetDesktopWindow())
|
|
{
|
|
hbr = (HBRUSH)SendMessage(plb->hwnd, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd);
|
|
}
|
|
else
|
|
{
|
|
hbr = (HBRUSH)SendMessage(hwndParent, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd);
|
|
}
|
|
|
|
ASSERT(hbr != 0);
|
|
|
|
//
|
|
// Select brush into dc
|
|
//
|
|
if (hbr != NULL)
|
|
{
|
|
hbrOld = SelectObject(plb->hdc, hbr);
|
|
if (phbrOld)
|
|
{
|
|
*phbrOld = hbrOld;
|
|
}
|
|
}
|
|
|
|
return hbr;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_GetItemRectHandler
|
|
//
|
|
// Return the rectangle that the item will be drawn in with respect to the
|
|
// listbox window. Returns TRUE if any portion of the item's rectangle
|
|
// is visible (ie. in the listbox client rect) else returns FALSE.
|
|
//
|
|
BOOL ListBox_GetItemRectHandler(PLBIV plb, INT sItem, LPRECT lprc)
|
|
{
|
|
INT sTmp;
|
|
int clientbottom;
|
|
|
|
//
|
|
// Always allow an item number of 0 so that we can draw the caret which
|
|
// indicates the listbox has the focus even though it is empty.
|
|
//
|
|
// FreeHand 3.1 passes in -1 as the itemNumber and expects
|
|
// a non-null rectangle. So we check for -1 specifically.
|
|
// BUGTAG: Fix for Bug #540 --Win95B-- SANKAR -- 2/20/95 --
|
|
//
|
|
|
|
if (sItem && (sItem != -1) && ((UINT)sItem >= (UINT)plb->cMac))
|
|
{
|
|
SetRectEmpty(lprc);
|
|
TraceMsg(TF_STANDARD, "Invalid index");
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
GetClientRect(plb->hwnd, lprc);
|
|
|
|
if (plb->fMultiColumn)
|
|
{
|
|
//
|
|
// itemHeight * sItem mod number ItemsPerColumn (itemsPerColumn)
|
|
//
|
|
lprc->top = plb->cyChar * (sItem % plb->itemsPerColumn);
|
|
lprc->bottom = lprc->top + plb->cyChar; //+(plb->OwnerDraw ? 0 : 1);
|
|
|
|
ASSERT(plb->itemsPerColumn);
|
|
|
|
if (plb->fRightAlign)
|
|
{
|
|
lprc->right = lprc->right - plb->cxColumn *
|
|
((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
|
|
|
|
lprc->left = lprc->right - plb->cxColumn;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Remember, this is integer division here...
|
|
//
|
|
lprc->left += plb->cxColumn *
|
|
((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
|
|
|
|
lprc->right = lprc->left + plb->cxColumn;
|
|
}
|
|
}
|
|
else if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
//
|
|
// Var height owner draw
|
|
//
|
|
lprc->right += plb->xOrigin;
|
|
clientbottom = lprc->bottom;
|
|
|
|
if (sItem >= plb->iTop)
|
|
{
|
|
for (sTmp = plb->iTop; sTmp < sItem; sTmp++)
|
|
{
|
|
lprc->top = lprc->top + ListBox_GetVarHeightItemHeight(plb, sTmp);
|
|
}
|
|
|
|
//
|
|
// If item number is 0, it may be we are asking for the rect
|
|
// associated with a nonexistant item so that we can draw a caret
|
|
// indicating focus on an empty listbox.
|
|
//
|
|
lprc->bottom = lprc->top + (sItem < plb->cMac ? ListBox_GetVarHeightItemHeight(plb, sItem) : plb->cyChar);
|
|
|
|
return (lprc->top < clientbottom);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Item we want the rect of is before plb->iTop. Thus, negative
|
|
// offsets for the rect and it is never visible.
|
|
//
|
|
for (sTmp = sItem; sTmp < plb->iTop; sTmp++)
|
|
{
|
|
lprc->top = lprc->top - ListBox_GetVarHeightItemHeight(plb, sTmp);
|
|
}
|
|
|
|
lprc->bottom = lprc->top + ListBox_GetVarHeightItemHeight(plb, sItem);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// For fixed height listboxes
|
|
//
|
|
if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
|
|
lprc->right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
|
|
else
|
|
lprc->right += plb->xOrigin;
|
|
lprc->top = (sItem - plb->iTop) * plb->cyChar;
|
|
lprc->bottom = lprc->top + plb->cyChar;
|
|
}
|
|
|
|
return (sItem >= plb->iTop) &&
|
|
(sItem < (plb->iTop + ListBox_CItemInWindow(plb, TRUE)));
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_PrintCallback
|
|
//
|
|
// Called back from DrawState
|
|
//
|
|
BOOL CALLBACK ListBox_PrintCallback(HDC hdc, LPARAM lData, WPARAM wData, int cx, int cy)
|
|
{
|
|
LPWSTR lpstr = (LPWSTR)lData;
|
|
PLBIV plb = (PLBIV)wData;
|
|
int xStart;
|
|
UINT cLen;
|
|
RECT rc;
|
|
UINT oldAlign;
|
|
|
|
if (!lpstr)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
xStart = plb->fMultiColumn ? 0 : 2;
|
|
|
|
if (plb->fRightAlign)
|
|
{
|
|
oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc));
|
|
xStart = cx - xStart;
|
|
}
|
|
|
|
cLen = wcslen(lpstr);
|
|
|
|
if (plb->fUseTabStops)
|
|
{
|
|
TabbedTextOut(hdc, xStart, 0, lpstr, cLen,
|
|
(plb->iTabPixelPositions ? plb->iTabPixelPositions[0] : 0),
|
|
(plb->iTabPixelPositions ? (LPINT)&plb->iTabPixelPositions[1] : NULL),
|
|
plb->fRightAlign ? cx : 0); //, TRUE, GetTextCharset(plb->hdc));
|
|
}
|
|
else
|
|
{
|
|
rc.left = 0;
|
|
rc.top = 0;
|
|
rc.right = cx;
|
|
rc.bottom = cy;
|
|
|
|
if (plb->wMultiple)
|
|
{
|
|
ExtTextOut(hdc, xStart, 0, ETO_OPAQUE, &rc, lpstr, cLen, NULL);
|
|
}
|
|
else if (plb->fMultiColumn)
|
|
{
|
|
ExtTextOut(hdc, xStart, 0, ETO_CLIPPED, &rc, lpstr, cLen, NULL);
|
|
}
|
|
else
|
|
{
|
|
ExtTextOut(hdc, xStart, 0, 0, NULL, lpstr, cLen, NULL);
|
|
|
|
//
|
|
// When the listbox is in the incremental search mode and the item
|
|
// is highlighted (so we only draw in the current item), draw the
|
|
// caret for search indication.
|
|
//
|
|
if ((plb->iTypeSearch != 0) && (plb->OwnerDraw == 0) &&
|
|
(GetBkColor(hdc) == SYSRGB(HIGHLIGHT)))
|
|
{
|
|
SIZE size;
|
|
GetTextExtentPointW(hdc, lpstr, plb->iTypeSearch, &size);
|
|
PatBlt(hdc, xStart + size.cx - 1, 1, 1, cy - 2, DSTINVERT);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (plb->fRightAlign)
|
|
{
|
|
SetTextAlign(hdc, oldAlign);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_DrawItem(PLBIV plb, INT sItem, LPRECT lprect, BOOL fHilite, HBRUSH hbr)
|
|
{
|
|
LPWSTR lpstr;
|
|
DWORD rgbSave;
|
|
DWORD rgbBkSave;
|
|
UINT uFlags;
|
|
HDC hdc = plb->hdc;
|
|
UINT oldAlign;
|
|
HBRUSH hNewBrush;
|
|
|
|
|
|
//
|
|
// If the item is selected, then fill with highlight color
|
|
//
|
|
if (fHilite)
|
|
{
|
|
FillRectClr(hdc, lprect, SYSRGB(HIGHLIGHT));
|
|
|
|
rgbBkSave = SetBkColor(hdc, SYSRGB(HIGHLIGHT));
|
|
rgbSave = SetTextColor(hdc, SYSRGB(HIGHLIGHTTEXT));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If fUseTabStops, we must fill the background, because later we use
|
|
// LBTabTheTextOutForWimps(), which fills the background only partially
|
|
// Fix for Bug #1509 -- 01/25/91 -- SANKAR --
|
|
//
|
|
if ((hbr != NULL) && ((sItem == plb->iSelBase) || (plb->fUseTabStops)))
|
|
{
|
|
FillRect(hdc, lprect, hbr);
|
|
}
|
|
}
|
|
|
|
uFlags = DST_COMPLEX;
|
|
lpstr = GetLpszItem(plb, sItem);
|
|
|
|
if (TESTFLAG(GET_STYLE(plb), WS_DISABLED))
|
|
{
|
|
if ((COLORREF)SYSRGB(GRAYTEXT) != GetBkColor(hdc))
|
|
{
|
|
SetTextColor(hdc, SYSRGB(GRAYTEXT));
|
|
}
|
|
else
|
|
{
|
|
uFlags |= DSS_UNION;
|
|
}
|
|
}
|
|
|
|
if (plb->fRightAlign)
|
|
{
|
|
uFlags |= DSS_RIGHT;
|
|
}
|
|
|
|
if (plb->fRtoLReading)
|
|
{
|
|
oldAlign = SetTextAlign(hdc, TA_RTLREADING | GetTextAlign(hdc));
|
|
}
|
|
|
|
hNewBrush = CreateSolidBrush(SYSRGB(WINDOWTEXT));
|
|
|
|
DrawState(hdc, hNewBrush,
|
|
ListBox_PrintCallback,
|
|
(LPARAM)lpstr,
|
|
(WPARAM)plb,
|
|
lprect->left,
|
|
lprect->top,
|
|
lprect->right-lprect->left,
|
|
lprect->bottom-lprect->top,
|
|
uFlags);
|
|
|
|
if (hNewBrush)
|
|
{
|
|
DeleteObject(hNewBrush);
|
|
}
|
|
|
|
if (plb->fRtoLReading)
|
|
{
|
|
SetTextAlign(hdc, oldAlign);
|
|
}
|
|
|
|
if (fHilite)
|
|
{
|
|
SetTextColor(hdc, rgbSave);
|
|
SetBkColor(hdc, rgbBkSave);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_SetCaret(PLBIV plb, BOOL fSetCaret)
|
|
{
|
|
RECT rc;
|
|
BOOL fNewDC;
|
|
|
|
if (plb->fCaret && ((BOOL) plb->fCaretOn != !!fSetCaret))
|
|
{
|
|
if (IsLBoxVisible(plb))
|
|
{
|
|
//
|
|
// Turn the caret (located at plb->iSelBase) on
|
|
//
|
|
fNewDC = ListBox_GetDC(plb);
|
|
|
|
ListBox_GetItemRectHandler(plb, plb->iSelBase, &rc);
|
|
|
|
if (fNewDC)
|
|
{
|
|
SetBkColor(plb->hdc, SYSRGB(WINDOW));
|
|
SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
|
|
}
|
|
|
|
if (plb->OwnerDraw)
|
|
{
|
|
//
|
|
// Fill in the drawitem struct
|
|
//
|
|
UINT itemState = (fSetCaret) ? ODS_FOCUS : 0;
|
|
|
|
if (ListBox_IsSelected(plb, plb->iSelBase, HILITEONLY))
|
|
{
|
|
itemState |= ODS_SELECTED;
|
|
}
|
|
|
|
ListBox_FillDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc);
|
|
}
|
|
else if (!TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN))
|
|
{
|
|
COLORREF crBk = SetBkColor(plb->hdc, SYSRGB(WINDOW));
|
|
COLORREF crText = SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
|
|
|
|
DrawFocusRect(plb->hdc, &rc);
|
|
|
|
SetBkColor(plb->hdc, crBk);
|
|
SetTextColor(plb->hdc, crText);
|
|
}
|
|
|
|
if (fNewDC)
|
|
{
|
|
ListBox_ReleaseDC(plb);
|
|
}
|
|
}
|
|
|
|
plb->fCaretOn = !!fSetCaret;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
BOOL ListBox_IsSelected(PLBIV plb, INT sItem, UINT wOpFlags)
|
|
{
|
|
LPBYTE lp;
|
|
|
|
if ((sItem >= plb->cMac) || (sItem < 0))
|
|
{
|
|
TraceMsg(TF_STANDARD, "Invalid index");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
{
|
|
return (sItem == plb->iSel);
|
|
}
|
|
|
|
lp = plb->rgpch + sItem +
|
|
(plb->cMac * (plb->fHasStrings
|
|
? sizeof(LBItem)
|
|
: (plb->fHasData
|
|
? sizeof(LBODItem)
|
|
: 0)));
|
|
sItem = *lp;
|
|
|
|
if (wOpFlags == HILITEONLY)
|
|
{
|
|
sItem >>= 4;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// SELONLY
|
|
//
|
|
sItem &= 0x0F;
|
|
}
|
|
|
|
return sItem;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_CItemInWindow
|
|
//
|
|
// Returns the number of items which can fit in a list box. It
|
|
// includes the partially visible one at the bottom if fPartial is TRUE. For
|
|
// var height ownerdraw, return the number of items visible starting at iTop
|
|
// and going to the bottom of the client rect.
|
|
//
|
|
INT ListBox_CItemInWindow(PLBIV plb, BOOL fPartial)
|
|
{
|
|
RECT rect;
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
return ListBox_VisibleItemsVarOwnerDraw(plb, fPartial);
|
|
}
|
|
|
|
if (plb->fMultiColumn)
|
|
{
|
|
return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0));
|
|
}
|
|
|
|
GetClientRect(plb->hwnd, &rect);
|
|
|
|
//
|
|
// fPartial must be considered only if the listbox height is not an
|
|
// integral multiple of character height.
|
|
// A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR --
|
|
//
|
|
ASSERT(plb->cyChar);
|
|
|
|
if (!plb->cyChar)
|
|
{
|
|
plb->cyChar = SYSFONT_CYCHAR;
|
|
}
|
|
|
|
return (INT)((rect.bottom / plb->cyChar) +
|
|
((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0));
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_VScroll
|
|
//
|
|
// Handles vertical scrolling of the listbox
|
|
//
|
|
void ListBox_VScroll(PLBIV plb, INT cmd, int yAmt)
|
|
{
|
|
INT iTopNew;
|
|
INT cItemPageScroll;
|
|
DWORD dwTime = 0;
|
|
|
|
if (plb->fMultiColumn)
|
|
{
|
|
//
|
|
// Don't allow vertical scrolling on a multicolumn list box. Needed
|
|
// in case app sends WM_VSCROLL messages to the listbox.
|
|
//
|
|
return;
|
|
}
|
|
|
|
cItemPageScroll = plb->cItemFullMax;
|
|
|
|
if (cItemPageScroll > 1)
|
|
{
|
|
cItemPageScroll--;
|
|
}
|
|
|
|
if (plb->cMac)
|
|
{
|
|
iTopNew = plb->iTop;
|
|
|
|
switch (cmd)
|
|
{
|
|
case SB_LINEUP:
|
|
dwTime = yAmt;
|
|
iTopNew--;
|
|
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
dwTime = yAmt;
|
|
iTopNew++;
|
|
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
iTopNew = ListBox_Page(plb, plb->iTop, FALSE);
|
|
}
|
|
else
|
|
{
|
|
iTopNew -= cItemPageScroll;
|
|
}
|
|
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
iTopNew = ListBox_Page(plb, plb->iTop, TRUE);
|
|
}
|
|
else
|
|
{
|
|
iTopNew += cItemPageScroll;
|
|
}
|
|
|
|
break;
|
|
|
|
case SB_THUMBTRACK:
|
|
case SB_THUMBPOSITION:
|
|
//
|
|
// If the listbox contains more than 0xFFFF items
|
|
// it means that the scrolbar can return a position
|
|
// that cannot fit in a WORD (16 bits), so use
|
|
// GetScrollInfo (which is slower) in this case.
|
|
//
|
|
if (plb->cMac < 0xFFFF)
|
|
{
|
|
iTopNew = yAmt;
|
|
}
|
|
else
|
|
{
|
|
SCROLLINFO si;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_TRACKPOS;
|
|
|
|
GetScrollInfo( plb->hwnd, SB_VERT, &si);
|
|
|
|
iTopNew = si.nTrackPos;
|
|
}
|
|
|
|
break;
|
|
|
|
case SB_TOP:
|
|
iTopNew = 0;
|
|
|
|
break;
|
|
|
|
case SB_BOTTOM:
|
|
iTopNew = plb->cMac - 1;
|
|
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
plb->fSmoothScroll = TRUE;
|
|
ListBox_SetCaret(plb, FALSE);
|
|
ListBox_ShowHideScrollBars(plb);
|
|
ListBox_SetCaret(plb, TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
ListBox_NewITopEx(plb, iTopNew, dwTime);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
DWORD ListBox_GetScrollFlags(PLBIV plb, DWORD dwTime)
|
|
{
|
|
DWORD dwFlags;
|
|
BOOL bUIEffects, bLBSmoothScroll;
|
|
|
|
SystemParametersInfo(SPI_GETUIEFFECTS, 0, &bUIEffects, 0);
|
|
SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &bLBSmoothScroll, 0);
|
|
|
|
if (dwTime != 0)
|
|
{
|
|
dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime);
|
|
}
|
|
else if (bUIEffects && bLBSmoothScroll && plb->fSmoothScroll)
|
|
{
|
|
dwFlags = SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN;
|
|
plb->fSmoothScroll = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// NoSmoothScrolling:
|
|
//
|
|
dwFlags = SW_SCROLLWINDOW | SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN;
|
|
}
|
|
|
|
return dwFlags;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_HScroll
|
|
//
|
|
// Supports horizontal scrolling of listboxes
|
|
//
|
|
void ListBox_HScroll(PLBIV plb, INT cmd, int xAmt)
|
|
{
|
|
int newOrigin = plb->xOrigin;
|
|
int oldOrigin = plb->xOrigin;
|
|
int windowWidth;
|
|
RECT rc;
|
|
DWORD dwTime = 0;
|
|
|
|
//
|
|
// Update the window so that we don't run into problems with invalid
|
|
// regions during the horizontal scroll.
|
|
//
|
|
if (plb->fMultiColumn)
|
|
{
|
|
//
|
|
// Handle multicolumn scrolling in a separate segment
|
|
//
|
|
ListBox_HSrollMultiColumn(plb, cmd, xAmt);
|
|
|
|
return;
|
|
}
|
|
|
|
GetClientRect(plb->hwnd, &rc);
|
|
windowWidth = rc.right;
|
|
|
|
if (plb->cMac)
|
|
{
|
|
|
|
switch (cmd)
|
|
{
|
|
case SB_LINEUP:
|
|
dwTime = xAmt;
|
|
newOrigin -= plb->cxChar;
|
|
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
dwTime = xAmt;
|
|
newOrigin += plb->cxChar;
|
|
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
newOrigin -= (windowWidth / 3) * 2;
|
|
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
newOrigin += (windowWidth / 3) * 2;
|
|
|
|
break;
|
|
|
|
case SB_THUMBTRACK:
|
|
case SB_THUMBPOSITION:
|
|
newOrigin = xAmt;
|
|
|
|
break;
|
|
|
|
case SB_TOP:
|
|
newOrigin = 0;
|
|
|
|
break;
|
|
|
|
case SB_BOTTOM:
|
|
newOrigin = plb->maxWidth;
|
|
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
plb->fSmoothScroll = TRUE;
|
|
ListBox_SetCaret(plb, FALSE);
|
|
ListBox_ShowHideScrollBars(plb);
|
|
ListBox_SetCaret(plb, TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
ListBox_SetCaret(plb, FALSE);
|
|
|
|
plb->xOrigin = newOrigin;
|
|
plb->xOrigin = ListBox_SetScrollParms(plb, SB_HORZ);
|
|
|
|
if ((cmd == SB_BOTTOM) && plb->fRightAlign)
|
|
{
|
|
//
|
|
// so we know where to draw from.
|
|
//
|
|
plb->xRightOrigin = plb->xOrigin;
|
|
}
|
|
|
|
if(oldOrigin != plb->xOrigin)
|
|
{
|
|
DWORD dwFlags;
|
|
|
|
dwFlags = ListBox_GetScrollFlags(plb, dwTime);
|
|
ScrollWindowEx(plb->hwnd, oldOrigin-plb->xOrigin,
|
|
0, NULL, &rc, NULL, NULL, dwFlags);
|
|
UpdateWindow(plb->hwnd);
|
|
}
|
|
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// this is a less-than-ideal fix for ImageMind ScreenSaver (Win95
|
|
// B#8252) but it works and it doesn't hurt anybody -- JEFFBOG 10/28/94
|
|
//
|
|
ListBox_SetScrollParms(plb, SB_HORZ);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_Paint(PLBIV plb, HDC hdc, LPRECT lprcBounds)
|
|
{
|
|
INT i;
|
|
RECT rect;
|
|
RECT scratchRect;
|
|
BOOL fHilite;
|
|
INT iLastItem;
|
|
HBRUSH hbrSave = NULL;
|
|
HBRUSH hbrControl;
|
|
BOOL fCaretOn;
|
|
RECT rcBounds;
|
|
HDC hdcSave;
|
|
|
|
if (lprcBounds == NULL)
|
|
{
|
|
lprcBounds = &rcBounds;
|
|
GetClientRect(plb->hwnd, lprcBounds);
|
|
}
|
|
|
|
hdcSave = plb->hdc;
|
|
plb->hdc = hdc;
|
|
|
|
//
|
|
// Initialize dc.
|
|
//
|
|
ListBox_InitDC(plb);
|
|
|
|
//
|
|
// Turn caret off
|
|
//
|
|
fCaretOn = plb->fCaretOn;
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
hbrSave = NULL;
|
|
hbrControl = ListBox_GetBrush(plb, &hbrSave);
|
|
|
|
//
|
|
// Get listbox's client
|
|
//
|
|
GetClientRect(plb->hwnd, &rect);
|
|
|
|
//
|
|
// Adjust width of client rect for scrolled amount
|
|
// fix for #140, t-arthb
|
|
//
|
|
if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
|
|
{
|
|
rect.right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
|
|
}
|
|
else
|
|
{
|
|
rect.right += plb->xOrigin;
|
|
}
|
|
|
|
//
|
|
// Get the index of the last item visible on the screen. This is also
|
|
// valid for var height ownerdraw.
|
|
//
|
|
iLastItem = plb->iTop + ListBox_CItemInWindow(plb,TRUE);
|
|
iLastItem = min(iLastItem, plb->cMac - 1);
|
|
|
|
//
|
|
// Fill in the background of the listbox if it's an empty listbox
|
|
// or if we're doing a control print
|
|
//
|
|
if (iLastItem == -1)
|
|
{
|
|
FillRect(plb->hdc, &rect, hbrControl);
|
|
}
|
|
|
|
|
|
//
|
|
// Allow AnimateWindow() catch the apps that do not use our DC when
|
|
// drawing the list box
|
|
//
|
|
SetBoundsRect(plb->hdc, NULL, DCB_RESET | DCB_ENABLE);
|
|
|
|
for (i = plb->iTop; i <= iLastItem; i++)
|
|
{
|
|
//
|
|
// Note that rect contains the clientrect from when we did the
|
|
// GetClientRect so the width is correct. We just need to adjust
|
|
// the top and bottom of the rectangle to the item of interest.
|
|
//
|
|
rect.bottom = rect.top + plb->cyChar;
|
|
|
|
if ((UINT)i < (UINT)plb->cMac)
|
|
{
|
|
//
|
|
// If var height, get the rectangle for the item.
|
|
//
|
|
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn)
|
|
{
|
|
ListBox_GetItemRectHandler(plb, i, &rect);
|
|
}
|
|
|
|
if (IntersectRect(&scratchRect, lprcBounds, &rect))
|
|
{
|
|
fHilite = !plb->fNoSel && ListBox_IsSelected(plb, i, HILITEONLY);
|
|
|
|
if (plb->OwnerDraw)
|
|
{
|
|
//
|
|
// Fill in the drawitem struct
|
|
//
|
|
ListBox_FillDrawItem(plb, i, ODA_DRAWENTIRE,
|
|
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
|
|
}
|
|
else
|
|
{
|
|
ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl);
|
|
}
|
|
}
|
|
}
|
|
rect.top = rect.bottom;
|
|
}
|
|
|
|
if (hbrSave != NULL)
|
|
{
|
|
SelectObject(hdc, hbrSave);
|
|
}
|
|
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
|
|
ListBox_TermDC(plb);
|
|
|
|
plb->hdc = hdcSave;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_ISelFromPt
|
|
//
|
|
// In the loword, returns the closest item number the pt is on. The high
|
|
// word is 0 if the point is within bounds of the listbox client rect and is
|
|
// 1 if it is outside the bounds. This will allow us to make the invertrect
|
|
// disappear if the mouse is outside the listbox yet we can still show the
|
|
// outline around the item that would be selected if the mouse is brought back
|
|
// in bounds...
|
|
BOOL ListBox_ISelFromPt(PLBIV plb, POINT pt, LPDWORD piItem)
|
|
{
|
|
RECT rect;
|
|
int y;
|
|
UINT mouseHighWord = 0;
|
|
INT sItem;
|
|
INT sTmp;
|
|
|
|
GetClientRect(plb->hwnd, &rect);
|
|
|
|
if (pt.y < 0)
|
|
{
|
|
//
|
|
// Mouse is out of bounds above listbox
|
|
//
|
|
*piItem = plb->iTop;
|
|
|
|
return TRUE;
|
|
}
|
|
else if ((y = pt.y) > rect.bottom)
|
|
{
|
|
y = rect.bottom;
|
|
mouseHighWord = 1;
|
|
}
|
|
|
|
if (pt.x < 0 || pt.x > rect.right)
|
|
{
|
|
mouseHighWord = 1;
|
|
}
|
|
|
|
//
|
|
// Now just need to check if y mouse coordinate intersects item's rectangle
|
|
//
|
|
if (plb->OwnerDraw != OWNERDRAWVAR)
|
|
{
|
|
if (plb->fMultiColumn)
|
|
{
|
|
if (y < plb->itemsPerColumn * plb->cyChar)
|
|
{
|
|
if (plb->fRightAlign)
|
|
{
|
|
sItem = plb->iTop + (INT)((y / plb->cyChar) +
|
|
((rect.right - pt.x) / plb->cxColumn) * plb->itemsPerColumn);
|
|
}
|
|
else
|
|
{
|
|
sItem = plb->iTop + (INT)((y / plb->cyChar) +
|
|
(pt.x / plb->cxColumn) * plb->itemsPerColumn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// User clicked in blank space at the bottom of a column.
|
|
// Just select the last item in the column.
|
|
//
|
|
mouseHighWord = 1;
|
|
sItem = plb->iTop + (plb->itemsPerColumn - 1) +
|
|
(INT)((pt.x / plb->cxColumn) * plb->itemsPerColumn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sItem = plb->iTop + (INT)(y / plb->cyChar);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// VarHeightOwnerdraw so we gotta do this the hardway... Set the x
|
|
// coordinate of the mouse down point to be inside the listbox client
|
|
// rectangle since we no longer care about it. This lets us use the
|
|
// point in rect calls.
|
|
//
|
|
pt.x = 8;
|
|
pt.y = y;
|
|
|
|
for (sTmp = plb->iTop; sTmp < plb->cMac; sTmp++)
|
|
{
|
|
ListBox_GetItemRectHandler(plb, sTmp, &rect);
|
|
|
|
if (PtInRect(&rect, pt))
|
|
{
|
|
*piItem = sTmp;
|
|
|
|
return mouseHighWord;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Point was at the empty area at the bottom of a not full listbox
|
|
//
|
|
*piItem = plb->cMac - 1;
|
|
|
|
return mouseHighWord;
|
|
}
|
|
|
|
//
|
|
// Check if user clicked on the blank area at the bottom of a not full list.
|
|
// Assumes > 0 items in the listbox.
|
|
//
|
|
if (sItem > plb->cMac - 1)
|
|
{
|
|
mouseHighWord = 1;
|
|
sItem = plb->cMac - 1;
|
|
}
|
|
|
|
*piItem = sItem;
|
|
|
|
return mouseHighWord;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_SetSelected
|
|
//
|
|
// This is used for button initiated changes of selection state.
|
|
//
|
|
// fSelected : TRUE if the item is to be set as selected, FALSE otherwise
|
|
//
|
|
// wOpFlags : HILITEONLY = Modify only the Display state (hi-nibble)
|
|
// SELONLY = Modify only the Selection state (lo-nibble)
|
|
// HILITEANDSEL = Modify both of them;
|
|
//
|
|
void ListBox_SetSelected(PLBIV plb, INT iSel, BOOL fSelected, UINT wOpFlags)
|
|
{
|
|
LPSTR lp;
|
|
BYTE cMask;
|
|
BYTE cSelStatus;
|
|
|
|
if (iSel < 0 || iSel >= plb->cMac)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
{
|
|
if (fSelected)
|
|
{
|
|
plb->iSel = iSel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cSelStatus = (BYTE)fSelected;
|
|
|
|
switch (wOpFlags)
|
|
{
|
|
case HILITEONLY:
|
|
//
|
|
// Mask out lo-nibble
|
|
//
|
|
cSelStatus = (BYTE)(cSelStatus << 4);
|
|
cMask = 0x0F;
|
|
|
|
break;
|
|
|
|
case SELONLY:
|
|
//
|
|
// Mask out hi-nibble
|
|
//
|
|
cMask = 0xF0;
|
|
|
|
break;
|
|
|
|
case HILITEANDSEL:
|
|
//
|
|
// Mask the byte fully
|
|
//
|
|
cSelStatus |= (cSelStatus << 4);
|
|
cMask = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
lp = (LPSTR)(plb->rgpch) + iSel +
|
|
(plb->cMac * (plb->fHasStrings
|
|
? sizeof(LBItem)
|
|
: (plb->fHasData ? sizeof(LBODItem) : 0)));
|
|
|
|
*lp = (*lp & cMask) | cSelStatus;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_LastFullVisible
|
|
//
|
|
// Returns the last fully visible item in the listbox. This is valid
|
|
// for ownerdraw var height and fixed height listboxes.
|
|
//
|
|
INT ListBox_LastFullVisible(PLBIV plb)
|
|
{
|
|
INT iLastItem;
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn)
|
|
{
|
|
iLastItem = plb->iTop + ListBox_CItemInWindow(plb, FALSE) - 1;
|
|
iLastItem = max(iLastItem, plb->iTop);
|
|
}
|
|
else
|
|
{
|
|
iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1);
|
|
}
|
|
|
|
return iLastItem;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_InvertItem( PLBIV plb, INT i, BOOL fHilite)
|
|
{
|
|
RECT rect;
|
|
BOOL fCaretOn;
|
|
HBRUSH hbrControl;
|
|
BOOL fNewDC;
|
|
|
|
//
|
|
// Skip if item isn't showing.
|
|
//
|
|
if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + ListBox_CItemInWindow(plb, TRUE))))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsLBoxVisible(plb))
|
|
{
|
|
ListBox_GetItemRectHandler(plb, i, &rect);
|
|
|
|
//
|
|
// Only turn off the caret if it is on. This avoids annoying caret
|
|
// flicker when nesting CaretOns and CaretOffs.
|
|
//
|
|
fCaretOn = plb->fCaretOn;
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
fNewDC = ListBox_GetDC(plb);
|
|
|
|
hbrControl = ListBox_GetBrush(plb, NULL);
|
|
|
|
if (!plb->OwnerDraw)
|
|
{
|
|
if (!fHilite)
|
|
{
|
|
FillRect(plb->hdc, &rect, hbrControl);
|
|
hbrControl = NULL;
|
|
}
|
|
|
|
ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We are ownerdraw so fill in the drawitem struct and send off
|
|
// to the owner.
|
|
//
|
|
ListBox_FillDrawItem(plb, i, ODA_SELECT,
|
|
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
|
|
}
|
|
|
|
if (fNewDC)
|
|
{
|
|
ListBox_ReleaseDC(plb);
|
|
}
|
|
|
|
//
|
|
// Turn the caret back on only if it was originally on.
|
|
//
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_ResetWorld
|
|
//
|
|
// Resets everyone's selection and hilite state except items in the
|
|
// range sStItem to sEndItem (Both inclusive).
|
|
void ListBox_ResetWorld(PLBIV plb, INT iStart, INT iEnd, BOOL fSelect)
|
|
{
|
|
INT i;
|
|
INT iLastInWindow;
|
|
BOOL fCaretOn;
|
|
|
|
//
|
|
// If iStart and iEnd are not in correct order we swap them
|
|
//
|
|
if (iStart > iEnd)
|
|
{
|
|
i = iStart;
|
|
iStart = iEnd;
|
|
iEnd = i;
|
|
}
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
{
|
|
if (plb->iSel != -1 && ((plb->iSel < iStart) || (plb->iSel > iEnd)))
|
|
{
|
|
ListBox_InvertItem(plb, plb->iSel, fSelect);
|
|
plb->iSel = -1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
iLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE);
|
|
|
|
fCaretOn = plb->fCaretOn;
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
for (i = 0; i < plb->cMac; i++)
|
|
{
|
|
if (i == iStart)
|
|
{
|
|
//
|
|
// skip range to be preserved
|
|
//
|
|
i = iEnd;
|
|
}
|
|
else
|
|
{
|
|
if ((plb->iTop <= i) && (i <= iLastInWindow) &&
|
|
(fSelect != ListBox_IsSelected(plb, i, HILITEONLY)))
|
|
{
|
|
//
|
|
// Only invert the item if it is visible and present Selection
|
|
// state is different from what is required.
|
|
//
|
|
ListBox_InvertItem(plb, i, fSelect);
|
|
}
|
|
|
|
//
|
|
// Set all items outside of preserved range to unselected
|
|
//
|
|
ListBox_SetSelected(plb, i, fSelect, HILITEANDSEL);
|
|
}
|
|
}
|
|
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_NotifyOwner(PLBIV plb, INT sEvt)
|
|
{
|
|
HWND hwndParent = plb->hwndParent;
|
|
if (hwndParent)
|
|
{
|
|
SendMessage(hwndParent, WM_COMMAND, MAKELONG(GetWindowID(plb->hwnd), sEvt), (LPARAM)(plb->hwnd));
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_SetISelBase(PLBIV plb, INT sItem)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
plb->iSelBase = sItem;
|
|
ListBox_SetCaret(plb, TRUE);
|
|
|
|
ListBox_InsureVisible(plb, plb->iSelBase, FALSE);
|
|
|
|
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
|
|
{
|
|
ListBox_Event(plb, EVENT_OBJECT_FOCUS, sItem);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_TrackMouse(PLBIV plb, UINT wMsg, POINT pt)
|
|
{
|
|
INT iSelFromPt;
|
|
INT iSelTemp;
|
|
BOOL mousetemp;
|
|
BOOL fMouseInRect;
|
|
RECT rcClient;
|
|
UINT wModifiers = 0;
|
|
BOOL fSelected;
|
|
UINT uEvent = 0;
|
|
INT trackPtRetn;
|
|
HWND hwnd = plb->hwnd;
|
|
RECT rcWindow;
|
|
|
|
//
|
|
// Optimization: do nothing if mouse not captured
|
|
//
|
|
if ((wMsg != WM_LBUTTONDOWN) && (wMsg != WM_LBUTTONDBLCLK))
|
|
{
|
|
if (!plb->fCaptured)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If we are processing a WM_MOUSEMOVE but the mouse has not moved from
|
|
// the previous point, then we may be dealing with a mouse "jiggle" sent
|
|
// from the kernel (see zzzInvalidateDCCache). If we process this, we will
|
|
// snap the listbox selection back to where the mouse cursor is pointing,
|
|
// even if the user has not touched the mouse. FritzS: NT5 bug 220722.
|
|
// Some apps (like MSMoney98) rely on this, so added the bLastRITWasKeyboard
|
|
// check. MCostea #244450
|
|
//
|
|
if ((wMsg == WM_MOUSEMOVE) && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT)) )
|
|
{
|
|
TraceMsg(TF_STANDARD, "ListBox_TrackMouse ignoring WM_MOUSEMOVE with no mouse movement");
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
mousetemp = ListBox_ISelFromPt(plb, pt, &iSelFromPt);
|
|
|
|
//
|
|
// If we allow the user to cancel his selection then fMouseInRect is true if
|
|
// the mouse is in the listbox client area otherwise it is false. If we
|
|
// don't allow the user to cancel his selection, then fMouseInRect will
|
|
// always be true. This allows us to implement cancelable selection
|
|
// listboxes ie. The selection reverts to the origional one if the user
|
|
// releases the mouse outside of the listbox.
|
|
//
|
|
fMouseInRect = !mousetemp || !plb->pcbox;
|
|
|
|
GetClientRect(plb->hwnd, &rcClient);
|
|
|
|
switch (wMsg)
|
|
{
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
//
|
|
// We want to divert mouse clicks. If the user clicks outside
|
|
// of a dropped down listbox, we want to popup it up, using
|
|
// the current selection.
|
|
//
|
|
if (plb->fCaptured)
|
|
{
|
|
//
|
|
// If plb->pcbox is NULL, this is a listbox that
|
|
// received a WM_LBUTTONDOWN again w/o receiving
|
|
// a WM_LBUTTONUP for the previous WM_LBUTTONDOWN bug
|
|
//
|
|
if (plb->pcbox && mousetemp)
|
|
{
|
|
// Translate pt and rcClient to screen rel coords
|
|
ClientToScreen(plb->hwnd, &pt);
|
|
ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.left );
|
|
ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.right );
|
|
|
|
GetWindowRect(plb->hwnd, &rcWindow);
|
|
|
|
if (!PtInRect(&rcWindow, pt))
|
|
{
|
|
//
|
|
// Cancel selection if clicked outside of combo;
|
|
// Accept if clicked on combo button or item.
|
|
//
|
|
ComboBox_HideListBoxWindow(plb->pcbox, TRUE, FALSE);
|
|
}
|
|
else if (!PtInRect(&rcClient, pt))
|
|
{
|
|
//
|
|
// Let it pass through. Save, restore capture in
|
|
// case user is clicking on scrollbar.
|
|
//
|
|
|
|
plb->fCaptured = FALSE;
|
|
|
|
ReleaseCapture();
|
|
|
|
SendMessageW(plb->hwnd, WM_NCLBUTTONDOWN,
|
|
(WPARAM)SendMessageW(plb->hwnd, WM_NCHITTEST, 0, POINTTOPOINTS(pt)), POINTTOPOINTS(pt));
|
|
|
|
SetCapture(hwnd);
|
|
|
|
plb->fCaptured = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
plb->fCaptured = FALSE;
|
|
ReleaseCapture();
|
|
}
|
|
|
|
if (plb->pcbox)
|
|
{
|
|
//
|
|
// If this listbox is in a combo box, set the focus to the combo
|
|
// box window so that the edit control/static text is also
|
|
// activated
|
|
//
|
|
SetFocus(plb->pcbox->hwndEdit);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the focus if the listbox is clicked in and we don't
|
|
// already have the focus. If we don't have the focus after
|
|
// this, run away...
|
|
//
|
|
SetFocus(hwnd);
|
|
|
|
if (!plb->fCaret)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (plb->fAddSelMode)
|
|
{
|
|
//
|
|
// If it is in "Add" mode, quit it using shift f8 key...
|
|
// However, since we can't send shift key state, we have to turn
|
|
// this off directly...
|
|
//
|
|
|
|
//
|
|
// Switch off the Caret blinking
|
|
//
|
|
KillTimer(hwnd, IDSYS_CARET);
|
|
|
|
//
|
|
// Make sure the caret does not vanish
|
|
//
|
|
ListBox_SetCaret(plb, TRUE);
|
|
plb->fAddSelMode = FALSE;
|
|
}
|
|
|
|
if (!plb->cMac)
|
|
{
|
|
//
|
|
// Don't even bother handling the mouse if no items in the
|
|
// listbox since the code below assumes >0 items in the
|
|
// listbox. We will just get the focus (the statement above) if
|
|
// we don't already have it.
|
|
//
|
|
break;
|
|
}
|
|
|
|
if (mousetemp && plb->fCaptured)
|
|
{
|
|
//
|
|
// Mouse down occurred in a empty spot. And we're tracking the list.
|
|
// Just ignore it.
|
|
//
|
|
break;
|
|
}
|
|
|
|
|
|
plb->fDoubleClick = (wMsg == WM_LBUTTONDBLCLK);
|
|
|
|
if (!plb->fDoubleClick)
|
|
{
|
|
//
|
|
// This hack put in for the shell. Tell the shell where in the
|
|
// listbox the user clicked and at what item number. The shell
|
|
// can return 0 to continue normal mouse tracking or TRUE to
|
|
// abort mouse tracking.
|
|
//
|
|
trackPtRetn = (INT)SendMessage(plb->hwndParent, WM_LBTRACKPOINT,
|
|
(DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y));
|
|
if (trackPtRetn)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (plb->pcbox)
|
|
{
|
|
//
|
|
// Save the last selection if this is a combo box. So that it
|
|
// can be restored if user decides to cancel the selection by up
|
|
// clicking outside the listbox.
|
|
//
|
|
plb->iLastSelection = plb->iSel;
|
|
}
|
|
|
|
//
|
|
// Save for timer
|
|
//
|
|
plb->ptPrev = pt;
|
|
|
|
plb->fMouseDown = TRUE;
|
|
SetCapture(hwnd);
|
|
plb->fCaptured = TRUE;
|
|
|
|
if (plb->fDoubleClick)
|
|
{
|
|
//
|
|
// Double click. Fake a button up and exit
|
|
//
|
|
ListBox_TrackMouse(plb, WM_LBUTTONUP, pt);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Set the system timer so that we can autoscroll if the mouse is
|
|
// outside the bounds of the listbox rectangle
|
|
//
|
|
SetTimer(hwnd, IDSYS_SCROLL, SCROLL_TIMEOUT(), NULL);
|
|
|
|
//
|
|
// If extended multiselection listbox, are any modifier key pressed?
|
|
//
|
|
if (plb->wMultiple == EXTENDEDSEL)
|
|
{
|
|
if (GetKeyState(VK_SHIFT) < 0)
|
|
{
|
|
wModifiers = SHIFTDOWN;
|
|
}
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
wModifiers += CTLDOWN;
|
|
}
|
|
|
|
//
|
|
// Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
|
|
//
|
|
}
|
|
|
|
|
|
switch (wModifiers)
|
|
{
|
|
case NOMODIFIER:
|
|
MouseMoveHandler:
|
|
if (plb->iSelBase != iSelFromPt)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
//
|
|
// We only look at the mouse if the point it is pointing to is
|
|
// not selected. Since we are not in ExtendedSelMode, anywhere
|
|
// the mouse points, we have to set the selection to that item.
|
|
// Hence, if the item isn't selected, it means the mouse never
|
|
// pointed to it before so we can select it. We ignore already
|
|
// selected items so that we avoid flashing the inverted
|
|
// selection rectangle. Also, we could get WM_SYSTIMER simulated
|
|
// mouse moves which would cause flashing otherwise...
|
|
//
|
|
|
|
if ( mousetemp || (plb->pcbox && plb->pcbox->fButtonPressed))
|
|
{
|
|
// We're outside the list but haven't begun tracking the list yet.
|
|
// Select the item that is already selected.
|
|
iSelTemp = plb->iSel;
|
|
}
|
|
else
|
|
{
|
|
iSelTemp = (fMouseInRect ? iSelFromPt : -1);
|
|
}
|
|
|
|
//
|
|
// If the LB is either SingleSel or Extended multisel, clear all
|
|
// old selections except the new one being made.
|
|
//
|
|
if (plb->wMultiple != MULTIPLESEL)
|
|
{
|
|
ListBox_ResetWorld(plb, iSelTemp, iSelTemp, FALSE);
|
|
|
|
//
|
|
// This will be TRUE if iSelTemp isn't -1 (like below)
|
|
// and also if it is but there is a current selection.
|
|
//
|
|
if ((iSelTemp == -1) && (plb->iSel != -1))
|
|
{
|
|
uEvent = EVENT_OBJECT_SELECTIONREMOVE;
|
|
}
|
|
}
|
|
|
|
fSelected = ListBox_IsSelected(plb, iSelTemp, HILITEONLY);
|
|
if (iSelTemp != -1)
|
|
{
|
|
//
|
|
// If it is MULTIPLESEL, then toggle; For others, only if
|
|
// not selected already, select it.
|
|
//
|
|
if (((plb->wMultiple == MULTIPLESEL) && (wMsg != WM_LBUTTONDBLCLK)) || !fSelected)
|
|
{
|
|
ListBox_SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL);
|
|
|
|
//
|
|
// And invert it
|
|
//
|
|
ListBox_InvertItem(plb, iSelTemp, !fSelected);
|
|
fSelected = !fSelected; // Set the new state
|
|
if (plb->wMultiple == MULTIPLESEL)
|
|
{
|
|
uEvent = (fSelected ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
}
|
|
else
|
|
{
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have to set iSel in case this is a multisel lb.
|
|
//
|
|
plb->iSel = iSelTemp;
|
|
|
|
//
|
|
// Set the new anchor point
|
|
//
|
|
plb->iMouseDown = iSelFromPt;
|
|
plb->iLastMouseMove = iSelFromPt;
|
|
plb->fNewItemState = fSelected;
|
|
|
|
break;
|
|
|
|
case SHIFTDOWN:
|
|
|
|
//
|
|
// This is so that we can handle click and drag for multisel
|
|
// listboxes using Shift modifier key .
|
|
//
|
|
plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
//
|
|
// Check if an anchor point already exists
|
|
//
|
|
if (plb->iMouseDown == -1)
|
|
{
|
|
plb->iMouseDown = iSelFromPt;
|
|
|
|
//
|
|
// Reset all the previous selections
|
|
//
|
|
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
|
|
|
|
//
|
|
// Select the current position
|
|
//
|
|
ListBox_SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL);
|
|
ListBox_InvertItem(plb, plb->iMouseDown, TRUE);
|
|
|
|
//
|
|
// We are changing the selction to this item only
|
|
//
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Reset all the previous selections
|
|
//
|
|
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
|
|
|
|
//
|
|
// Select all items from anchor point upto current click pt
|
|
//
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, iSelFromPt, HILITE, HILITEONLY, FALSE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
}
|
|
|
|
plb->fNewItemState = (UINT)TRUE;
|
|
|
|
break;
|
|
|
|
case CTLDOWN:
|
|
|
|
//
|
|
// This is so that we can handle click and drag for multisel
|
|
// listboxes using Control modifier key.
|
|
//
|
|
|
|
//
|
|
// Reset the anchor point to the current point
|
|
//
|
|
plb->iMouseDown = plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
//
|
|
// The state we will be setting items to
|
|
//
|
|
plb->fNewItemState = (UINT)!ListBox_IsSelected(plb, iSelFromPt, (UINT)HILITEONLY);
|
|
|
|
//
|
|
// Toggle the current point
|
|
//
|
|
ListBox_SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL);
|
|
ListBox_InvertItem(plb, iSelFromPt, plb->fNewItemState);
|
|
|
|
uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
break;
|
|
|
|
case SHCTLDOWN:
|
|
|
|
//
|
|
// This is so that we can handle click and drag for multisel
|
|
// listboxes using Shift and Control modifier keys.
|
|
//
|
|
|
|
//
|
|
// Preserve all the previous selections
|
|
//
|
|
|
|
//
|
|
// Deselect only the selection connected with the last
|
|
// anchor point; If the last anchor point is associated with a
|
|
// de-selection, then do not do it
|
|
//
|
|
if (plb->fNewItemState)
|
|
{
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE, HILITEANDSEL, FALSE);
|
|
}
|
|
|
|
plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
//
|
|
// Check if an anchor point already exists
|
|
//
|
|
if (plb->iMouseDown == -1)
|
|
{
|
|
//
|
|
// No existing anchor point; Make the current pt as anchor
|
|
//
|
|
plb->iMouseDown = iSelFromPt;
|
|
}
|
|
|
|
//
|
|
// If one exists preserve the most recent anchor point
|
|
//
|
|
|
|
//
|
|
// The state we will be setting items to
|
|
//
|
|
plb->fNewItemState = (UINT)ListBox_IsSelected(plb, plb->iMouseDown, HILITEONLY);
|
|
|
|
//
|
|
// Select all items from anchor point upto current click pt
|
|
//
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, HILITEONLY, FALSE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Set the new base point (the outline frame caret). We do the check
|
|
// first to avoid flashing the caret unnecessarly.
|
|
//
|
|
if (plb->iSelBase != iSelFromPt)
|
|
{
|
|
//
|
|
// Since ListBox_SetISelBase always turns on the caret, we don't need to
|
|
// do it here...
|
|
//
|
|
ListBox_SetISelBase(plb, iSelFromPt);
|
|
}
|
|
|
|
//
|
|
// ListBox_SetISelBase will change the focus and send a focus event.
|
|
// Then we send the selection event.
|
|
//
|
|
if (uEvent)
|
|
{
|
|
ListBox_Event(plb, uEvent, iSelFromPt);
|
|
}
|
|
|
|
if (wMsg == WM_LBUTTONDOWN && (GET_EXSTYLE(plb) & WS_EX_DRAGOBJECT)!=0)
|
|
{
|
|
if (DragDetect(hwnd, pt))
|
|
{
|
|
//
|
|
// User is trying to drag object...
|
|
//
|
|
|
|
//
|
|
// Fake an up click so that the item is selected...
|
|
//
|
|
ListBox_TrackMouse(plb, WM_LBUTTONUP, pt);
|
|
|
|
//
|
|
// Notify parent
|
|
// #ifndef WIN16 (32-bit Windows), plb->iSelBase gets
|
|
// zero-extended to LONG wParam automatically by the compiler.
|
|
//
|
|
SendMessage(plb->hwndParent, WM_BEGINDRAG, plb->iSelBase, (LPARAM)hwnd);
|
|
}
|
|
else
|
|
{
|
|
ListBox_TrackMouse(plb, WM_LBUTTONUP, pt);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
int dist;
|
|
int iTimer;
|
|
|
|
//
|
|
// Save for timer.
|
|
//
|
|
plb->ptPrev = pt;
|
|
|
|
//
|
|
// Autoscroll listbox if mouse button is held down and mouse is
|
|
// moved outside of the listbox
|
|
//
|
|
if (plb->fMouseDown)
|
|
{
|
|
if (plb->fMultiColumn)
|
|
{
|
|
if ((pt.x < 0) || (pt.x >= rcClient.right - 1))
|
|
{
|
|
//
|
|
// Reset timer interval based on distance from listbox.
|
|
// use a longer default interval because each multicolumn
|
|
// scrolling increment is larger
|
|
//
|
|
dist = pt.x < 0 ? -pt.x : (pt.x - rcClient.right + 1);
|
|
iTimer = ((SCROLL_TIMEOUT() * 3) / 2) - ((WORD) dist << 4);
|
|
|
|
if (plb->fRightAlign)
|
|
{
|
|
ListBox_HSrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0);
|
|
}
|
|
else
|
|
{
|
|
ListBox_HSrollMultiColumn(plb, (pt.x < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
|
|
}
|
|
|
|
goto SetTimerAndSel;
|
|
}
|
|
}
|
|
else if ((pt.y < 0) || (pt.y >= rcClient.bottom - 1))
|
|
{
|
|
//
|
|
// Reset timer interval based on distance from listbox.
|
|
//
|
|
dist = pt.y < 0 ? -pt.y : (pt.y - rcClient.bottom + 1);
|
|
iTimer = SCROLL_TIMEOUT() - ((WORD) dist << 4);
|
|
|
|
ListBox_VScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
|
|
SetTimerAndSel:
|
|
SetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL);
|
|
ListBox_ISelFromPt(plb, pt, &iSelFromPt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Ignore if not in client since we don't autoscroll
|
|
//
|
|
if (!PtInRect(&rcClient, pt))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (plb->wMultiple)
|
|
{
|
|
case SINGLESEL:
|
|
|
|
//
|
|
// If it is a single selection or plain multisel list box
|
|
//
|
|
goto MouseMoveHandler;
|
|
|
|
case MULTIPLESEL:
|
|
case EXTENDEDSEL:
|
|
|
|
//
|
|
// Handle mouse movement with extended selection of items
|
|
//
|
|
if (plb->iSelBase != iSelFromPt)
|
|
{
|
|
ListBox_SetISelBase(plb, iSelFromPt);
|
|
|
|
//
|
|
// If this is an extended Multi sel list box, then
|
|
// adjust the display of the range due to the mouse move
|
|
//
|
|
if (plb->wMultiple == EXTENDEDSEL)
|
|
{
|
|
ListBox_BlockHilite(plb, iSelFromPt, FALSE);
|
|
ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt);
|
|
}
|
|
plb->iLastMouseMove = iSelFromPt;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case WM_LBUTTONUP:
|
|
if (plb->fMouseDown)
|
|
{
|
|
ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
|
|
(mousetemp ? LBUP_RESETSELECTION : 0) |
|
|
(fMouseInRect ? LBUP_SUCCESS : 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_ButtonUp
|
|
//
|
|
// Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP.
|
|
//
|
|
void ListBox_ButtonUp(PLBIV plb, UINT uFlags)
|
|
{
|
|
//
|
|
// If the list box is an Extended listbox, then change the select status
|
|
// of all items between the anchor and the last mouse position to the
|
|
// newItemState
|
|
//
|
|
if (plb->wMultiple == EXTENDEDSEL)
|
|
{
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
|
|
plb->fNewItemState, SELONLY, FALSE);
|
|
}
|
|
|
|
//
|
|
// This is a combo box and user upclicked outside the listbox
|
|
// so we want to restore the original selection.
|
|
//
|
|
if (plb->pcbox && (uFlags & LBUP_RESETSELECTION))
|
|
{
|
|
int iSelOld;
|
|
|
|
iSelOld = plb->iSel;
|
|
|
|
if (iSelOld >= 0)
|
|
{
|
|
ListBox_InvertItem(plb, plb->iSel, FALSE);
|
|
}
|
|
|
|
plb->iSel = plb->iLastSelection;
|
|
ListBox_InvertItem(plb, plb->iSel, TRUE);
|
|
|
|
//
|
|
// Note that we always send selection events before we tell the
|
|
// app. This is on purpose--the app may turn around and select
|
|
// something else when notified. In which case our event would
|
|
// be out of order.
|
|
//
|
|
ListBox_Event(plb, EVENT_OBJECT_SELECTION, plb->iSel);
|
|
|
|
//
|
|
// On win-95 and NT4 the check used to be !(uFlags & LBUP_NOTIFY) which
|
|
// is a bug because we would notify even when the lb is not LBUP_NOTIFY
|
|
//
|
|
if ((uFlags & LBUP_NOTIFY) && plb->fNotify && (iSelOld != plb->iSel))
|
|
{
|
|
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
}
|
|
|
|
KillTimer(plb->hwnd, IDSYS_SCROLL);
|
|
plb->fMouseDown = FALSE;
|
|
|
|
if ( plb->fCaptured || (GetCapture() == plb->hwndParent) )
|
|
{
|
|
plb->fCaptured = FALSE;
|
|
if (uFlags & LBUP_RELEASECAPTURE)
|
|
{
|
|
ReleaseCapture();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Don't scroll item as long as any part of it is visible
|
|
//
|
|
if (plb->iSelBase < plb->iTop ||
|
|
plb->iSelBase > plb->iTop + ListBox_CItemInWindow(plb, TRUE))
|
|
{
|
|
ListBox_InsureVisible(plb, plb->iSelBase, FALSE);
|
|
}
|
|
|
|
if (plb->fNotify)
|
|
{
|
|
if (uFlags & LBUP_NOTIFY)
|
|
{
|
|
if (uFlags & LBUP_SUCCESS)
|
|
{
|
|
//
|
|
// ArtMaster needs this SELCHANGE notification now!
|
|
//
|
|
if ((plb->fDoubleClick) && !TESTFLAG(GET_STATE2(plb), WS_S2_WIN31COMPAT))
|
|
{
|
|
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
|
|
//
|
|
// Notify owner of click or double click on selection
|
|
//
|
|
ListBox_NotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Notify owner that the attempted selection was cancelled.
|
|
//
|
|
ListBox_NotifyOwner(plb, LBN_SELCANCEL);
|
|
}
|
|
}
|
|
else if (uFlags & LBUP_SELCHANGE)
|
|
{
|
|
//
|
|
// Did we do some semi-selecting with mouse moves, then hit Enter?
|
|
// If so, we need to make sure the app knows that something was
|
|
// really truly selected.
|
|
//
|
|
ASSERT(TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT));
|
|
|
|
if (plb->iLastSelection != plb->iSel)
|
|
{
|
|
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
INT ListBox_IncrementISel(PLBIV plb, INT iSel, INT sInc)
|
|
{
|
|
//
|
|
// Assumes cMac > 0, return iSel+sInc in range [0..cmac).
|
|
//
|
|
iSel += sInc;
|
|
if (iSel < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (iSel >= plb->cMac)
|
|
{
|
|
return plb->cMac - 1;
|
|
}
|
|
|
|
return iSel;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_NewITop(PLBIV plb, INT iTopNew)
|
|
{
|
|
ListBox_NewITopEx(plb, iTopNew, 0);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_NewITopEx(PLBIV plb, INT iTopNew, DWORD dwTime)
|
|
{
|
|
int iTopOld;
|
|
BOOL fCaretOn;
|
|
BOOL fMulti = plb->fMultiColumn;
|
|
|
|
|
|
//
|
|
// Always try to turn off caret whether or not redraw is on
|
|
//
|
|
if (fCaretOn = plb->fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop;
|
|
plb->iTop = iTopNew;
|
|
iTopNew = ListBox_SetScrollParms(plb, (fMulti) ? SB_HORZ : SB_VERT);
|
|
plb->iTop = (fMulti) ? (iTopNew * plb->itemsPerColumn) : iTopNew;
|
|
|
|
if (!IsLBoxVisible(plb))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (iTopNew != iTopOld)
|
|
{
|
|
int xAmt, yAmt;
|
|
RECT rc;
|
|
DWORD dwFlags;
|
|
|
|
GetClientRect(plb->hwnd, &rc);
|
|
|
|
if (fMulti)
|
|
{
|
|
yAmt = 0;
|
|
if (abs(iTopNew - iTopOld) > plb->numberOfColumns)
|
|
{
|
|
//
|
|
// Handle scrolling a large number of columns properly so that
|
|
// we don't overflow the size of a rect.
|
|
//
|
|
xAmt = 32000;
|
|
}
|
|
else
|
|
{
|
|
xAmt = (iTopOld - iTopNew) * plb->cxColumn;
|
|
if (plb->fRightAlign)
|
|
{
|
|
xAmt = -xAmt;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xAmt = 0;
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
//
|
|
// Have to fake iTopOld for OWNERDRAWVAR listboxes so that
|
|
// the scrolling amount calculations work properly.
|
|
//
|
|
plb->iTop = iTopOld;
|
|
yAmt = ListBox_CalcVarITopScrollAmt(plb, iTopOld, iTopNew);
|
|
plb->iTop = iTopNew;
|
|
}
|
|
else if (abs(iTopNew - iTopOld) > plb->cItemFullMax)
|
|
{
|
|
yAmt = 32000;
|
|
}
|
|
else
|
|
{
|
|
yAmt = (iTopOld - iTopNew) * plb->cyChar;
|
|
}
|
|
}
|
|
|
|
dwFlags = ListBox_GetScrollFlags(plb, dwTime);
|
|
ScrollWindowEx(plb->hwnd, xAmt, yAmt, NULL, &rc, NULL, NULL, dwFlags);
|
|
UpdateWindow(plb->hwnd);
|
|
}
|
|
|
|
//
|
|
// Note that although we turn off the caret regardless of redraw, we
|
|
// only turn it on if redraw is true. Slimy thing to fixup many
|
|
// caret related bugs...
|
|
//
|
|
if (fCaretOn)
|
|
{
|
|
// Turn the caret back on only if we turned it off. This avoids
|
|
// annoying caret flicker.
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_InsureVisible( PLBIV plb, INT iSel, BOOL fPartial)
|
|
{
|
|
INT sLastVisibleItem;
|
|
|
|
if (iSel < plb->iTop)
|
|
{
|
|
ListBox_NewITop(plb, iSel);
|
|
}
|
|
else
|
|
{
|
|
if (fPartial)
|
|
{
|
|
//
|
|
// 1 must be subtracted to get the last visible item
|
|
// A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR
|
|
//
|
|
sLastVisibleItem = plb->iTop + ListBox_CItemInWindow(plb, TRUE) - (INT)1;
|
|
}
|
|
else
|
|
{
|
|
sLastVisibleItem = ListBox_LastFullVisible(plb);
|
|
}
|
|
|
|
if (plb->OwnerDraw != OWNERDRAWVAR)
|
|
{
|
|
if (iSel > sLastVisibleItem)
|
|
{
|
|
if (plb->fMultiColumn)
|
|
{
|
|
ListBox_NewITop(plb,
|
|
((iSel / plb->itemsPerColumn) -
|
|
max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn);
|
|
}
|
|
else
|
|
{
|
|
ListBox_NewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop));
|
|
}
|
|
}
|
|
}
|
|
else if (iSel > sLastVisibleItem)
|
|
{
|
|
ListBox_NewITop(plb, ListBox_Page(plb, iSel, FALSE));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_CareBlinker
|
|
//
|
|
// Timer callback function toggles Caret
|
|
// Since it is a callback, it is APIENTRY
|
|
//
|
|
VOID ListBox_CareBlinker(HWND hwnd, UINT wMsg, UINT_PTR nIDEvent, DWORD dwTime)
|
|
{
|
|
PLBIV plb;
|
|
|
|
//
|
|
// Standard parameters for a timer callback function that aren't used.
|
|
// Mentioned here to avoid compiler warnings
|
|
//
|
|
UNREFERENCED_PARAMETER(wMsg);
|
|
UNREFERENCED_PARAMETER(nIDEvent);
|
|
UNREFERENCED_PARAMETER(dwTime);
|
|
|
|
plb = ListBox_GetPtr(hwnd);
|
|
|
|
//
|
|
// leave caret on, don't blink it off (prevents rapid blinks?)
|
|
//
|
|
if (ISREMOTESESSION() && plb->fCaretOn)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check if the Caret is ON, if so, switch it OFF
|
|
//
|
|
ListBox_SetCaret(plb, !plb->fCaretOn);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_KeyInput
|
|
//
|
|
// If msg == LB_KEYDOWN, vKey is the number of the item to go to,
|
|
// otherwise it is the virtual key.
|
|
//
|
|
void ListBox_KeyInput(PLBIV plb, UINT msg, UINT vKey)
|
|
{
|
|
INT i;
|
|
INT iNewISel;
|
|
INT cItemPageScroll;
|
|
PCBOX pcbox;
|
|
BOOL fDropDownComboBox;
|
|
BOOL fExtendedUIComboBoxClosed;
|
|
UINT wModifiers = 0;
|
|
BOOL fSelectKey = FALSE; // assume it is a navigation key
|
|
UINT uEvent = 0;
|
|
HWND hwnd = plb->hwnd;
|
|
|
|
BOOL hScrollBar = (GET_STYLE(plb)&WS_HSCROLL)!=0;
|
|
|
|
pcbox = plb->pcbox;
|
|
|
|
//
|
|
// Is this a dropdown style combo box/listbox ?
|
|
//
|
|
fDropDownComboBox = pcbox && (pcbox->CBoxStyle & SDROPPABLE);
|
|
|
|
//
|
|
// Is this an extended ui combo box which is closed?
|
|
//
|
|
fExtendedUIComboBoxClosed = fDropDownComboBox && pcbox->fExtendedUI &&
|
|
!pcbox->fLBoxVisible;
|
|
|
|
if (plb->fMouseDown || (!plb->cMac && vKey != VK_F4))
|
|
{
|
|
//
|
|
// Ignore keyboard input if we are in the middle of a mouse down deal or
|
|
// if there are no items in the listbox. Note that we let F4's go
|
|
// through for combo boxes so that the use can pop up and down empty
|
|
// combo boxes.
|
|
//
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Modifiers are considered only in EXTENDED sel list boxes.
|
|
//
|
|
if (plb->wMultiple == EXTENDEDSEL)
|
|
{
|
|
//
|
|
// If multiselection listbox, are any modifiers used ?
|
|
//
|
|
if (GetKeyState(VK_SHIFT) < 0)
|
|
{
|
|
wModifiers = SHIFTDOWN;
|
|
}
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
wModifiers += CTLDOWN;
|
|
}
|
|
|
|
//
|
|
// Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
|
|
//
|
|
}
|
|
|
|
if (msg == LB_KEYDOWN)
|
|
{
|
|
//
|
|
// This is a listbox "go to specified item" message which means we want
|
|
// to go to a particular item number (given by vKey) directly. ie. the
|
|
// user has typed a character and we want to go to the item which
|
|
// starts with that character.
|
|
//
|
|
iNewISel = (INT)vKey;
|
|
|
|
goto TrackKeyDown;
|
|
}
|
|
|
|
cItemPageScroll = plb->cItemFullMax;
|
|
|
|
if (cItemPageScroll > 1)
|
|
{
|
|
cItemPageScroll--;
|
|
}
|
|
|
|
if (plb->fWantKeyboardInput)
|
|
{
|
|
//
|
|
// Note: msg must not be LB_KEYDOWN here or we'll be in trouble...
|
|
//
|
|
iNewISel = (INT)SendMessage(plb->hwndParent, WM_VKEYTOITEM,
|
|
MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd);
|
|
|
|
if (iNewISel == -2)
|
|
{
|
|
//
|
|
// Don't move the selection...
|
|
//
|
|
return;
|
|
}
|
|
|
|
if (iNewISel != -1)
|
|
{
|
|
//
|
|
// Jump directly to the item provided by the app
|
|
//
|
|
goto TrackKeyDown;
|
|
}
|
|
|
|
//
|
|
// else do default processing of the character.
|
|
//
|
|
}
|
|
|
|
switch (vKey)
|
|
{
|
|
//
|
|
// LATER IanJa: not language independent!!!
|
|
// We could use VkKeyScan() to find out which is the '\' key
|
|
// This is VK_OEM_5 '\|' for US English only.
|
|
// Germans, Italians etc. have to type CTRL+^ (etc) for this.
|
|
// This is documented as File Manager behaviour for 3.0, but apparently
|
|
// not for 3.1., although functionality remains. We should still fix it,
|
|
// although German (etc?) '\' is generated with AltGr (Ctrl-Alt) (???)
|
|
//
|
|
case VERKEY_BACKSLASH:
|
|
//
|
|
// '\' character for US English
|
|
//
|
|
|
|
//
|
|
// Check if this is CONTROL-\ ; If so Deselect all items
|
|
//
|
|
if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL))
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
ListBox_ResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE);
|
|
|
|
//
|
|
// And select the current item
|
|
//
|
|
ListBox_SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL);
|
|
ListBox_InvertItem(plb, plb->iSelBase, TRUE);
|
|
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
goto CaretOnAndNotify;
|
|
}
|
|
|
|
return;
|
|
|
|
case VK_DIVIDE:
|
|
//
|
|
// NumPad '/' character on enhanced keyboard
|
|
//
|
|
|
|
//
|
|
// LATER IanJa: not language independent!!!
|
|
// We could use VkKeyScan() to find out which is the '/' key
|
|
// This is VK_OEM_2 '/?' for US English only.
|
|
// Germans, Italians etc. have to type CTRL+# (etc) for this.
|
|
//
|
|
case VERKEY_SLASH:
|
|
//
|
|
// '/' character
|
|
//
|
|
|
|
//
|
|
// Check if this is CONTROL-/ ; If so select all items
|
|
//
|
|
if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL))
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
ListBox_ResetWorld(plb, -1, -1, TRUE);
|
|
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
CaretOnAndNotify:
|
|
ListBox_SetCaret(plb, TRUE);
|
|
ListBox_Event(plb, uEvent, plb->iSelBase);
|
|
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
|
|
return;
|
|
|
|
case VK_F8:
|
|
|
|
//
|
|
// The "Add" mode is possible only in Multiselection listboxes... Get
|
|
// into it via SHIFT-F8... (Yes, sometimes these UI people are sillier
|
|
// than your "typical dumb user"...)
|
|
//
|
|
if (plb->wMultiple != SINGLESEL && wModifiers == SHIFTDOWN)
|
|
{
|
|
//
|
|
// We have to make the caret blink! Do something...
|
|
//
|
|
if (plb->fAddSelMode)
|
|
{
|
|
//
|
|
// Switch off the Caret blinking
|
|
//
|
|
KillTimer(hwnd, IDSYS_CARET);
|
|
|
|
//
|
|
// Make sure the caret does not vanish
|
|
//
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Create a timer to make the caret blink
|
|
//
|
|
SetTimer(hwnd, IDSYS_CARET, GetCaretBlinkTime(),
|
|
ListBox_CareBlinker);
|
|
}
|
|
|
|
//
|
|
// Toggle the Add mode flag
|
|
//
|
|
plb->fAddSelMode = (UINT)!plb->fAddSelMode;
|
|
}
|
|
|
|
return;
|
|
|
|
case VK_SPACE:
|
|
//
|
|
// Selection key is space
|
|
//
|
|
i = 0;
|
|
fSelectKey = TRUE;
|
|
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// Disable movement keys for TandyT.
|
|
//
|
|
return;
|
|
}
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
i = ListBox_Page(plb, plb->iSelBase, FALSE) - plb->iSelBase;
|
|
}
|
|
else
|
|
{
|
|
i = -cItemPageScroll;
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// Disable movement keys for TandyT.
|
|
//
|
|
return;
|
|
}
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR)
|
|
{
|
|
i = ListBox_Page(plb, plb->iSelBase, TRUE) - plb->iSelBase;
|
|
}
|
|
else
|
|
{
|
|
i = cItemPageScroll;
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_HOME:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// Disable movement keys for TandyT.
|
|
//
|
|
return;
|
|
}
|
|
|
|
i = (INT_MIN/2)+1; // A very big negative number
|
|
|
|
break;
|
|
|
|
case VK_END:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// Disable movement keys for TandyT.
|
|
//
|
|
return;
|
|
}
|
|
|
|
i = (INT_MAX/2)-1; // A very big positive number
|
|
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
if (plb->fMultiColumn)
|
|
{
|
|
if (plb->fRightAlign
|
|
#ifdef USE_MIRRORING
|
|
^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL))
|
|
|
|
#endif
|
|
)
|
|
{
|
|
goto ReallyRight;
|
|
}
|
|
|
|
ReallyLeft:
|
|
if (plb->iSelBase / plb->itemsPerColumn == 0)
|
|
{
|
|
i = 0;
|
|
}
|
|
else
|
|
{
|
|
i = -plb->itemsPerColumn;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (hScrollBar)
|
|
{
|
|
goto HandleHScrolling;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Fall through and handle this as if the up arrow was pressed.
|
|
//
|
|
vKey = VK_UP;
|
|
}
|
|
|
|
//
|
|
// Fall through
|
|
//
|
|
|
|
case VK_UP:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// Disable movement keys for TandyT.
|
|
//
|
|
return;
|
|
}
|
|
|
|
i = -1;
|
|
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
if (plb->fMultiColumn)
|
|
{
|
|
if (plb->fRightAlign
|
|
#ifdef USE_MIRRORING
|
|
^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL))
|
|
|
|
#endif
|
|
)
|
|
{
|
|
goto ReallyLeft;
|
|
}
|
|
|
|
ReallyRight:
|
|
if (plb->iSelBase / plb->itemsPerColumn == plb->cMac / plb->itemsPerColumn)
|
|
{
|
|
i = 0;
|
|
}
|
|
else
|
|
{
|
|
i = plb->itemsPerColumn;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (hScrollBar)
|
|
{
|
|
HandleHScrolling:
|
|
PostMessage(hwnd, WM_HSCROLL,
|
|
(vKey == VK_RIGHT ? SB_LINEDOWN : SB_LINEUP), 0L);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Fall through and handle this as if the down arrow was
|
|
// pressed.
|
|
//
|
|
vKey = VK_DOWN;
|
|
}
|
|
|
|
//
|
|
// Fall through
|
|
//
|
|
|
|
case VK_DOWN:
|
|
if (fExtendedUIComboBoxClosed)
|
|
{
|
|
//
|
|
// If the combo box is closed, down arrow should open it.
|
|
//
|
|
if (!pcbox->fLBoxVisible)
|
|
{
|
|
//
|
|
// If the listbox isn't visible, just show it
|
|
//
|
|
ComboBox_ShowListBoxWindow(pcbox, TRUE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
i = 1;
|
|
|
|
break;
|
|
|
|
case VK_ESCAPE:
|
|
case VK_RETURN:
|
|
if (!fDropDownComboBox || !pcbox->fLBoxVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// | If this is a dropped listbox for a combobox and the ENTER |
|
|
// | key is pressed, close up the listbox, so FALLTHRU |
|
|
// V V
|
|
//
|
|
|
|
case VK_F4:
|
|
if (fDropDownComboBox && !pcbox->fExtendedUI)
|
|
{
|
|
//
|
|
// If we are a dropdown combo box/listbox we want to process
|
|
// this key. BUT for TandtT, we don't do anything on VK_F4 if we
|
|
// are in extended ui mode.
|
|
//
|
|
if (!pcbox->fLBoxVisible)
|
|
{
|
|
//
|
|
// If the listbox isn't visible, just show it
|
|
//
|
|
ComboBox_ShowListBoxWindow(pcbox, (vKey != VK_ESCAPE));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Ok, the listbox is visible. So hide the listbox window.
|
|
//
|
|
ComboBox_HideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fall through to the return
|
|
//
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Find out what the new selection should be
|
|
//
|
|
iNewISel = ListBox_IncrementISel(plb, plb->iSelBase, i);
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
{
|
|
if (plb->iSel == iNewISel)
|
|
{
|
|
//
|
|
// If we are single selection and the keystroke is moving us to an
|
|
// item which is already selected, we don't have to do anything...
|
|
//
|
|
return;
|
|
}
|
|
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
|
|
plb->iTypeSearch = 0;
|
|
if ((vKey == VK_UP || vKey == VK_DOWN) &&
|
|
!ListBox_IsSelected(plb, plb->iSelBase, HILITEONLY))
|
|
{
|
|
//
|
|
// If the caret is on an unselected item and the user just hits the
|
|
// up or down arrow key (ie. with no shift or ctrl modifications),
|
|
// then we will just select the item the cursor is at. This is
|
|
// needed for proper behavior in combo boxes but do we always want
|
|
// to run this code??? Note that this is only used in single
|
|
// selection list boxes since it doesn't make sense in the
|
|
// multiselection case. Note that an LB_KEYDOWN message must not be
|
|
// checked here because the vKey will be an item number not a
|
|
// VK_and we will goof. Thus, trackkeydown label is below this to
|
|
// fix a bug caused by it being above this...
|
|
//
|
|
iNewISel = (plb->iSelBase == -1) ? 0 : plb->iSelBase;
|
|
}
|
|
}
|
|
|
|
TrackKeyDown:
|
|
|
|
ListBox_SetISelBase(plb, iNewISel);
|
|
|
|
ListBox_SetCaret(plb, FALSE);
|
|
|
|
if (wModifiers & SHIFTDOWN)
|
|
{
|
|
//
|
|
// Check if iMouseDown is un-initialised
|
|
//
|
|
if (plb->iMouseDown == -1)
|
|
{
|
|
plb->iMouseDown = iNewISel;
|
|
}
|
|
|
|
if (plb->iLastMouseMove == -1)
|
|
{
|
|
plb->iLastMouseMove = iNewISel;
|
|
}
|
|
|
|
//
|
|
// Check if we are in ADD mode
|
|
//
|
|
if (plb->fAddSelMode)
|
|
{
|
|
//
|
|
// Preserve all the pre-existing selections except the
|
|
// ones connected with the last anchor point; If the last
|
|
// Preserve all the previous selections
|
|
//
|
|
|
|
//
|
|
// Deselect only the selection connected with the last
|
|
// anchor point; If the last anchor point is associated
|
|
// with de-selection, then do not do it
|
|
//
|
|
|
|
if (!plb->fNewItemState)
|
|
{
|
|
plb->iLastMouseMove = plb->iMouseDown;
|
|
}
|
|
|
|
//
|
|
// We haven't done anything here because, ListBox_BlockHilite()
|
|
// will take care of wiping out the selection between
|
|
// Anchor point and iLastMouseMove and select the block
|
|
// between anchor point and current cursor location
|
|
//
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We are not in ADD mode
|
|
//
|
|
|
|
//
|
|
// Remove all selections except between the anchor point
|
|
// and last mouse move because it will be taken care of in
|
|
// ListBox_BlockHilite
|
|
//
|
|
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE);
|
|
}
|
|
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
//
|
|
// ListBox_BlockHilite takes care to deselect the block between
|
|
// the anchor point and iLastMouseMove and select the block
|
|
// between the anchor point and the current cursor location
|
|
//
|
|
|
|
//
|
|
// Toggle all items to the same selection state as the item
|
|
// item at the anchor point) from the anchor point to the
|
|
// current cursor location.
|
|
//
|
|
plb->fNewItemState = ListBox_IsSelected(plb, plb->iMouseDown, SELONLY);
|
|
ListBox_BlockHilite(plb, iNewISel, TRUE);
|
|
|
|
plb->iLastMouseMove = iNewISel;
|
|
|
|
//
|
|
// Preserve the existing anchor point
|
|
//
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Check if this is in ADD mode
|
|
//
|
|
if ((plb->fAddSelMode) || (plb->wMultiple == MULTIPLESEL))
|
|
{
|
|
//
|
|
// Preserve all pre-exisiting selections
|
|
//
|
|
if (fSelectKey)
|
|
{
|
|
//
|
|
// Toggle the selection state of the current item
|
|
//
|
|
plb->fNewItemState = !ListBox_IsSelected(plb, iNewISel, SELONLY);
|
|
ListBox_SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL);
|
|
|
|
ListBox_InvertItem(plb, iNewISel, plb->fNewItemState);
|
|
|
|
//
|
|
// Set the anchor point at the current location
|
|
//
|
|
plb->iLastMouseMove = plb->iMouseDown = iNewISel;
|
|
uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We are NOT in ADD mode
|
|
//
|
|
|
|
//
|
|
// Remove all existing selections except iNewISel, to
|
|
// avoid flickering.
|
|
//
|
|
ListBox_ResetWorld(plb, iNewISel, iNewISel, FALSE);
|
|
|
|
//
|
|
// Select the current item
|
|
//
|
|
ListBox_SetSelected(plb, iNewISel, TRUE, HILITEANDSEL);
|
|
ListBox_InvertItem(plb, iNewISel, TRUE);
|
|
|
|
//
|
|
// Set the anchor point at the current location
|
|
//
|
|
plb->iLastMouseMove = plb->iMouseDown = iNewISel;
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move the cursor to the new location
|
|
//
|
|
ListBox_InsureVisible(plb, iNewISel, FALSE);
|
|
ListBox_ShowHideScrollBars(plb);
|
|
|
|
ListBox_SetCaret(plb, TRUE);
|
|
|
|
if (uEvent)
|
|
{
|
|
ListBox_Event(plb, uEvent, iNewISel);
|
|
}
|
|
|
|
//
|
|
// Should we notify our parent?
|
|
//
|
|
if (plb->fNotify)
|
|
{
|
|
if (fDropDownComboBox && pcbox->fLBoxVisible)
|
|
{
|
|
//
|
|
// If we are in a drop down combo box/listbox and the listbox is
|
|
// visible, we need to set the fKeyboardSelInListBox bit so that the
|
|
// combo box code knows not to hide the listbox since the selchange
|
|
// message is caused by the user keyboarding through...
|
|
//
|
|
pcbox->fKeyboardSelInListBox = TRUE;
|
|
plb->iLastSelection = iNewISel;
|
|
}
|
|
|
|
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_Compare
|
|
//
|
|
// Is lpstr1 equal/prefix/less-than/greater-than lsprst2 (case-insensitive) ?
|
|
//
|
|
// LATER IanJa: this assume a longer string is never a prefix of a longer one.
|
|
// Also assumes that removing 1 or more characters from the end of a string will
|
|
// give a string tahs sort before the original. These assumptions are not valid
|
|
// for all languages. We nedd better support from NLS. (Consider French
|
|
// accents, Spanish c/ch, ligatures, German sharp-s/SS, etc.)
|
|
//
|
|
INT ListBox_Compare(LPCWSTR pwsz1, LPCWSTR pwsz2, DWORD dwLocaleId)
|
|
{
|
|
UINT len1 = wcslen(pwsz1);
|
|
UINT len2 = wcslen(pwsz2);
|
|
INT result;
|
|
|
|
//
|
|
// CompareStringW returns:
|
|
// 1 = pwsz1 < pwsz2
|
|
// 2 = pwsz1 == pwsz2
|
|
// 3 = pwsz1 > pwsz2
|
|
//
|
|
result = CompareStringW((LCID)dwLocaleId, NORM_IGNORECASE,
|
|
pwsz1, min(len1,len2), pwsz2, min(len1, len2));
|
|
|
|
if (result == CSTR_LESS_THAN)
|
|
{
|
|
return LT;
|
|
}
|
|
else if (result == CSTR_EQUAL)
|
|
{
|
|
if (len1 == len2)
|
|
{
|
|
return EQ;
|
|
}
|
|
else if (len1 < len2)
|
|
{
|
|
//
|
|
// LATER IanJa: should not assume shorter string is a prefix
|
|
// Spanish "c" and "ch", ligatures, German sharp-s/SS etc.
|
|
//
|
|
return PREFIX;
|
|
}
|
|
}
|
|
|
|
return GT;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// Listbox_FindStringHandler
|
|
//
|
|
// Scans for a string in the listbox prefixed by or equal to lpstr.
|
|
// For OWNERDRAW listboxes without strings and without the sort style, we
|
|
// try to match the long app supplied values.
|
|
//
|
|
INT Listbox_FindStringHandler(PLBIV plb, LPWSTR lpstr, INT sStart, INT code, BOOL fWrap)
|
|
{
|
|
//
|
|
// Search for a prefix match (case-insensitive equal/prefix)
|
|
// sStart == -1 means start from beginning, else start looking at sStart+1
|
|
// assumes cMac > 0.
|
|
//
|
|
INT sInd; // index of string
|
|
INT sStop; // index to stop searching at
|
|
lpLBItem pRg;
|
|
INT sortResult;
|
|
|
|
//
|
|
// Owner-Draw version of pRg
|
|
//
|
|
#define pODRg ((lpLBODItem)pRg)
|
|
|
|
COMPAREITEMSTRUCT cis;
|
|
LPWSTR listboxString;
|
|
|
|
|
|
if (plb->fHasStrings && (!lpstr || !*lpstr))
|
|
{
|
|
return LB_ERR;
|
|
}
|
|
|
|
if (!plb->fHasData)
|
|
{
|
|
TraceMsg(TF_STANDARD, "Listbox_FindStringHandler called on NODATA lb");
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
if ((sInd = sStart + 1) >= plb->cMac)
|
|
{
|
|
sInd = (fWrap ? 0 : plb->cMac - 1);
|
|
}
|
|
|
|
sStop = (fWrap ? sInd : 0);
|
|
|
|
//
|
|
// If at end and no wrap, stop right away
|
|
//
|
|
if (((sStart >= plb->cMac - 1) && !fWrap) || (plb->cMac < 1))
|
|
{
|
|
return LB_ERR;
|
|
}
|
|
|
|
//
|
|
// Apps could pass in an invalid sStart like -2 and we would blow up.
|
|
// Win 3.1 would not so we need to fixup sInd to be zero
|
|
//
|
|
if (sInd < 0)
|
|
{
|
|
sInd = 0;
|
|
}
|
|
|
|
pRg = (lpLBItem)(plb->rgpch);
|
|
|
|
do
|
|
{
|
|
if (plb->fHasStrings)
|
|
{
|
|
//
|
|
// Searching for string matches.
|
|
//
|
|
listboxString = (LPWSTR)((LPBYTE)plb->hStrings + pRg[sInd].offsz);
|
|
|
|
if (code == PREFIX &&
|
|
listboxString &&
|
|
*lpstr != TEXT('[') &&
|
|
*listboxString == TEXT('['))
|
|
{
|
|
//
|
|
// If we are looking for a prefix string and the first items
|
|
// in this string are [- then we ignore them. This is so
|
|
// that in a directory listbox, the user can goto drives
|
|
// by selecting the drive letter.
|
|
//
|
|
listboxString++;
|
|
|
|
if (*listboxString == TEXT('-'))
|
|
{
|
|
listboxString++;
|
|
}
|
|
}
|
|
|
|
if (ListBox_Compare(lpstr, listboxString, plb->dwLocaleId) <= code)
|
|
{
|
|
goto FoundIt;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (plb->fSort)
|
|
{
|
|
//
|
|
// Send compare item messages to the parent for sorting
|
|
//
|
|
cis.CtlType = ODT_LISTBOX;
|
|
cis.CtlID = GetDlgCtrlID(plb->hwnd);
|
|
cis.hwndItem = plb->hwnd;
|
|
cis.itemID1 = (UINT)-1;
|
|
cis.itemData1 = (ULONG_PTR)lpstr;
|
|
cis.itemID2 = (UINT)sInd;
|
|
cis.itemData2 = pODRg[sInd].itemData;
|
|
cis.dwLocaleId = plb->dwLocaleId;
|
|
|
|
sortResult = (INT)SendMessage(plb->hwndParent, WM_COMPAREITEM,
|
|
cis.CtlID, (LPARAM)&cis);
|
|
|
|
|
|
if (sortResult == -1)
|
|
{
|
|
sortResult = LT;
|
|
}
|
|
else if (sortResult == 1)
|
|
{
|
|
sortResult = GT;
|
|
}
|
|
else
|
|
{
|
|
sortResult = EQ;
|
|
}
|
|
|
|
if (sortResult <= code)
|
|
{
|
|
goto FoundIt;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Searching for app supplied long data matches.
|
|
//
|
|
if ((ULONG_PTR)lpstr == pODRg[sInd].itemData)
|
|
{
|
|
goto FoundIt;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Wrap round to beginning of list
|
|
//
|
|
if (++sInd == plb->cMac)
|
|
{
|
|
sInd = 0;
|
|
}
|
|
}
|
|
while (sInd != sStop);
|
|
|
|
sInd = -1;
|
|
|
|
FoundIt:
|
|
return sInd;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_CharHandler(PLBIV plb, UINT inputChar, BOOL fAnsi)
|
|
{
|
|
INT iSel;
|
|
BOOL fControl;
|
|
|
|
if (plb->cMac == 0 || plb->fMouseDown)
|
|
{
|
|
//
|
|
// Get out if we are in the middle of mouse routines or if we have no
|
|
// items in the listbox, we just return without doing anything.
|
|
//
|
|
return;
|
|
}
|
|
|
|
fControl = (GetKeyState(VK_CONTROL) < 0);
|
|
|
|
switch (inputChar)
|
|
{
|
|
case VK_ESCAPE:
|
|
plb->iTypeSearch = 0;
|
|
if (plb->pszTypeSearch)
|
|
{
|
|
plb->pszTypeSearch[0] = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_BACK:
|
|
if (plb->iTypeSearch)
|
|
{
|
|
plb->pszTypeSearch[plb->iTypeSearch--] = 0;
|
|
if (plb->fSort)
|
|
{
|
|
iSel = -1;
|
|
goto TypeSearch;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_SPACE:
|
|
if (plb->fAddSelMode || plb->wMultiple == MULTIPLESEL)
|
|
{
|
|
break;
|
|
}
|
|
//
|
|
// Otherwise, for single/extended selection listboxes not in add
|
|
// selection mode, let the space go thru as a type search character
|
|
//
|
|
|
|
//
|
|
// FALL THRU
|
|
//
|
|
default:
|
|
|
|
//
|
|
// Move selection to first item beginning with the character the
|
|
// user typed. We don't want do this if we are using owner draw.
|
|
//
|
|
if (fAnsi && IsDBCSLeadByteEx(CP_ACP, (BYTE)inputChar))
|
|
{
|
|
WCHAR wch;
|
|
LPWSTR lpwstr = &wch;
|
|
|
|
inputChar = DbcsCombine(plb->hwnd, (BYTE)inputChar);
|
|
if (inputChar == 0)
|
|
{
|
|
TraceMsg(TF_STANDARD, "ListBox_CharHandler: cannot combine two DBCS. LB=0x%02x", inputChar);
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If it is DBCS, let's ignore the ctrl status.
|
|
//
|
|
fControl = FALSE;
|
|
|
|
//
|
|
// Convert DBCS to UNICODE.
|
|
// Note: Leading byte is in the low byte, trailing byte is in high byte.
|
|
// Let's assume Little Endian CPUs only, so inputChar can directly be
|
|
// input for MBSToWCSEx as an ANSI string.
|
|
//
|
|
if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)&inputChar, 2, lpwstr, 1) == 0)
|
|
{
|
|
TraceMsg(TF_STANDARD, "ListBox_CharHandler: cannot convert 0x%04x to UNICODE.", inputChar);
|
|
|
|
break;
|
|
}
|
|
|
|
inputChar = wch;
|
|
}
|
|
|
|
if (plb->fHasStrings)
|
|
{
|
|
//
|
|
// Incremental Type Search processing
|
|
//
|
|
// update szTypeSearch string and then move to the first item from
|
|
// the current selection whose prefix matches szTypeSearch
|
|
//
|
|
// the szTypeSearch will continue to grow until a "long enough"
|
|
// gap between key entries is encountered -- at which point any
|
|
// more searching will start over
|
|
//
|
|
|
|
//
|
|
// Undo CONTROL-char to char
|
|
//
|
|
if (fControl && inputChar < 0x20)
|
|
{
|
|
inputChar += 0x40;
|
|
}
|
|
|
|
if (plb->iTypeSearch == MAX_TYPESEARCH)
|
|
{
|
|
MessageBeep(0);
|
|
|
|
break;
|
|
}
|
|
|
|
iSel = -1;
|
|
|
|
if (plb->pszTypeSearch == NULL)
|
|
{
|
|
plb->pszTypeSearch = (LPWSTR)ControlAlloc(GetProcessHeap(), sizeof(WCHAR) * (MAX_TYPESEARCH + 1));
|
|
}
|
|
|
|
if (plb->pszTypeSearch == NULL)
|
|
{
|
|
MessageBeep(0);
|
|
|
|
break;
|
|
}
|
|
|
|
plb->pszTypeSearch[plb->iTypeSearch++] = (WCHAR) inputChar;
|
|
plb->pszTypeSearch[plb->iTypeSearch] = 0;
|
|
|
|
TypeSearch:
|
|
if (plb->fSort)
|
|
{
|
|
//
|
|
// Set timer to determine when to kill incremental searching
|
|
//
|
|
SetTimer(plb->hwnd, IDSYS_LBSEARCH,
|
|
GetDoubleClickTime()*4, NULL);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If this is not a sorted listbox, no incremental search.
|
|
//
|
|
plb->iTypeSearch = 0;
|
|
iSel = plb->iSelBase;
|
|
}
|
|
|
|
//
|
|
// Search for the item beginning with the given character starting
|
|
// at iSel+1. We will wrap the search to the beginning of the
|
|
// listbox if we don't find the item. If SHIFT is down and we are
|
|
// a multiselection lb, then the item's state will be set to
|
|
// plb->fNewItemState according to the current mode.
|
|
//
|
|
iSel = Listbox_FindStringHandler(plb, plb->pszTypeSearch, iSel, PREFIX, TRUE);
|
|
if (iSel == -1)
|
|
{
|
|
//
|
|
// no match found -- check for prefix match
|
|
// (i.e. "p" find FIRST item that starts with 'p',
|
|
// "pp" find NEXT item that starts with 'p')
|
|
//
|
|
if(plb->iTypeSearch)
|
|
{
|
|
plb->iTypeSearch--;
|
|
if ((plb->iTypeSearch == 1) && (plb->pszTypeSearch[0] == plb->pszTypeSearch[1]))
|
|
{
|
|
plb->pszTypeSearch[1] = 0;
|
|
iSel = Listbox_FindStringHandler(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if match is found -- select it
|
|
//
|
|
if (iSel != -1)
|
|
{
|
|
CtlKeyInput:
|
|
ListBox_KeyInput(plb, LB_KEYDOWN, iSel);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HWND hwndParent = plb->hwndParent;
|
|
|
|
if (hwndParent != NULL)
|
|
{
|
|
if(fAnsi)
|
|
{
|
|
iSel = (INT)SendMessageA(hwndParent, WM_CHARTOITEM,
|
|
MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd);
|
|
}
|
|
else
|
|
{
|
|
iSel = (INT)SendMessageW(hwndParent, WM_CHARTOITEM,
|
|
MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iSel = -1;
|
|
}
|
|
|
|
if (iSel != -1 && iSel != -2)
|
|
{
|
|
goto CtlKeyInput;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_GetSelItemsHandler
|
|
//
|
|
// effects: For multiselection listboxes, this returns the total number of
|
|
// selection items in the listbox if fCountOnly is true. or it fills an array
|
|
// (lParam) with the items numbers of the first wParam selected items.
|
|
//
|
|
int ListBox_GetSelItemsHandler(PLBIV plb, BOOL fCountOnly, int wParam, LPINT lParam)
|
|
{
|
|
int i;
|
|
int itemsselected = 0;
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
{
|
|
return LB_ERR;
|
|
}
|
|
|
|
for (i = 0; i < plb->cMac; i++)
|
|
{
|
|
if (ListBox_IsSelected(plb, i, SELONLY))
|
|
{
|
|
if (!fCountOnly)
|
|
{
|
|
if (itemsselected < wParam)
|
|
{
|
|
*lParam++ = i;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// That's all the items we can fit in the array.
|
|
//
|
|
return itemsselected;
|
|
}
|
|
}
|
|
|
|
itemsselected++;
|
|
}
|
|
}
|
|
|
|
return itemsselected;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_SetRedraw
|
|
//
|
|
// Handle WM_SETREDRAW message
|
|
//
|
|
void ListBox_SetRedraw(PLBIV plb, BOOL fRedraw)
|
|
{
|
|
if (fRedraw)
|
|
{
|
|
fRedraw = TRUE;
|
|
}
|
|
|
|
if (plb->fRedraw != (UINT)fRedraw)
|
|
{
|
|
plb->fRedraw = !!fRedraw;
|
|
|
|
if (fRedraw)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
ListBox_ShowHideScrollBars(plb);
|
|
|
|
if (plb->fDeferUpdate)
|
|
{
|
|
plb->fDeferUpdate = FALSE;
|
|
RedrawWindow(plb->hwnd, NULL, NULL,
|
|
RDW_INVALIDATE | RDW_ERASE |
|
|
RDW_FRAME | RDW_ALLCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_SetRange
|
|
//
|
|
// Selects the range of items between i and j, inclusive.
|
|
//
|
|
void ListBox_SetRange(PLBIV plb, int iStart, int iEnd, BOOL fnewstate)
|
|
{
|
|
DWORD temp;
|
|
RECT rc;
|
|
|
|
if (iStart > iEnd)
|
|
{
|
|
temp = iEnd;
|
|
iEnd = iStart;
|
|
iStart = temp;
|
|
}
|
|
|
|
//
|
|
// We don't want to loop through items that don't exist.
|
|
//
|
|
iEnd = min(plb->cMac, iEnd);
|
|
iStart = max(iStart, 0);
|
|
if (iStart > iEnd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// iEnd could be equal to MAXINT which is why we test temp and iEnd
|
|
// as DWORDs.
|
|
//
|
|
for (temp = iStart; temp <= (DWORD)iEnd; temp++)
|
|
{
|
|
if (ListBox_IsSelected(plb, temp, SELONLY) != fnewstate)
|
|
{
|
|
ListBox_SetSelected(plb, temp, fnewstate, HILITEANDSEL);
|
|
ListBox_GetItemRectHandler(plb, temp, &rc);
|
|
|
|
ListBox_InvalidateRect(plb, (LPRECT)&rc, FALSE);
|
|
}
|
|
}
|
|
|
|
ASSERT(plb->wMultiple);
|
|
|
|
ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
int ListBox_SetCurSelHandler(PLBIV plb, int iSel)
|
|
{
|
|
|
|
if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac))
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
|
|
if (plb->iSel != -1)
|
|
{
|
|
//
|
|
// This prevents scrolling when iSel == -1
|
|
//
|
|
if (iSel != -1)
|
|
{
|
|
ListBox_InsureVisible(plb, iSel, FALSE);
|
|
}
|
|
|
|
//
|
|
// Turn off old selection
|
|
//
|
|
ListBox_InvertItem(plb, plb->iSel, FALSE);
|
|
}
|
|
|
|
if (iSel != -1)
|
|
{
|
|
ListBox_InsureVisible(plb, iSel, FALSE);
|
|
plb->iSelBase = plb->iSel = iSel;
|
|
|
|
//
|
|
// Highlight new selection
|
|
//
|
|
ListBox_InvertItem(plb, plb->iSel, TRUE);
|
|
}
|
|
else
|
|
{
|
|
plb->iSel = -1;
|
|
|
|
if (plb->cMac)
|
|
{
|
|
plb->iSelBase = min(plb->iSelBase, plb->cMac-1);
|
|
}
|
|
else
|
|
{
|
|
plb->iSelBase = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send both focus and selection events
|
|
//
|
|
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
|
|
{
|
|
ListBox_Event(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
|
|
ListBox_Event(plb, EVENT_OBJECT_SELECTION, plb->iSel);
|
|
}
|
|
|
|
ListBox_SetCaret(plb, TRUE);
|
|
|
|
return plb->iSel;
|
|
}
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_SetItemDataHandler
|
|
//
|
|
// Makes the item at index contain the data given.
|
|
//
|
|
int ListBox_SetItemDataHandler(PLBIV plb, int index, LONG_PTR data)
|
|
{
|
|
LPSTR lpItemText;
|
|
|
|
//
|
|
// v-ronaar: fix bug #25865, don't allow negative indices!
|
|
//
|
|
if ((index != -1) && ((UINT) index >= (UINT) plb->cMac))
|
|
{
|
|
TraceMsg(TF_STANDARD, "ListBox_SetItemDataHandler with invalid index %x", index);
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
//
|
|
// No-data listboxes just ignore all LB_SETITEMDATA calls
|
|
//
|
|
if (!plb->fHasData)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
lpItemText = (LPSTR)plb->rgpch;
|
|
|
|
if (index == -1)
|
|
{
|
|
//
|
|
// index == -1 means set the data to all the items
|
|
//
|
|
if (plb->fHasStrings)
|
|
{
|
|
for (index = 0; index < plb->cMac; index++)
|
|
{
|
|
((lpLBItem)lpItemText)->itemData = data;
|
|
lpItemText += sizeof(LBItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (index = 0; index < plb->cMac; index++)
|
|
{
|
|
((lpLBODItem)lpItemText)->itemData = data;
|
|
lpItemText += sizeof(LBODItem);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (plb->fHasStrings)
|
|
{
|
|
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBItem)));
|
|
((lpLBItem)lpItemText)->itemData = data;
|
|
}
|
|
else
|
|
{
|
|
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBODItem)));
|
|
((lpLBODItem)lpItemText)->itemData = data;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_CheckRedraw(PLBIV plb, BOOL fConditional, INT sItem)
|
|
{
|
|
if (fConditional && plb->cMac &&
|
|
(sItem > (plb->iTop + ListBox_CItemInWindow(plb, TRUE))))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Don't do anything if the parent is not visible.
|
|
//
|
|
ListBox_InvalidateRect(plb, (LPRECT)NULL, TRUE);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
void ListBox_CaretDestroy(PLBIV plb)
|
|
{
|
|
//
|
|
// We're losing the focus. Act like up clicks are happening so we release
|
|
// capture, set the current selection, notify the parent, etc.
|
|
//
|
|
if (plb->fCaptured)
|
|
{
|
|
//
|
|
// If we have the capture and we lost the focus, that means we already
|
|
// changed the selection and we have to notify also the parent about
|
|
// this. So we need to add also the LBUP_SUCCESS flag in this case.
|
|
//
|
|
ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
|
|
(plb->fMouseDown ? LBUP_SUCCESS : 0));
|
|
}
|
|
|
|
if (plb->fAddSelMode)
|
|
{
|
|
//
|
|
// Switch off the Caret blinking
|
|
//
|
|
KillTimer(plb->hwnd, IDSYS_CARET);
|
|
|
|
//
|
|
// Make sure the caret goes away
|
|
//
|
|
ListBox_SetCaret(plb, FALSE);
|
|
plb->fAddSelMode = FALSE;
|
|
}
|
|
|
|
plb->fCaret = FALSE;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
LONG ListBox_SetSelHandler(PLBIV plb, BOOL fSelect, INT iSel)
|
|
{
|
|
INT sItem;
|
|
RECT rc;
|
|
UINT uEvent = 0;
|
|
|
|
//
|
|
// Bug 17656. WinZip's accelerator key for 'DeSelect All' sends a LB_SETSEL
|
|
// message with lparam = 0x0000ffff instead of 0xffffffff(-1). If iSel
|
|
// is equal to 0x0000ffff and there are less than 0xffff elements in the
|
|
// list we set iSel equal to 0xffffffff.
|
|
//
|
|
if ((iSel == (UINT)0xffff) && (iSel >= plb->cMac))
|
|
{
|
|
iSel = -1;
|
|
|
|
TraceMsg(TF_STANDARD, "Sign extending iSel=0xffff to 0xffffffff");
|
|
}
|
|
|
|
|
|
if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac))
|
|
{
|
|
TraceMsg(TF_STANDARD, "Invalid index");
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
ListBox_SetCaret(plb, FALSE);
|
|
|
|
if (iSel == -1)
|
|
{
|
|
//
|
|
// Set/clear selection from all items if -1
|
|
//
|
|
for (sItem = 0; sItem < plb->cMac; sItem++)
|
|
{
|
|
if (ListBox_IsSelected(plb, sItem, SELONLY) != fSelect)
|
|
{
|
|
ListBox_SetSelected(plb, sItem, fSelect, HILITEANDSEL);
|
|
|
|
if (ListBox_GetItemRectHandler(plb, sItem, &rc))
|
|
{
|
|
ListBox_InvalidateRect(plb, &rc, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
ListBox_SetCaret(plb, TRUE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
}
|
|
else
|
|
{
|
|
if (fSelect)
|
|
{
|
|
//
|
|
// Check if the item if fully hidden and scroll it into view if it
|
|
// is. Note that we don't want to scroll partially visible items
|
|
// into full view because this breaks the shell...
|
|
//
|
|
ListBox_InsureVisible(plb, iSel, TRUE);
|
|
plb->iSelBase = plb->iSel = iSel;
|
|
|
|
plb->iMouseDown = plb->iLastMouseMove = iSel;
|
|
uEvent = EVENT_OBJECT_FOCUS;
|
|
}
|
|
else
|
|
{
|
|
uEvent = EVENT_OBJECT_SELECTIONREMOVE;
|
|
}
|
|
|
|
ListBox_SetSelected(plb, iSel, fSelect, HILITEANDSEL);
|
|
|
|
//
|
|
// Note that we set the caret on bit directly so that we avoid flicker
|
|
// when drawing this item. ie. We turn on the caret, redraw the item and
|
|
// turn it back on again.
|
|
//
|
|
if (!fSelect && plb->iSelBase != iSel)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
else if (plb->fCaret)
|
|
{
|
|
plb->fCaretOn = TRUE;
|
|
}
|
|
|
|
if (ListBox_GetItemRectHandler(plb, iSel, &rc))
|
|
{
|
|
ListBox_InvalidateRect(plb, &rc, FALSE);
|
|
}
|
|
}
|
|
|
|
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
|
|
{
|
|
if (uEvent == EVENT_OBJECT_FOCUS)
|
|
{
|
|
ListBox_Event(plb, uEvent, plb->iSelBase);
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
|
|
ListBox_Event(plb, uEvent, iSel);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_FillDrawItem
|
|
//
|
|
// This fills the draw item struct with some constant data for the given
|
|
// item. The caller will only have to modify a small part of this data
|
|
// for specific needs.
|
|
//
|
|
void ListBox_FillDrawItem(PLBIV plb, INT item, UINT itemAction, UINT itemState, LPRECT lprect)
|
|
{
|
|
DRAWITEMSTRUCT dis;
|
|
|
|
//
|
|
// Fill the DRAWITEMSTRUCT with the unchanging constants
|
|
//
|
|
|
|
dis.CtlType = ODT_LISTBOX;
|
|
dis.CtlID = GetDlgCtrlID(plb->hwnd);
|
|
|
|
//
|
|
// Use -1 if an invalid item number is being used. This is so that the app
|
|
// can detect if it should draw the caret (which indicates the lb has the
|
|
// focus) in an empty listbox
|
|
//
|
|
dis.itemID = (UINT)(item < plb->cMac ? item : -1);
|
|
dis.itemAction = itemAction;
|
|
dis.hwndItem = plb->hwnd;
|
|
dis.hDC = plb->hdc;
|
|
dis.itemState = itemState |
|
|
(UINT)((GET_STYLE(plb)&WS_DISABLED) ? ODS_DISABLED : 0);
|
|
|
|
if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN))
|
|
{
|
|
dis.itemState |= ODS_NOFOCUSRECT;
|
|
}
|
|
|
|
if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIACCELHIDDEN))
|
|
{
|
|
dis.itemState |= ODS_NOACCEL;
|
|
}
|
|
|
|
//
|
|
// Set the app supplied data
|
|
//
|
|
if (!plb->cMac || !plb->fHasData)
|
|
{
|
|
//
|
|
// If no strings or no items, just use 0 for data. This is so that we
|
|
// can display a caret when there are no items in the listbox.
|
|
//
|
|
// Lazy-eval listboxes of course have no data to pass - only itemID.
|
|
//
|
|
dis.itemData = 0L;
|
|
}
|
|
else
|
|
{
|
|
dis.itemData = ListBox_GetItemDataHandler(plb, item);
|
|
}
|
|
|
|
CopyRect(&dis.rcItem, lprect);
|
|
|
|
//
|
|
// Set the window origin to the horizontal scroll position. This is so that
|
|
// text can always be drawn at 0,0 and the view region will only start at
|
|
// the horizontal scroll offset. We pass this as wParam
|
|
//
|
|
|
|
SendMessage(plb->hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_BlockHilite
|
|
//
|
|
// In Extended selection mode for multiselection listboxes, when
|
|
// mouse is draged to a new position, the range being marked should be
|
|
// properly sized(parts of which will be highlighted/dehighlighted).
|
|
// NOTE: This routine assumes that iSelFromPt and LasMouseMove are not
|
|
// equal because only in that case this needs to be called;
|
|
// NOTE: This routine calculates the region whose display attribute is to
|
|
// be changed in an optimised way. Instead of de-highlighting the
|
|
// the old range completely and highlight the new range, it omits
|
|
// the regions that overlap and repaints only the non-pverlapping
|
|
// area.
|
|
// fKeyBoard = TRUE if this is called for Keyboard interface
|
|
// FALSE if called from Mouse interface routines
|
|
//
|
|
void ListBox_BlockHilite(PLBIV plb, INT iSelFromPt, BOOL fKeyBoard)
|
|
{
|
|
INT sCurPosOffset;
|
|
INT sLastPosOffset;
|
|
INT sHiliteOrSel;
|
|
BOOL fUseSelStatus;
|
|
BOOL DeHiliteStatus;
|
|
|
|
if (fKeyBoard)
|
|
{
|
|
//
|
|
// Set both Hilite and Selection states
|
|
//
|
|
sHiliteOrSel = HILITEANDSEL;
|
|
|
|
//
|
|
// Do not use the Selection state while de-hiliting
|
|
//
|
|
fUseSelStatus = FALSE;
|
|
DeHiliteStatus = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Set/Reset only the Hilite state
|
|
//
|
|
sHiliteOrSel = HILITEONLY;
|
|
|
|
//
|
|
// Use the selection state for de-hilighting
|
|
//
|
|
fUseSelStatus = TRUE;
|
|
DeHiliteStatus = plb->fNewItemState;
|
|
}
|
|
|
|
//
|
|
// The idea of the routine is to :
|
|
// 1. De-hilite the old range (iMouseDown to iLastMouseDown) and
|
|
// 2. Hilite the new range (iMouseDwon to iSelFromPt)
|
|
//
|
|
|
|
//
|
|
// Offset of current mouse position from the anchor point
|
|
//
|
|
sCurPosOffset = plb->iMouseDown - iSelFromPt;
|
|
|
|
//
|
|
// Offset of last mouse position from the anchor point
|
|
//
|
|
sLastPosOffset = plb->iMouseDown - plb->iLastMouseMove;
|
|
|
|
//
|
|
// Check if both current position and last position lie on the same
|
|
// side of the anchor point.
|
|
//
|
|
if ((sCurPosOffset * sLastPosOffset) >= 0)
|
|
{
|
|
//
|
|
// Yes they are on the same side; So, highlight/dehighlight only
|
|
// the difference.
|
|
//
|
|
if (abs(sCurPosOffset) > abs(sLastPosOffset))
|
|
{
|
|
ListBox_AlterHilite(plb, plb->iLastMouseMove, iSelFromPt,
|
|
plb->fNewItemState, sHiliteOrSel, FALSE);
|
|
}
|
|
else
|
|
{
|
|
ListBox_AlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus,
|
|
sHiliteOrSel, fUseSelStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
|
|
DeHiliteStatus, sHiliteOrSel, fUseSelStatus);
|
|
|
|
ListBox_AlterHilite(plb, plb->iMouseDown, iSelFromPt,
|
|
plb->fNewItemState, sHiliteOrSel, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------//
|
|
//
|
|
// ListBox_AlterHilite
|
|
//
|
|
// Changes the hilite state of (i..j] (ie. excludes i, includes j in case
|
|
// you've forgotten this notation) to fHilite. It inverts this changes
|
|
// the hilite state.
|
|
//
|
|
// OpFlags:
|
|
// HILITEONLY Only change the display state of the items
|
|
// SELONLY Only Change the selection state of the items
|
|
// HILITEANDSELECT Do both.
|
|
//
|
|
// fHilite:
|
|
// HILITE/TRUE
|
|
// DEHILITE/FALSE
|
|
//
|
|
// fSelStatus:
|
|
// if TRUE, use the selection state of the item to hilite/dehilite
|
|
// if FALSE, use the fHilite parameter to hilite/dehilite
|
|
//
|
|
void ListBox_AlterHilite(PLBIV plb, INT i, INT j, BOOL fHilite, INT OpFlags, BOOL fSelStatus)
|
|
{
|
|
INT low;
|
|
INT high;
|
|
INT sLastInWindow;
|
|
BOOL fCaretOn;
|
|
BOOL fSelected;
|
|
|
|
sLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE);
|
|
sLastInWindow = min(sLastInWindow, plb->cMac - 1);
|
|
high = max(i, j) + 1;
|
|
|
|
if (fCaretOn = plb->fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, FALSE);
|
|
}
|
|
|
|
for (low = min(i, j); low < high; low++)
|
|
{
|
|
if (low != i)
|
|
{
|
|
if (OpFlags & HILITEONLY)
|
|
{
|
|
if (fSelStatus)
|
|
{
|
|
fSelected = ListBox_IsSelected(plb, low, SELONLY);
|
|
}
|
|
else
|
|
{
|
|
fSelected = fHilite;
|
|
}
|
|
|
|
if (ListBox_IsSelected(plb, low, HILITEONLY) != fSelected)
|
|
{
|
|
if (plb->iTop <= low && low <= sLastInWindow)
|
|
{
|
|
//
|
|
// Invert the item only if it is visible
|
|
//
|
|
ListBox_InvertItem(plb, low, fSelected);
|
|
}
|
|
|
|
ListBox_SetSelected(plb, low, fSelected, HILITEONLY);
|
|
}
|
|
}
|
|
|
|
if (OpFlags & SELONLY)
|
|
{
|
|
ListBox_SetSelected(plb, low, fHilite, SELONLY);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fCaretOn)
|
|
{
|
|
ListBox_SetCaret(plb, TRUE);
|
|
}
|
|
}
|