windows-nt/Source/XPSP1/NT/windows/richedit/re30/lbhost.cpp
2020-09-26 16:20:57 +08:00

5049 lines
124 KiB
C++

/*
* @doc INTERNAL
*
* @module LBHOST.CPP -- Text Host for CreateWindow() Rich Edit
* List Box Control |
* Implements CLstBxWinHost message
*
* Original Author:
* Jerry Kim
*
* History: <nl>
* 12/15/97 - v-jerrki Created
*
* Set tabs every four (4) columns
*
* Copyright (c) 1997-1998 Microsoft Corporation. All rights reserved.
*/
#include "_common.h"
#include "_host.h"
#include "imm.h"
#include "_format.h"
#include "_edit.h"
#include "_cfpf.h"
#include "_cbhost.h"
ASSERTDATA
#ifdef DEBUG
const UINT db_rgLBUnsupportedStyle[] = {
LBS_MULTICOLUMN,
LBS_NODATA,
LBS_NOREDRAW,
LBS_NOSEL,
LBS_OWNERDRAWVARIABLE,
LBS_WANTKEYBOARDINPUT,
0
};
const UINT db_rgLBUnsupportedMsg[] = {
LB_GETHORIZONTALEXTENT,
LB_GETLOCALE,
LB_SETLOCALE,
LB_GETHORIZONTALEXTENT,
LB_INITSTORAGE,
LB_ITEMFROMPOINT,
LB_SETANCHORINDEX,
LB_SETHORIZONTALEXTENT,
LB_SETCOLUMNWIDTH,
LB_ADDFILE,
LB_DIR,
EM_GETLIMITTEXT,
EM_POSFROMCHAR,
EM_CHARFROMPOS,
EM_SCROLLCARET,
EM_CANPASTE,
EM_DISPLAYBAND,
EM_EXGETSEL,
EM_EXLIMITTEXT,
EM_EXLINEFROMCHAR,
EM_EXSETSEL,
EM_FINDTEXT,
EM_FORMATRANGE,
EM_GETEVENTMASK,
EM_GETOLEINTERFACE,
EM_GETPARAFORMAT,
EM_GETSELTEXT,
EM_HIDESELECTION,
EM_PASTESPECIAL,
EM_REQUESTRESIZE,
EM_SELECTIONTYPE,
EM_SETBKGNDCOLOR,
EM_SETEVENTMASK,
EM_SETOLECALLBACK,
EM_SETTARGETDEVICE,
EM_STREAMIN,
EM_STREAMOUT,
EM_GETTEXTRANGE,
EM_FINDWORDBREAK,
EM_SETOPTIONS,
EM_GETOPTIONS,
EM_FINDTEXTEX,
#ifdef _WIN32
EM_GETWORDBREAKPROCEX,
EM_SETWORDBREAKPROCEX,
#endif
/* Richedit v2.0 messages */
EM_SETUNDOLIMIT,
EM_REDO,
EM_CANREDO,
EM_GETUNDONAME,
EM_GETREDONAME,
EM_STOPGROUPTYPING,
EM_SETTEXTMODE,
EM_GETTEXTMODE,
EM_AUTOURLDETECT,
EM_GETAUTOURLDETECT,
EM_GETTEXTEX,
EM_GETTEXTLENGTHEX,
EM_SHOWSCROLLBAR,
/* Far East specific messages */
EM_SETPUNCTUATION,
EM_GETPUNCTUATION,
EM_SETWORDWRAPMODE,
EM_GETWORDWRAPMODE,
EM_SETIMECOLOR,
EM_GETIMECOLOR,
EM_SETIMEOPTIONS,
EM_GETIMEOPTIONS,
EM_CONVPOSITION,
EM_SETLANGOPTIONS,
EM_GETLANGOPTIONS,
EM_GETIMECOMPMODE,
EM_FINDTEXTW,
EM_FINDTEXTEXW,
/* RE3.0 FE messages */
EM_RECONVERSION,
EM_SETIMEMODEBIAS,
EM_GETIMEMODEBIAS,
/* Extended edit style specific messages */
0
};
// Checks if the style is in the passed in array
BOOL LBCheckStyle(UINT msg, const UINT* rg)
{
for (int i = 0; rg[i]; i++)
if (rg[i] & msg)
{
AssertSz(FALSE, "Unsupported style recieved");
return TRUE;
}
return FALSE;
}
// Checks if the msg is in the passed in array
BOOL LBCheckMessage(UINT msg, const UINT* rg)
{
for (int i = 0; rg[i]; i++)
if (rg[i] == msg)
{
AssertSz(FALSE, "Unsupported message recieved");
return TRUE;
}
return FALSE;
}
#define CHECKSTYLE(msg) if (LBCheckStyle(msg, db_rgLBUnsupportedStyle)) Assert(FALSE && "Unsupported Style")
#define CHECKMESSAGE(msg) if (LBCheckMessage(msg, db_rgLBUnsupportedMsg)) Assert(FALSE && "Unsupported Message")
#else
#define CHECKSTYLE(msg)
#define CHECKMESSAGE(msg)
#endif
// internal listbox messages
#define LB_KEYDOWN WM_USER+1
// UNDONE:
// Should this go into _w32sys.h??
#ifndef CSTR_LESS_THAN
//
// Compare String Return Values.
//
#define CSTR_LESS_THAN 1 // string 1 less than string 2
#define CSTR_EQUAL 2 // string 1 equal to string 2
#define CSTR_GREATER_THAN 3 // string 1 greater than string
#endif
// UNDONE : LOCALIZATION
// 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 */
// Used for Listbox notifications
#define LBNOTIFY_CANCEL 1
#define LBNOTIFY_SELCHANGE 2
#define LBNOTIFY_DBLCLK 4
// Used for LBSetSelection
#define LBSEL_SELECT 1
#define LBSEL_NEWANCHOR 2
#define LBSEL_NEWCURSOR 4
#define LBSEL_RESET 8
#define LBSEL_HIGHLIGHTONLY 16
#define LBSEL_DEFAULT (LBSEL_SELECT | LBSEL_NEWANCHOR | LBSEL_NEWCURSOR | LBSEL_RESET)
// Used for keyboard and mouse messages
#define LBKEY_NONE 0
#define LBKEY_SHIFT 1
#define LBKEY_CONTROL 2
#define LBKEY_SHIFTCONTROL 3
extern const TCHAR szCR[];
// Timer id when mouse is captured
#define ID_LB_CAPTURE 28988
#define ID_LB_CAPTURE_DEFAULT 250
// Timer id when type search is required
#define ID_LB_SEARCH 28989
#define ID_LB_SEARCH_DEFAULT 750 //.75 seconds is the value for winnt
// Size of allocated string
#define LBSEARCH_MAXSIZE 256
// Helper function in edit.cpp
LONG GetECDefaultHeightAndWidth(
ITextServices *pts,
HDC hdc,
LONG lZoomNumerator,
LONG lZoomDenominator,
LONG yPixelsPerInch,
LONG *pxAveWidth,
LONG *pxOverhang,
LONG *pxUnderhang);
// helper function for compare string. This function checks for null strings
// because CStrIn doesn't like initializing string with zero length
int CompareStringWrapper(
LCID Locale, // locale identifier
DWORD dwCmpFlags, // comparison-style options
LPCWSTR lpString1, // pointer to first string
int cch1, // size, in bytes or characters, of first string
LPCWSTR lpString2, // pointer to second string
int cch2 // size, in bytes or characters, of second string
)
{
// check if one of the 2 strings is 0-length if so then
// no need to proceed the one with the 0-length is the less
if (!cch1 || !cch2)
{
if (cch1 < cch2)
return CSTR_LESS_THAN;
else if (cch1 > cch2)
return CSTR_GREATER_THAN;
return CSTR_EQUAL;
}
return CompareString(Locale, dwCmpFlags, lpString1, cch1, lpString2, cch2);
}
template<class CLbData> CLbData
CDynamicArray<CLbData>::_sDummy = {0, 0};
//////////////////////////// System Window Procs ////////////////////////////
/*
* RichListBoxWndProc (hwnd, msg, wparam, lparam)
*
* @mfunc
* Handle window messages pertinent to the host and pass others on to
* text services.
* #rdesc
* LRESULT = (code processed) ? 0 : 1
*/
LRESULT CALLBACK RichListBoxWndProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "RichListBoxWndProc");
LRESULT lres = 0;
HRESULT hr;
CLstBxWinHost *phost = (CLstBxWinHost *) GetWindowLongPtr(hwnd, ibPed);
#ifdef DEBUG
Tracef(TRCSEVINFO, "hwnd %lx, msg %lx, wparam %lx, lparam %lx", hwnd, msg, wparam, lparam);
#endif // DEBUG
switch(msg)
{
case WM_NCCREATE:
return CLstBxWinHost::OnNCCreate(hwnd, (CREATESTRUCT *)lparam);
case WM_CREATE:
// We may be on a system with no WM_NCCREATE (e.g. WINCE)
if (!phost)
{
(void) CLstBxWinHost::OnNCCreate(hwnd, (CREATESTRUCT *) lparam);
phost = (CLstBxWinHost *) GetWindowLongPtr(hwnd, ibPed);
}
break;
case WM_DESTROY:
if(phost)
CLstBxWinHost::OnNCDestroy(phost);
return 0;
}
if (!phost)
return ::DefWindowProc(hwnd, msg, wparam, lparam);
// in certain out-of-memory situations, clients may try to re-enter us
// with calls. Just bail on the call if we don't have a text services
// pointer.
if(!phost->_pserv)
return 0;
// stabilize ourselves
phost->AddRef();
CHECKMESSAGE(msg);
long nTemp = 0;
switch(msg)
{
///////////////////////Painting. Messages///////////////////////////////
case WM_NCPAINT:
lres = ::DefWindowProc(hwnd, msg, wparam, lparam);
phost->OnSysColorChange();
if(phost->TxGetEffects() == TXTEFFECT_SUNKEN && dwMajorVersion < VERS4 &&
phost->_fLstType != CLstBxWinHost::kCombo)
{
HDC hdc = GetDC(hwnd);
if(hdc)
{
phost->DrawSunkenBorder(hwnd, hdc);
ReleaseDC(hwnd, hdc);
}
}
break;
case WM_PRINTCLIENT:
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT rc;
HPALETTE hpalOld = NULL;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rcClient;
// Since we are using the CS_PARENTDC style, make sure
// the clip region is limited to our client window.
GetClientRect(hwnd, &rcClient);
// Set up the palette for drawing our data
if(phost->_hpal)
{
hpalOld = SelectPalette(hdc, phost->_hpal, TRUE);
RealizePalette(hdc);
}
SaveDC(hdc);
IntersectClipRect(hdc, rcClient.left, rcClient.top, rcClient.right,
rcClient.bottom);
if (!phost->_fOwnerDraw)
{
phost->_pserv->TxDraw(
DVASPECT_CONTENT, // Draw Aspect
-1, // Lindex
NULL, // Info for drawing optimazation
NULL, // target device information
hdc, // Draw device HDC
NULL, // Target device HDC
(const RECTL *) &rcClient,// Bounding client rectangle
NULL, // Clipping rectangle for metafiles
&ps.rcPaint, // Update rectangle
NULL, // Call back function
NULL, // Call back parameter
TXTVIEW_ACTIVE); // What view - the active one!
// Restore palette if there is one
#ifndef PEGASUS
if(hpalOld)
SelectPalette(hdc, hpalOld, TRUE);
#endif
if(phost->TxGetEffects() == TXTEFFECT_SUNKEN && dwMajorVersion < VERS4 &&
phost->_fLstType != CLstBxWinHost::kCombo)
phost->DrawSunkenBorder(hwnd, hdc);
}
else
{
// Owner draw
int nViewsize = phost->GetViewSize();
int nCount = phost->GetCount();
int nTopidx = phost->GetTopIndex();
// notify each visible item and then the one which has the focus
int nBottom = min(nCount, nTopidx + nViewsize);
if (nBottom >= nCount || !phost->IsItemViewable(nBottom))
nBottom--;
for (int i = nTopidx; i <= nBottom; i++)
{
// get Rect of region and see if it intersects
phost->LbGetItemRect(i, &rc);
if (IntersectRect(&rc, &rc, &ps.rcPaint))
{
//first erase the background and notify parent to draw
FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
phost->LbDrawItemNotify(hdc, i, ODA_DRAWENTIRE, phost->IsSelected(i) ? ODS_SELECTED : 0);
}
}
// Now draw onto the area where drawing may not have been done or erased
int nDiff = nCount - nTopidx;
if (nDiff < nViewsize ||
(phost->_fNoIntegralHeight && nDiff == nViewsize))
{
rc = rcClient;
if (nDiff < 0)
nDiff *= -1; // lets be positive
rc.top = nDiff * phost->GetItemHeight();
if (IntersectRect(&rc, &rc, &ps.rcPaint))
FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
}
#ifndef PEGASUS
if(hpalOld)
SelectPalette(hdc, hpalOld, TRUE);
#endif
}
RestoreDC(hdc, -1);
// Check if we need to draw the focus rect by checking if the focus rect intersects
// the painting rect
phost->LbGetItemRect(phost->GetCursor(), &rc);
// NOTE: Bug #5431
// This bug could be fixed by replacing the hDC to NULL
// The hdc can be clipped from BeginPaint API. So just pass in NULL
// when drawing focus rect
phost->SetCursor(hdc, phost->GetCursor(), FALSE);
EndPaint(hwnd, &ps);
}
break;
/////////////////////////Mouse Messages/////////////////////////////////
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_MBUTTONDOWN:
break;
case WM_LBUTTONDBLCLK:
phost->_fDblClick = 1;
/* Fall through case */
case WM_LBUTTONDOWN:
if (!phost->_fFocus)
SetFocus(hwnd);
phost->OnLButtonDown(wparam, lparam);
break;
case WM_MOUSEMOVE:
if (!phost->GetCapture())
break;
phost->OnMouseMove(wparam, lparam);
break;
case WM_LBUTTONUP:
if (!phost->GetCapture())
break;
phost->OnLButtonUp(wparam, lparam, LBN_SELCHANGE);
break;
case WM_MOUSEWHEEL:
if (wparam & (MK_SHIFT | MK_CONTROL))
goto defwndproc;
lres = phost->OnMouseWheel(wparam, lparam);
break;
///////////////////////KeyBoard Messages////////////////////////////////
case WM_KEYDOWN:
phost->OnKeyDown(LOWORD(wparam), lparam, 0);
break;
case WM_CHAR:
if (W32->OnWin9x() || phost->_fANSIwindow)
{
CW32System::WM_CHAR_INFO wmci;
wmci._fAccumulate = phost->_fAccumulateDBC != 0;
W32->AnsiFilter( msg, wparam, lparam, (void *) &wmci );
if (wmci._fLeadByte)
{
phost->_fAccumulateDBC = TRUE;
phost->_chLeadByte = wparam << 8;
goto Exit; // Wait for trail byte
}
else if (wmci._fTrailByte)
{
// UNDONE:
// Need to see what we should do in WM_IME_CHAR
wparam = phost->_chLeadByte | wparam;
phost->_fAccumulateDBC = FALSE;
phost->_chLeadByte = 0;
msg = WM_IME_CHAR;
goto serv;
}
else if (wmci._fIMEChar)
{
msg = WM_IME_CHAR;
goto serv;
}
else if (wmci._fIMEChar)
{
msg = WM_IME_CHAR;
goto serv;
}
}
phost->OnChar(LOWORD(wparam), lparam);
break;
case WM_TIMER:
if (phost->OnTimer(wparam, lparam))
goto serv;
break;
case LBCB_TRACKING:
phost->OnCBTracking(wparam, lparam);
break;
//UNDONE:
// Messages should be ordered from most often called --> least often called
//
case LB_GETITEMRECT:
Assert(lparam);
lres = -1;
if (((wparam < (unsigned)phost->GetCount()) &&
phost->IsItemViewable((long)wparam)) || wparam == (unsigned int)-1 ||
wparam == 0 && phost->GetCount() == 0)
lres = phost->LbGetItemRect(wparam, (RECT*)lparam);
break;
///////////////////////ListBox Messages/////////////////////////////////
case LB_GETITEMDATA:
if ((unsigned)phost->GetCount() <= wparam)
lres = LB_ERR;
else
lres = phost->GetData(wparam);
break;
case LB_SETITEMDATA:
lres = LB_ERR;
if ((int)wparam >= -1 && (int)wparam < phost->GetCount())
{
// if index is -1 this means all the dataItems are set
// to the value
lres = 1;
if (wparam == -1)
phost->LbSetItemData(0, phost->GetCount() - 1, lparam);
else
phost->LbSetItemData(wparam, wparam, lparam);
}
break;
case LB_GETSELCOUNT:
if (lparam != NULL || wparam != 0)
{
lres = LB_ERR;
break;
}
wparam = phost->GetCount();
// FALL through case
case LB_GETSELITEMS:
// retrieves all the selected items in the list
lres = LB_ERR;
if (!phost->IsSingleSelection())
{
int j = 0;
int nMin = min(phost->GetCount(), (int)wparam);
for (int i = 0; i < nMin; i++)
if (phost->IsSelected(i))
{
if (lparam)
((int*)lparam)[j] = i;
j++;
}
lres = j;
}
break;
case LB_GETSEL:
// return the select state of the passed in index
lres = LB_ERR;
if ((int)wparam >= 0 && (int)wparam < phost->GetCount())
lres = phost->IsSelected((long)wparam);
break;
case LB_GETCURSEL:
// Get the current selection
lres = LB_ERR;
if (!phost->IsSingleSelection())
lres = phost->GetCursor();
else
{
if (phost->IsSelected(phost->GetCursor()))
lres = phost->GetCursor();
}
break;
case LB_GETTEXTLEN:
// Retieves the text at the requested index
lres = LB_ERR;
if (wparam < (unsigned)phost->GetCount())
lres = phost->GetString(wparam, (PWCHAR)NULL);
break;
case LB_GETTEXT:
// Retieves the text at the requested index
lres = LB_ERR;
if ((int)lparam != NULL && (int)wparam >= 0 && (int)wparam < phost->GetCount())
lres = phost->GetString(wparam, (PWCHAR)lparam);
break;
case LB_RESETCONTENT:
// Reset the contents
lres = phost->LbDeleteString(0, phost->GetCount() - 1);
break;
case LB_DELETESTRING:
// Delete requested item
lres = phost->LbDeleteString(wparam, wparam);
break;
case LB_ADDSTRING:
lres = phost->LbInsertString((phost->_fSort) ? -2 : -1, (LPCTSTR)lparam);
break;
case LB_INSERTSTRING:
lres = LB_ERR;
if (wparam <= (unsigned long)phost->GetCount() || (signed int)wparam == -1 || wparam == 0)
lres = phost->LbInsertString(wparam, (LPCTSTR)lparam);
break;
case LB_GETCOUNT:
// retrieve the count
lres = phost->GetCount();
break;
case LB_GETTOPINDEX:
// Just return the top index
lres = phost->GetTopIndex();
break;
case LB_GETCARETINDEX:
lres = phost->GetCursor();
break;
case LB_GETANCHORINDEX:
lres = phost->GetAnchor();
break;
case LB_FINDSTRINGEXACT:
// For NT compatibility
wparam++;
// Find and select the item matching the string text
if ((int)wparam >= phost->GetCount() || (int)wparam < 0)
wparam = 0;
lres = phost->LbFindString(wparam, (LPCTSTR)lparam, TRUE);
if (0 <= lres)
break;
lres = LB_ERR;
break;
case LB_FINDSTRING:
// For NT compatibility
wparam++;
// Find and select the item matching the string text
if (wparam >= (unsigned)phost->GetCount())
wparam = 0;
lres = phost->LbFindString(wparam, (LPCTSTR)lparam, FALSE);
if (0 > lres)
lres = LB_ERR;
break;
case LB_SELECTSTRING:
if (phost->IsSingleSelection())
{
// For NT compatibility
wparam++;
// Find and select the item matching the string text
if ((int)wparam >= phost->GetCount() || (int)wparam < 0)
wparam = 0;
lres = phost->LbFindString(wparam, (LPCTSTR)lparam, FALSE);
if (0 <= lres)
{
// bug fix #5260 - need to move to selected item first
// Unselect last item and select new one
Assert(lres >= 0 && lres < phost->GetCount());
if (phost->LbShowIndex(lres, FALSE) && phost->LbSetSelection(lres, lres, LBSEL_DEFAULT, lres, lres))
{
#ifndef NOACCESSIBILITY
phost->_dwWinEvent = EVENT_OBJECT_FOCUS;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
phost->_dwWinEvent = EVENT_OBJECT_SELECTION;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
#endif
break;
}
}
}
// If failed then let it fall through to the LB_ERR
lres = LB_ERR;
break;
case LB_SETSEL:
// We only update the GetAnchor() and _nCursor if we are selecting an item
if (!phost->IsSingleSelection())
{
// We need to return zero to mimic system listbox
if (!phost->GetCount())
break;
//bug fix #4265
int nStart = lparam;
int nEnd = lparam;
int nAnCur = lparam;
if (lparam == (unsigned long)-1)
{
nAnCur = phost->GetCursor();
nStart = 0;
nEnd = phost->GetCount() - 1;
}
if (phost->LbSetSelection(nStart, nEnd, (BOOL)wparam ?
LBSEL_SELECT | LBSEL_NEWANCHOR | LBSEL_NEWCURSOR : 0, nAnCur, nAnCur))
{
#ifndef NOACCESSIBILITY
if (lparam == (unsigned long)-1)
{
phost->_dwWinEvent = EVENT_OBJECT_SELECTIONWITHIN;
}
else if (wparam)
{
phost->_dwWinEvent = EVENT_OBJECT_FOCUS;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
phost->_dwWinEvent = EVENT_OBJECT_SELECTION;
}
else
{
phost->_nAccessibleIdx = lparam + 1;
phost->_dwWinEvent = EVENT_OBJECT_SELECTIONREMOVE;
}
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
#endif
break;
}
}
// We only get here if error occurs or list box is a singel sel Listbox
lres = LB_ERR;
break;
case LB_SELITEMRANGEEX:
// For this message we need to munge the messages a little bit so it
// conforms with LB_SETITEMRANGE
if ((int)lparam > (int)wparam)
{
nTemp = MAKELONG(wparam, lparam);
wparam = 1;
lparam = nTemp;
}
else
{
nTemp = MAKELONG(lparam, wparam);
wparam = 0;
lparam = nTemp;
}
/* Fall through case */
case LB_SELITEMRANGE:
// We have to make sure the range is valid
if (LOWORD(lparam) >= phost->GetCount())
{
if (HIWORD(lparam) >= phost->GetCount())
//nothing to do so exit without error
break;
lparam = MAKELONG(HIWORD(lparam), phost->GetCount() - 1);
}
else if (HIWORD(lparam) > LOWORD(lparam))
{
// NT swaps the start and end value if start > end
lparam = MAKELONG(LOWORD(lparam), HIWORD(lparam) < phost->GetCount() ?
HIWORD(lparam) : phost->GetCount()-1);
}
// Item range messages do not effect the GetAnchor() nor the _nCursor
if (!phost->IsSingleSelection() && phost->LbSetSelection(HIWORD(lparam),
LOWORD(lparam), LBSEL_RESET | ((wparam) ? LBSEL_SELECT : 0), 0, 0))
{
#ifndef NOACCESSIBILITY
phost->_dwWinEvent = EVENT_OBJECT_SELECTIONWITHIN;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
#endif
break;
}
// We only get here if error occurs or list box is a singel sel Listbox
lres = LB_ERR;
break;
case LB_SETCURSEL:
// Only single selection list box can call this!!
if (phost->IsSingleSelection())
{
// -1 should return LB_ERR and turn off any selection
// special flag indicating no items should be selected
if (wparam == (unsigned)-1)
{
// turn-off any selections
int nCurrentCursor = phost->GetCursor();
phost->LbSetSelection(phost->GetCursor(), phost->GetCursor(), LBSEL_RESET, 0, 0);
phost->SetCursor(NULL, -1, phost->_fFocus);
#ifndef NOACCESSIBILITY
if (nCurrentCursor != -1)
{
phost->_dwWinEvent = EVENT_OBJECT_FOCUS;
phost->_nAccessibleIdx = nCurrentCursor + 1;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
phost->_dwWinEvent = EVENT_OBJECT_SELECTIONREMOVE;
phost->_nAccessibleIdx = nCurrentCursor + 1;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
}
#endif
}
else if (wparam < (unsigned)(phost->GetCount()))
{
if ((int)wparam == phost->GetCursor() && phost->IsSelected((int)wparam) &&
phost->IsItemViewable((signed)wparam) ||
phost->LbShowIndex(wparam, FALSE) /* bug fix #5260 - need to move to selected item first */
&& phost->LbSetSelection(wparam, wparam, LBSEL_DEFAULT, wparam, wparam))
{
lres = (unsigned)wparam;
#ifndef NOACCESSIBILITY
phost->_dwWinEvent = EVENT_OBJECT_FOCUS;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
phost->_dwWinEvent = EVENT_OBJECT_SELECTION;
phost->_fNotifyWinEvt = TRUE;
phost->TxNotify(phost->_dwWinEvent, NULL);
#endif
break;
}
}
}
// If failed then let it fall through to the LB_ERR
lres = LB_ERR;
break;
case LB_SETTOPINDEX:
// Set the top index
if ((!phost->GetCount() && !wparam) || phost->LbSetTopIndex(wparam) >= 0)
break;
// We get here if something went wrong
lres = LB_ERR;
break;
case LB_SETITEMHEIGHT:
if (!phost->LbSetItemHeight(LOWORD(lparam)))
lres = LB_ERR;
break;
case LB_GETITEMHEIGHT:
lres = LB_ERR;
if ((unsigned)phost->GetCount() > wparam || wparam == 0 || wparam == (unsigned)-1)
lres = phost->GetItemHeight();
break;
case LB_SETCARETINDEX:
if (((phost->GetCursor() == -1) || (!phost->IsSingleSelection()) &&
(phost->GetCount() > (INT)wparam)))
{
/*
* Set's the Cursor to the wParam
* if lParam, then don't scroll if partially visible
* else scroll into view if not fully visible
*/
if (!phost->IsItemViewable(wparam) || lparam)
{
phost->LbShowIndex(wparam, FALSE);
phost->SetCursor(NULL, wparam, TRUE);
}
lres = 0;
}
else
return LB_ERR;
break;
case EM_SETTEXTEX:
lres = LB_ERR;
if (lparam)
lres = phost->LbBatchInsert((WCHAR*)lparam);
break;
////////////////////////Windows Messages////////////////////////////////
case WM_VSCROLL:
phost->OnVScroll(wparam, lparam);
break;
case WM_CAPTURECHANGED:
lres = phost->OnCaptureChanged(wparam, lparam);
if (!lres)
break;
goto serv;
case WM_KILLFOCUS:
lres = 1;
phost->_fFocus = 0;
phost->SetCursor(NULL, phost->GetCursor(), TRUE); // force the removal of focus rect
phost->InitSearch();
phost->InitWheelDelta();
if (phost->_fLstType == CLstBxWinHost::kCombo)
phost->OnCBTracking(LBCBM_END, 0); //this will internally release the mouse capture
phost->TxNotify(LBN_KILLFOCUS, NULL);
break;
case WM_SETFOCUS:
lres = 1;
phost->_fFocus = 1;
phost->SetCursor(NULL, (phost->GetCursor() < 0) ? -2 : phost->GetCursor(),
FALSE); // force the displaying of the focus rect
phost->TxNotify(LBN_SETFOCUS, NULL);
break;
case WM_SETCURSOR:
lres = phost->OnSetCursor();
if (lres)
break;
goto serv;
case WM_CREATE:
lres = phost->OnCreate((CREATESTRUCT*)lparam);
break;
case WM_GETDLGCODE:
phost->_fInDialogBox = TRUE;
lres |= DLGC_WANTARROWS | DLGC_WANTCHARS;
break;
////////////////////////System setting messages/////////////////////
case WM_SETTINGCHANGE:
case WM_SYSCOLORCHANGE:
phost->OnSysColorChange();
// Need to update the edit controls colors!!!!
goto serv; // Notify text services that
// system colors have changed
case EM_SETPALETTE:
// Application is setting a palette for us to use.
phost->_hpal = (HPALETTE) wparam;
// Invalidate the window & repaint to reflect the new palette.
InvalidateRect(hwnd, NULL, FALSE);
break;
/////////////////////////Misc. Messages/////////////////////////////////
case WM_ENABLE:
if(!wparam ^ phost->_fDisabled)
{
// Stated of window changed so invalidate it so it will
// get redrawn.
InvalidateRect(phost->_hwnd, NULL, TRUE);
phost->SetScrollBarsForWmEnable(wparam);
}
phost->_fDisabled = !wparam; // Set disabled flag
// Fall thru to WM_SYSCOLORCHANGE?
case WM_STYLECHANGING:
// Just pass this one to the default window proc
goto defwndproc;
break;
case WM_STYLECHANGED:
// FUTURE:
// We should support style changes after the control has been created
// to be more compatible with the system controls
//
// For now, we only interested in GWL_EXSTYLE Transparent mode changed.
// This is to fix Bug 753 since Window95 is not passing us
// the WS_EX_TRANSPARENT.
//
lres = 1;
if(GWL_EXSTYLE == wparam)
{
LPSTYLESTRUCT lpss = (LPSTYLESTRUCT) lparam;
if(phost->IsTransparentMode() != (BOOL)(lpss->styleNew & WS_EX_TRANSPARENT))
{
phost->_dwExStyle = lpss->styleNew;
((CTxtEdit *)phost->_pserv)->OnTxBackStyleChange(TRUE);
// Return 0 to indicate we have handled this message
lres = 0;
}
}
break;
case WM_SIZE:
// Check if we have to recalculate the height of the listbox
// Note if window is resized we will receive another WM_SIZE message
// upon which the RecalcHeight will fail and we will proceed
// normally
if (phost->RecalcHeight(LOWORD(lparam), HIWORD(lparam)))
break;
phost->_pserv->TxSendMessage(msg, wparam, lparam, &lres);
lres = phost->OnSize(hwnd, wparam, (int)LOWORD(lparam), (int)HIWORD(lparam));
break;
case WM_WINDOWPOSCHANGING:
lres = ::DefWindowProc(hwnd, msg, wparam, lparam);
if(phost->TxGetEffects() == TXTEFFECT_SUNKEN)
phost->OnSunkenWindowPosChanging(hwnd, (WINDOWPOS *) lparam);
break;
case WM_SHOWWINDOW:
if ((phost->GetViewSize() == 0 || phost->_fLstType == CLstBxWinHost::kCombo) && wparam == 1)
{
// we need to do this because if we are part of a combo box
// we won't get the size message because listbox may not be visible at time of sizing
RECT rc;
GetClientRect(hwnd, &rc);
phost->_fVisible = 1;
phost->RecalcHeight(rc.right, rc.bottom);
//Since we may not get the WM_SIZE message for combo boxes we need to
// do this in WM_SHOWWINDOW: bug fix #4080
if (phost->_fLstType == CLstBxWinHost::kCombo)
{
phost->_pserv->TxSendMessage(WM_SIZE, SIZE_RESTORED, MAKELONG(rc.right, rc.bottom), NULL);
phost->OnSize(hwnd, SIZE_RESTORED, rc.right, rc.bottom);
}
}
hr = phost->OnTxVisibleChange((BOOL)wparam);
break;
case LB_SETTABSTOPS:
msg = EM_SETTABSTOPS;
goto serv;
case WM_ERASEBKGND:
lres = 1;
break;
case EM_SETPARAFORMAT:
wparam = SPF_SETDEFAULT;
goto serv;
case EM_SETCHARFORMAT:
wparam = SCF_ALL; //wparam for this message should always be SCF_ALL
goto serv;
case WM_GETTEXT:
GETTEXTEX gt;
if (W32->OnWin9x() || phost->_fANSIwindow)
W32->AnsiFilter( msg, wparam, lparam, (void *) &gt );
goto serv;
case WM_GETTEXTLENGTH:
GETTEXTLENGTHEX gtl;
if (W32->OnWin9x() || phost->_fANSIwindow)
W32->AnsiFilter( msg, wparam, lparam, (void *) &gtl );
goto serv;
#ifndef NOACCESSIBILITY
case WM_GETOBJECT:
IUnknown* punk;
phost->QueryInterface(IID_IUnknown, (void**)&punk);
Assert(punk);
lres = W32->LResultFromObject(IID_IUnknown, wparam, (LPUNKNOWN)punk);
AssertSz(!FAILED((HRESULT)lres), "WM_GETOBJECT message FAILED\n");
punk->Release();
break;
#endif
default:
serv:
hr = phost->_pserv->TxSendMessage(msg, wparam, lparam, &lres);
if(hr == S_FALSE)
{
defwndproc:
// Message was not processed by text services so send it
// to the default window proc.
lres = ::DefWindowProc(hwnd, msg, wparam, lparam);
}
}
// Special border processing. The inset changes based on the size of the
// defautl character set. So if we got a message that changes the default
// character set, we need to update the inset.
if ((msg == WM_SETFONT && wparam) || msg == EM_SETCHARFORMAT)
{
// need to recalculate the height of each item
phost->ResizeInset();
// need to resize the window to update internal window variables
RECT rc;
GetClientRect(phost->_hwnd, &rc);
phost->RecalcHeight(rc.right, rc.bottom);
}
Exit:
phost->Release();
return lres;
}
//////////////// CTxtWinHost Creation/Initialization/Destruction ///////////////////////
#ifndef NOACCESSIBILITY
/*
* CLstBxWinHost::OnNCCreate (hwnd, pcs)
*
* @mfunc
*
*/
HRESULT CLstBxWinHost::QueryInterface(REFIID riid, void **ppv)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CTxtWinHost::QueryInterface");
if(riid == IID_IAccessible)
*ppv = (IAccessible*)this;
else if (riid == IID_IDispatch)
*ppv = (IDispatch*)(IAccessible*)this;
else if (IsEqualIID(riid, IID_IUnknown))
*ppv = (IUnknown*)(IAccessible*)this;
else
return CTxtWinHost::QueryInterface(riid, ppv);
AddRef();
return NOERROR;
}
#endif
/*
* CLstBxWinHost::OnNCCreate (hwnd, pcs)
*
* @mfunc
* Static global method to handle WM_NCCREATE message (see remain.c)
*/
LRESULT CLstBxWinHost::OnNCCreate(
HWND hwnd,
const CREATESTRUCT *pcs)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnNCCreate");
#if defined DEBUG && !defined(PEGASUS)
GdiSetBatchLimit(1);
#endif
CLstBxWinHost *phost = new CLstBxWinHost();
Assert(phost);
if(!phost)
return 0;
if(!phost->Init(hwnd, pcs)) // Stores phost in associated
{ // window data
Assert(FALSE);
phost->Shutdown();
delete phost;
return FALSE;
}
return TRUE;
}
/*
* CLstBxWinHost::OnNCDestroy (phost)
*
* @mfunc
* Static global method to handle WM_CREATE message
*
* @devnote
* phost ptr is stored in window data (GetWindowLong())
*/
void CLstBxWinHost::OnNCDestroy(
CLstBxWinHost *phost)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnNCDestroy");
// We need to send WM_DELETEITEM messages for owner draw list boxes
if (phost->_fOwnerDraw && phost->_nCount)
{
phost->LbDeleteItemNotify(0, phost->_nCount - 1);
}
if (phost->_pwszSearch)
delete phost->_pwszSearch;
// set the combobox's listbox hwnd pointer to null so combo box won't try
// to delete the window twice
if (phost->_pcbHost)
{
phost->_pcbHost->_hwndList = NULL;
phost->_pcbHost->Release();
}
phost->Shutdown();
phost->Release();
}
/*
* CLstBxWinHost::CLstBxWinHost()
*
* @mfunc
* constructor
*/
CLstBxWinHost::CLstBxWinHost() : CTxtWinHost(), _nCount(0), _fSingleSel(0), _nidxSearch(0),
_pwszSearch(NULL), _pcbHost(NULL)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::CTxtWinHost");
#ifndef NOACCESSIBILITY
_dwWinEvent = 0; // Win Event code (ACCESSIBILITY use)
_nAccessibleIdx = -1; // Index (ACCESSIBILITY use)
#endif
}
/*
* CLstBxWinHost::~CLstBxWinHost()
*
* @mfunc
* destructor
*/
CLstBxWinHost::~CLstBxWinHost()
{
}
/*
* CLstBxWinHost::Init (hwnd, pcs)
*
* @mfunc
* Initialize this CLstBxWinHost
*/
BOOL CLstBxWinHost::Init(
HWND hwnd, //@parm Window handle for this control
const CREATESTRUCT *pcs) //@parm Corresponding CREATESTRUCT
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::Init");
if(!pcs->lpszClass)
return FALSE;
// Set pointer back to CLstBxWinHost from the window
if(hwnd)
SetWindowLongPtr(hwnd, ibPed, (INT_PTR)this);
_hwnd = hwnd;
_fHidden = TRUE;
if(pcs)
{
_hwndParent = pcs->hwndParent;
_dwExStyle = pcs->dwExStyle;
_dwStyle = pcs->style;
CHECKSTYLE(_dwStyle);
// Internally WinNT defines a LBS_COMBOBOX to determine
// if the list box is part of a combo box. So we will use
// the same flag and value!!
if (_dwStyle & LBS_COMBOBOX)
{
AssertSz(pcs->hMenu == (HMENU)CB_LISTBOXID && pcs->lpCreateParams,
"invalid combo box parameters");
if (pcs->hMenu != (HMENU)CB_LISTBOXID || !pcs->lpCreateParams)
return -1;
_pcbHost = (CCmbBxWinHost*) pcs->lpCreateParams;
_pcbHost->AddRef();
_fLstType = kCombo;
_fSingleSel = 1;
}
else
{
// NOTE:
// The order in which we check the style flags immulate
// WinNT's order. So please verify with NT order before
// reaaranging order.
// determine the type of list box
//if (_dwStyle & LBS_NOSEL) //Not implemented but may be in the future
// _fLstType = kNoSel;
//else
_fSingleSel = 0;
if (_dwStyle & LBS_EXTENDEDSEL)
_fLstType = kExtended;
else if (_dwStyle & LBS_MULTIPLESEL)
_fLstType = kMultiple;
else
{
_fLstType = kSingle;
_fSingleSel = 1;
}
}
_fNotify = ((_dwStyle & LBS_NOTIFY) != 0);
if (!(_dwStyle & LBS_HASSTRINGS))
{
_dwStyle |= LBS_HASSTRINGS;
SetWindowLong(_hwnd, GWL_STYLE, _dwStyle);
}
_fDisableScroll = 0;
if (_dwStyle & LBS_DISABLENOSCROLL)
{
_fDisableScroll = 1;
// WARNING!!!
// ES_DISABLENOSCROLL is equivalent to LBS_NODATA
// Since we don'w support LBS_NODATA this should be
// fine. But in the event we do want to support this
// in the future we will have to override the
// TxGetScrollBars member function and return the
// proper window style
// set the equivalent ES style
_dwStyle |= ES_DISABLENOSCROLL;
}
_fNoIntegralHeight = ((_dwStyle & LBS_NOINTEGRALHEIGHT) != 0);
_fOwnerDraw = ((_dwStyle & LBS_OWNERDRAWFIXED) != 0);
_fSort = ((_dwStyle & LBS_SORT) != 0);
// We should always have verticle scroll & never horizontal scroll
//_dwStyle |= ES_AUTOVSCROLL;
_dwStyle &= ~(WS_HSCROLL);
_fBorder = !!(_dwStyle & WS_BORDER);
if(_dwExStyle & WS_EX_CLIENTEDGE)
_fBorder = TRUE;
// handle default disabled
if(_dwStyle & WS_DISABLED)
_fDisabled = TRUE;
}
// Create Text Services component
if(FAILED(CreateTextServices()))
return FALSE;
_yInset = 0;
_xInset = 0; //_xWidthSys / 2;
// Shut-off the undo stack since listbox don't have undo's
((CTxtEdit*)_pserv)->HandleSetUndoLimit(0);
// Set alignment
PARAFORMAT PF2;
PF2.dwMask = 0;
if(_dwExStyle & WS_EX_RIGHT)
{
PF2.dwMask |= PFM_ALIGNMENT;
PF2.wAlignment = PFA_RIGHT; // right or center-aligned
}
if(_dwExStyle & WS_EX_RTLREADING)
{
PF2.dwMask |= PFM_RTLPARA;
PF2.wEffects = PFE_RTLPARA; // RTL reading order
}
if (PF2.dwMask)
{
PF2.cbSize = sizeof(PARAFORMAT2);
// tell text services
_pserv->TxSendMessage(EM_SETPARAFORMAT, SPF_SETDEFAULT, (LPARAM)&PF2, NULL);
}
// Tell textservices to select the entire background
_pserv->TxSendMessage(EM_SETEDITSTYLE, SES_EXTENDBACKCOLOR, SES_EXTENDBACKCOLOR, NULL);
// disable ime for listbox
_pserv->TxSendMessage(EM_SETEDITSTYLE, 0, SES_NOIME, NULL);
// Tell textservices to turn-on auto font sizing
_pserv->TxSendMessage(EM_SETLANGOPTIONS, 0, IMF_AUTOFONT | IMF_AUTOFONTSIZEADJUST | IMF_UIFONTS, NULL);
// NOTE:
// It is important we call this after
// ITextServices is created because this function relies on certain
// variable initialization to be performed on the creation by ITextServices
// At this point the border flag is set and so is the pixels per inch
// so we can initalize the inset.
_rcViewInset.left = 0;
_rcViewInset.bottom = 0;
_rcViewInset.right = 0;
_rcViewInset.top = 0;
return TRUE;
}
/*
* CLstBxWinHost::OnCreate (pcs)
*
* @mfunc
* Handle WM_CREATE message
*
* @rdesc
* LRESULT = -1 if failed to in-place activate; else 0
*/
LRESULT CLstBxWinHost::OnCreate(
const CREATESTRUCT *pcs)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnCreate");
RECT rcClient;
// sometimes, these values are -1 (from windows itself); just treat them
// as zero in that case
LONG cy = (pcs->cy < 0) ? 0 : pcs->cy;
LONG cx = (pcs->cx < 0) ? 0 : pcs->cx;
rcClient.top = pcs->y;
rcClient.bottom = rcClient.top + cy;
rcClient.left = pcs->x;
rcClient.right = rcClient.left + cx;
DWORD dwStyle = GetWindowLong(_hwnd, GWL_STYLE);
// init variables
UpdateSysColors();
_idCtrl = (UINT)(DWORD_PTR)pcs->hMenu;
_fKeyMaskSet = 0;
_fMouseMaskSet = 0;
_fScrollMaskSet = 0;
_nAnchor = _nCursor = -1;
_nOldCursor = -1;
_fMouseDown = 0;
_nTopIdx = 0;
_fSearching = 0;
_nyFont = _nyItem = 1;
_fNoResize = 1;
_stvidx = -1;
InitWheelDelta();
// Hide all scrollbars to start unless the disable scroll flag
// is set
if(_hwnd && !_fDisableScroll)
{
SetScrollRange(_hwnd, SB_VERT, 0, 0, TRUE);
SetScrollRange(_hwnd, SB_HORZ, 0, 0, TRUE);
dwStyle &= ~(WS_VSCROLL | WS_HSCROLL);
SetWindowLong(_hwnd, GWL_STYLE, dwStyle);
}
// Notify Text Services that we are in place active
if(FAILED(_pserv->OnTxInPlaceActivate(&rcClient)))
return -1;
// Initially the font height is the item height
ResizeInset();
Assert(_yInset == 0); // _yInset should be zero since listbox's doesn't have yinsets
//We never want to display the selection or caret so tell textservice this
_pserv->TxSendMessage(EM_HIDESELECTION, TRUE, FALSE, NULL);
//Set the indents to 2 pixels like system listboxes
SetListIndent(2);
_fNoResize = 0;
_usIMEMode = ES_NOIME;
return 0;
}
/*
* CLstBxWinHost::SetListIndent(int)
*
* @mfunc
* Sets the left indent of a paragraph to the equivalent point value of nLeft, nLeft is
* given in device-coordinate pixels.
*
* #rdesc
* BOOL = Successful ? TRUE : FALSE
*/
BOOL CLstBxWinHost::SetListIndent(int nLeft)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetListIndent");
LRESULT lres;
PARAFORMAT2 pf2;
// tranlate the nLeft pixel value to point value
long npt = MulDiv(nLeft, 1440, W32->GetXPerInchScreenDC());
//format message struct
pf2.cbSize = sizeof(PARAFORMAT2);
pf2.dwMask = PFM_STARTINDENT;
pf2.dxStartIndent = npt;
// indent first line
_pserv->TxSendMessage(EM_SETPARAFORMAT, SPF_SETDEFAULT, (LPARAM)&pf2, &lres);
return lres;
}
/////////////////////////////// Helper Functions //////////////////////////////////
/*
* CLstBxWinHost::FindString(long, LPCTSTR, BOOL)
*
* @mfunc
* This function checks a given index matches the search string
*
* #rdesc
* BOOL = Match ? TRUE : FALSE
*/
BOOL CLstBxWinHost::FindString(long idx, LPCTSTR szSearch, BOOL bExact)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::FindString");
Assert(_nCount);
// allocate string buffer into stack
WCHAR sz[1024];
WCHAR *psz = sz;
if ( (wcslen(szSearch) + 3 /* 2 paragraphs and a NULL*/) > 1024)
psz = new WCHAR[wcslen(szSearch) + 3 /* 2 paragraphs and a NULL*/];
Assert(psz);
if (psz == NULL)
{
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return FALSE;
}
// format the string the way we need it
wcscpy(psz, szSearch);
if (bExact)
wcscat(psz, szCR);
BOOL bMatch = FALSE;
ITextRange *pRange = NULL;
BSTR bstrQuery = SysAllocString(psz);
if (!bstrQuery)
goto CleanExit;
if (psz != sz)
delete [] psz;
// Set starting position for the search
long cp, cp2;
if (!GetRange(idx, idx, &pRange))
{
SysFreeString(bstrQuery);
return FALSE;
}
CHECKNOERROR(pRange->GetStart(&cp));
CHECKNOERROR(pRange->FindTextStart(bstrQuery, 0, FR_MATCHALEFHAMZA | FR_MATCHKASHIDA | FR_MATCHDIAC, NULL));
CHECKNOERROR(pRange->GetStart(&cp2));
bMatch = (cp == cp2);
CleanExit:
if (bstrQuery)
SysFreeString(bstrQuery);
if (pRange)
pRange->Release();
return bMatch;
}
/*
* CLstBxWinHost::MouseMoveHelper(int)
*
* @mfunc
* Helper function for the OnMouseMove function. Performs
* the correct type of selection given an index to select
*
* #rdesc
* void
*/
void CLstBxWinHost::MouseMoveHelper(int idx, BOOL bSelect)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::MouseMoveHelper");
int ff = LBSEL_RESET | LBSEL_NEWCURSOR;
if (bSelect)
ff |= LBSEL_SELECT;
switch (_fLstType)
{
case kSingle:
case kCombo:
case kExtended: // perform the extended selection
if (LbSetSelection(_fLstType == kExtended ? _nAnchor : idx, idx, ff, idx, 0))
{
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_FOCUS;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
if (_fLstType == kCombo)
{
_dwWinEvent = bSelect ? EVENT_OBJECT_SELECTION : EVENT_OBJECT_SELECTIONREMOVE;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
}
#endif
}
break;
case kMultiple:
// Just change the cursor position
SetCursor(NULL, idx, TRUE);
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_FOCUS;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
#endif
break;
}
}
/*
* CLstBxWinHost::ResizeInset
*
* @mfunc Recalculates rectangle for a font change.
*
* @rdesc None.
*/
void CLstBxWinHost::ResizeInset()
{
// Create a DC
HDC hdc = GetDC(_hwnd);
// Get the inset information
LONG xAveCharWidth = 0;
LONG yCharHeight = GetECDefaultHeightAndWidth(_pserv, hdc, 1, 1,
W32->GetYPerInchScreenDC(), &xAveCharWidth, NULL, NULL);
ReleaseDC(_hwnd, hdc);
// update our internal font and item height information with the new font
if (_nyItem == _nyFont)
{
// We need to set the new font height before calling set item height
// so set item height will set exact height rather than space after
// for the default paragraph
_nyFont = yCharHeight;
SetItemsHeight(yCharHeight, TRUE);
}
else
_nyFont = yCharHeight;
}
/*
* CLstBxWinHost::RecalcHeight(int, int)
*
* @mfunc
* Resized the height so no partial text will be displayed
*
* #rdesc
* BOOL = window has been resized ? TRUE : FALSE
*/
BOOL CLstBxWinHost::RecalcHeight(int nWidth, int nHeight)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::RecalcHeight");
// NOTE: We should also exit if nWidth == 0 but PPT does some
// sizing tests which we cause it to fail because before we
// just exited when nWidth was 0. (bug fix #4196)
// Check if any resizing should be done in the first place
if (_fNoResize || !nHeight || IsIconic(_hwnd))
return FALSE;
// get # of viewable items
Assert(_yInset == 0);
_nViewSize = max(1, (nHeight / max(_nyItem, 1)));
// Calculate the viewport
_rcViewport.left = 0;//(_fBorder) ? _xInset : 0;
_rcViewport.bottom = nHeight;
_rcViewport.right = nWidth;
_rcViewport.top = 0;
// bug fix don't do anything if the height is smaller then our font height
if (nHeight <= _nyItem)
return FALSE;
if (_nyItem && (nHeight % _nyItem) && !_fNoIntegralHeight)
{
// we need to get the window rect before we can call SetWindowPos because
// we have to include the scrollbar if the scrollbar is visible
RECT rc;
::GetWindowRect(_hwnd, &rc);
// instead of worrying about the dimensions of the client edge and stuff we
// figure-out the difference between the window size and the client size and add
// that to the end of calculating the new height
int nDiff = max(rc.bottom - rc.top - nHeight, 0);
nHeight = (_nViewSize * _nyItem) + nDiff;
// Resize the window
SetWindowPos(_hwnd, HWND_TOP, 0, 0, rc.right - rc.left, nHeight,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOSENDCHANGING);
return TRUE;
}
else
{
// bug fix #6011
// we need to force the display to update the width since it doesn't do it on
// WM_SIZE
_sWidth = nWidth;
_pserv->OnTxPropertyBitsChange(TXTBIT_EXTENTCHANGE, TXTBIT_EXTENTCHANGE);
// We may need to adjust the top index if suddenly the viewsize becomes larger
// and causes empty space to be displayed at the bottom
int idx = GetTopIndex();
if ((GetCount() - max(0, idx)) < _nViewSize)
idx = GetCount() - _nViewSize;
//bug fix #4374
// We need to make sure our internal state is in sync so update the top index
// based on the new _nViewSize
SetTopViewableItem(max(0, idx));
}
return FALSE;
}
/*
* CLstBxWinHost::SortInsertList(WCHAR* pszDst, WCHAR* pszSrc)
*
* @mfunc
* inserts a list of strings rather than one at a time with addstring
*
* #rdesc
* int = amount of strings inserted;
*/
int CLstBxWinHost::SortInsertList(WCHAR* pszDst, WCHAR* pszSrc)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SortInsertList");
Assert(pszSrc != NULL);
Assert(pszDst != NULL);
const int ARRAY_DEFAULT = 256;
//calculate the amount of strings to be inserted
CHARSORTINFO rg[ARRAY_DEFAULT];
int nMax = ARRAY_DEFAULT;
int nLen = wcslen(pszSrc);
CHARSORTINFO* prg = rg;
memset(rg, 0, sizeof(rg));
//insert first item in list to head or array
prg[0].str = pszSrc;
int i = 1;
// go through store strings into array and replace <CR> with NULL
WCHAR* psz = nLen + pszSrc - 1; //start at end of list
int nSz = 0;
while (psz >= pszSrc)
{
if (*psz == *szCR)
{
// Check if we need to allocate memory since we hit the maximum amount
// allowed in array
if (i == nMax)
{
int nSize = nMax + ARRAY_DEFAULT;
CHARSORTINFO* prgTemp = new CHARSORTINFO[nSize];
// Check if memory allocation failed
Assert(prgTemp);
if (!prgTemp)
{
if (prg != rg)
delete [] prg;
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return LB_ERR;
}
// copy memory from 1 array to the next
memcpy(prgTemp, prg, sizeof(CHARSORTINFO) * nMax);
// delete any previously allocated memory
if (prg != rg)
delete [] prg;
// set pointers and max to new values
prg = prgTemp;
nMax = nSize;
}
// record position of string into array
prg[i].str = psz + 1;
prg[i].sz = nSz;
i++;
nSz = 0;
}
else
nSz++;
psz--;
}
prg[0].sz = nSz; // update the size of first index since we didn't do it before
i--; // set i to last valid index
//now sort the array of items
QSort(prg, 0, i);
//create string list with the newly sorted list
WCHAR* pszOut = pszDst;
for (int j = 0; j <= i; j++)
{
memcpy(pszOut, (prg + j)->str, (prg + j)->sz * sizeof(WCHAR));
pszOut = pszOut + (prg + j)->sz;
*pszOut++ = L'\r';
}
*(--pszOut) = L'\0';
// delete any previously allocated memory
if (prg != rg)
delete [] prg;
return ++i;
}
/*
* CLstBxWinHost::QSort(CHARSORTINFO rg[], int nStart, int nEnd)
*
* @mfunc
* recursively quick sorts a given list of strings
*
* #rdesc
* int = SHOULD ALWAYS RETURN TRUE;
*/
int CLstBxWinHost::QSort(CHARSORTINFO rg[], int nStart, int nEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::QSort");
// it's important these values are what they are since we use < and >
Assert(CSTR_LESS_THAN == 1);
Assert(CSTR_EQUAL == 2);
Assert(CSTR_GREATER_THAN == 3);
if (nStart >= nEnd)
return TRUE;
// for statisical efficiency lets use the item in the middle of the array for
// the sentinal
int mid = (nStart + nEnd) / 2;
CHARSORTINFO tmp = rg[mid];
rg[mid] = rg[nEnd];
rg[nEnd] = tmp;
int x = nStart;
int y = nEnd - 1;
WCHAR* psz = rg[nEnd].str;
int nSz = rg[nEnd].sz;
for(;;)
{
while ((x < nEnd) && CompareStringWrapper(LOCALE_USER_DEFAULT, NORM_IGNORECASE, rg[x].str, rg[x].sz,
psz, nSz) == CSTR_LESS_THAN)
x++;
while ((y > x) && CompareStringWrapper(LOCALE_USER_DEFAULT, NORM_IGNORECASE, rg[y].str, rg[y].sz,
psz, nSz) == CSTR_GREATER_THAN)
y--;
// swap elements
if (x >= y)
break;
//if we got here then we need to swap the indexes
tmp = rg[x];
rg[x] = rg[y];
rg[y] = tmp;
// move to next index
x++;
y--;
}
tmp = rg[x];
rg[x] = rg[nEnd];
rg[nEnd] = tmp;
QSort(rg, nStart, x - 1);
QSort(rg, x + 1, nEnd);
return TRUE;
}
/*
* CLstBxWinHost::CompareIndex(LPCTSTR, int)
*
* @mfunc
* Recursive function which returns the insertion index of a sorted list
*
* #rdesc
* int = position to insert string
*/
int CLstBxWinHost::CompareIndex(LPCTSTR szInsert, int nIndex)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::CompareIndex");
Assert(0 <= nIndex && nIndex < _nCount);
// Get the string at a given index
// compare the string verses the index
ITextRange* pRange;
if (!GetRange(nIndex, nIndex, &pRange))
return -1;
// Exclude the paragraph character at the end
long lcid;
if (NOERROR != pRange->MoveEnd(tomCharacter, -1, NULL))
{
pRange->Release();
return -1;
}
// we need to get the locale for the comparison
// we will just use the locale of the string we want to compare with
ITextFont* pFont;
if (NOERROR != pRange->GetFont(&pFont))
{
pRange->Release();
return -1;
}
// UNDONE:
// move the lcid stuff to be part of the initialization
BSTR bstr;
int nRet;
CHECKNOERROR(pFont->GetLanguageID(&lcid));
CHECKNOERROR(pRange->GetText(&bstr));
if (!bstr)
nRet = CSTR_GREATER_THAN;
else if (!szInsert || !*szInsert)
nRet = CSTR_LESS_THAN;
else
{
nRet = CompareString(lcid, NORM_IGNORECASE, szInsert, wcslen(szInsert),
bstr, wcslen(bstr));
SysFreeString(bstr);
}
pFont->Release();
pRange->Release();
return nRet;
CleanExit:
Assert(FALSE);
pFont->Release();
pRange->Release();
return -1;
}
/*
* CLstBxWinHost::GetSortedPosition(LPCTSTR, int, int)
*
* @mfunc
* Recursive function which returns the insertion index of a sorted list
*
* #rdesc
* int = position to insert string
*/
int CLstBxWinHost::GetSortedPosition(LPCTSTR szInsert, int nStart, int nEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetSortedPosition");
Assert(nStart <= nEnd);
// Start at the middle of the list
int nBisect = (nStart + nEnd) / 2;
int fResult = CompareIndex(szInsert, nBisect);
if (fResult == CSTR_LESS_THAN)
{
if (nStart == nBisect)
return nBisect;
else
return GetSortedPosition(szInsert, nStart, nBisect - 1); // [nStart, nBisect)
}
else if (fResult == CSTR_GREATER_THAN)
{
if (nEnd == nBisect)
return nBisect + 1;
else
return GetSortedPosition(szInsert, nBisect + 1, nEnd); // (nBisect, nStart]
}
else /*fResult == 0 (found match)*/
return nBisect;
}
/*
* CLstBxWinHost::SetScrollInfo
*
* @mfunc Set scrolling information for the scroll bar.
*/
void CLstBxWinHost::SetScrollInfo(
INT fnBar, //@parm Specifies scroll bar to be updated
BOOL fRedraw) //@parm whether redraw is necessary
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetScrollInfo");
Assert(_pserv);
// Call back to the control to get the parameters
if(fnBar == SB_VERT)
{
// Bug Fix #4913
// if the scrollbar is disabled and count is less than the view size
// then there is nothing to do so just exit out
if (GetCount() <= _nViewSize)
{
if (_fDisableScroll)
{
// Since listboxes changes height according to its content textservice
// might of turned-on the scrollbar during an insert string. Make sure
// the scrollbar is disabled
TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH);
}
else
TxShowScrollBar(SB_VERT, FALSE);
return;
}
else
TxEnableScrollBar(SB_VERT, ESB_ENABLE_BOTH);
// Set up the basic structure for the call
SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
RECT rc;
TxGetClientRect(&rc);
// For owner draw cases we have to set the scroll positioning
// ourselves
if (_fOwnerDraw)
{
Assert(GetCount() >= 0);
// We don't do anything here if
// 1) item height is smaller than font height
// 2) count is less than _nViewSize
if ((_nyItem < _nyFont) && GetCount() <= _nViewSize)
{
if (!_fDisableScroll)
TxShowScrollBar(SB_VERT, FALSE);
return;
}
si.nMin = 0;
si.nMax = _nyItem * GetCount();
si.nPos = _nyItem * max(GetTopIndex(), 0);
}
else
_pserv->TxGetVScroll((LONG *) &si.nMin, (LONG *) &si.nMax,
(LONG *) &si.nPos, (LONG *) &si.nPage, NULL);
// need to take care of cases where items are partially exposed
if (si.nMax)
{
si.nPage = rc.bottom; //our scrollbar range is based on pixels so just use the
//height of the window for the page size
si.nMax += (rc.bottom % _nyItem);
// We need to decrement the max by one so maximum scroll pos will match
// what the listbox should be the maximum value
si.nMax--;
}
// Do the call
::SetScrollInfo(_hwnd, fnBar, &si, fRedraw);
}
}
/*
* CLstBxWinHost::TxGetScrollBars (pdwScrollBar)
*
* @mfunc
* Get Text Host's scroll bars supported.
*
* @rdesc
* HRESULT = S_OK
*
* @comm
* <p pdwScrollBar> is filled with a boolean combination of the
* window styles related to scroll bars. Specifically, these are:
*
* WS_VSCROLL <nl>
* WS_HSCROLL <nl>
* ES_AUTOVSCROLL <nl>
* ES_AUTOHSCROLL <nl>
* ES_DISABLENOSCROLL <nl>
*/
HRESULT CLstBxWinHost::TxGetScrollBars(
DWORD *pdwScrollBar) //@parm Where to put scrollbar information
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEEXTERN, "CLstBxWinHost::TxGetScrollBars");
*pdwScrollBar = _dwStyle & (WS_VSCROLL | ((_fDisableScroll) ? ES_DISABLENOSCROLL : 0));
return NOERROR;
}
/*
* CLstBxWinHost::TxGetEffects()
*
* @mfunc
* Indicates if a sunken window effect should be drawn
*
* #rdesc
* HRESULT = (_fBorder) ? TXTEFFECT_SUNKEN : TXTEFFECT_NONE
*/
TXTEFFECT CLstBxWinHost::TxGetEffects() const
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::TxGetEffects");
return (_fBorder) ? TXTEFFECT_SUNKEN : TXTEFFECT_NONE;
}
/*
* CLstBxWinHost::TxNotify (iNotify, pv)
*
* @mfunc
* Notify Text Host of various events. Note that there are
* two basic categories of events, "direct" events and
* "delayed" events. All listbox notifications are post-action
*
*
* @rdesc
* S_OK - call succeeded <nl>
* S_FALSE -- success, but do some different action
* depending on the event type (see below).
*
* @comm
* The notification events are the same as the notification
* messages sent to the parent window of a listbox window.
*
* <LBN_DBLCLK> user double-clicks an item in teh list box
*
* <LBN_ERRSPCAE> The list box cannot allocate enough memory to
* fulfill a request
*
* <LBN_KILLFOCUS> The list box loses the keyboard focus
*
* <LBN_CANCEL> The user cancels te selection of an item in the list
* box
*
* <LBN_SELCHANGE> The selection in a list box is about to change
*
* <LBN_SETFOCUS> The list box receives the keyboard focus
*
*/
HRESULT CLstBxWinHost::TxNotify(
DWORD iNotify, //@parm Event to notify host of. One of the
// EN_XXX values from Win32, e.g., EN_CHANGE
void *pv) //@parm In-only parameter with extra data. Type
// dependent on <p iNotify>
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEEXTERN, "CLstBxWinHost::TxNotify");
HRESULT hr = NOERROR;
// Filter-out all the messages except Listbox notification messages
// If _fNotifyWinEvt is true, we only need to do NotifyWinEvent
if (_fNotify && !_fNotifyWinEvt) // Notify parent?
{
Assert(_hwndParent);
switch (iNotify)
{
case LBN_DBLCLK:
case LBN_ERRSPACE:
case LBN_KILLFOCUS:
case LBN_SELCANCEL:
case LBN_SELCHANGE:
case LBN_SETFOCUS:
hr = SendMessage(_hwndParent, WM_COMMAND,
GET_WM_COMMAND_MPS(_idCtrl, _hwnd, iNotify));
}
}
_fNotifyWinEvt = 0;
#ifndef NOACCESSIBILITY
DWORD dwLocalWinEvent = _dwWinEvent;
int nLocalIdx = _nAccessibleIdx;
_dwWinEvent = 0;
if (nLocalIdx == -1)
nLocalIdx = _nCursor+1;
_nAccessibleIdx = -1;
if (iNotify == LBN_SELCHANGE || dwLocalWinEvent)
W32->NotifyWinEvent(dwLocalWinEvent ? dwLocalWinEvent : EVENT_OBJECT_SELECTION, _hwnd, _idCtrl, nLocalIdx);
#endif
return hr;
}
/*
* CLstBxWinHost::TxGetPropertyBits(DWORD, DWORD *)
*
* @mfunc
* returns the proper style. This is a way to fool the edit
* control to behave the way we want it to
*
* #rdesc
* HRESULT = always NOERROR
*/
HRESULT CLstBxWinHost::TxGetPropertyBits(DWORD dwMask, DWORD *pdwBits)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::TxGetPropertyBits");
// Note: the rich edit host will never set TXTBIT_SHOWACCELERATOR or
// TXTBIT_SAVESELECTION. Those are currently only used by forms^3 host.
// This host is always rich text.
*pdwBits = (TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_HIDESELECTION |
TXTBIT_DISABLEDRAG | TXTBIT_USECURRENTBKG) & dwMask;
return NOERROR;
}
/*
* CLstBxWinHost::TxShowScrollBar (fnBar, fShow)
*
* @mfunc
* Shows or Hides scroll bar in Text Host window
*
* @rdesc
* TRUE on success, FALSE otherwise
*
* @comm
* This method is only valid when the control is in-place active;
* calls while inactive may fail.
*/
BOOL CLstBxWinHost::TxShowScrollBar(
INT fnBar, //@parm Specifies scroll bar(s) to be shown or hidden
BOOL fShow) //@parm Specifies whether scroll bar is shown or hidden
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEEXTERN, "CLstBxWinHost::TxShowScrollBar");
// There maybe cases where the item height is smaller than the font size
// which means the notifications from ITextServices is wrong because
// it uses the wrong line height. We will use the following case
// 1a) if _nyItem >= _nyFont OR
// 1b) if window style is LBS_DISABLESCROLL OR
// 1c) We are showing the scrollbar w/ current count greater than viewsize OR
// 1d) We are hiding the scrollbar w/ current count <= viewsize
Assert(fShow == TRUE || fShow == FALSE);
if (_nyItem >= _nyFont || _fDisableScroll || fShow == (GetCount() > _nViewSize))
return CTxtWinHost::TxShowScrollBar(fnBar, fShow);
return FALSE;
}
/*
* CLstBxWinHost::TxEnableScrollBar (fuSBFlags, fuArrowflags)
*
* @mfunc
* Enables or disables one or both scroll bar arrows
* in Text Host window.
*
* @rdesc
* If the arrows are enabled or disabled as specified, the return
* value is TRUE. If the arrows are already in the requested state or an
* error occurs, the return value is FALSE.
*
* @comm
* This method is only valid when the control is in-place active;
* calls while inactive may fail.
*/
BOOL CLstBxWinHost::TxEnableScrollBar (
INT fuSBFlags, //@parm Specifies scroll bar type
INT fuArrowflags) //@parm Specifies whether and which scroll bar arrows
// are enabled or disabled
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEEXTERN, "CLstBxWinHost::TxEnableScrollBar");
// There may be cases where the item height is smaller than the font size
// which means the notifications from ITextServices is wrong. We have to perform
// some manual checking for owner draw listboxes. The following cases will be valid
// 1. If the listbox is NOT owner draw
// 2. If the message is to disable the control
// 3. If the count is greater than the viewsize
if (!_fOwnerDraw || ESB_ENABLE_BOTH != fuArrowflags || GetCount() > _nViewSize)
return CTxtWinHost::TxEnableScrollBar(fuSBFlags, fuArrowflags);
return FALSE;
}
/*
* CLstBxWinHost::SetItemsHeight(int, BOOL)
*
* @mfunc
* Sets the items height for all items
*
* #rdesc
* int = number of paragraphs whose fontsize has been changed
*/
int CLstBxWinHost::SetItemsHeight(int nHeight, BOOL bUseExact)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetItemsHeight");
// Calculate the new size in points
long nptNew = MulDiv(nHeight, 1440, W32->GetYPerInchScreenDC());
long nptMin = MulDiv(_nyFont, 1440, W32->GetYPerInchScreenDC());
// NOTE:
// This diverges from what the system list box does but there isn't a way
// to set the height of a item to smaller than what the richedit will allow and
// is not ownerdraw. If it is owner draw make sure our height is not zero
if (((nptNew < nptMin && !_fOwnerDraw) || nHeight <= 0) && !bUseExact)
nptNew = nptMin;
// Start setting the new height
Freeze();
long nPt;
PARAFORMAT2 pf2;
pf2.cbSize = sizeof(PARAFORMAT2);
if (bUseExact)
{
pf2.dwMask = PFM_LINESPACING;
pf2.bLineSpacingRule = 4;
pf2.dyLineSpacing = nPt = nptNew;
}
else
{
pf2.dwMask = PFM_SPACEAFTER;
pf2.dySpaceAfter = max(nptNew - nptMin, 0);
nPt = pf2.dySpaceAfter + nptMin;
}
// Set the default paragraph format
LRESULT lr;
_pserv->TxSendMessage(EM_SETPARAFORMAT, SPF_SETDEFAULT, (WPARAM)&pf2, &lr);
// set the item height
if (lr)
_nyItem = (_fOwnerDraw && nHeight > 0) ? nHeight :
MulDiv(nPt, W32->GetYPerInchScreenDC(), 1440);
Unfreeze();
return lr;
}
/*
* CLstBxWinHost::UpdateSysColors()
*
* @mfunc
* update the system colors in the event they changed or for initialization
* purposes
*
* #rdesc
* <none>
*/
void CLstBxWinHost::UpdateSysColors()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::UpdateSysColors");
// Update the system colors
_crDefBack = ::GetSysColor(COLOR_WINDOW);
_crSelBack = ::GetSysColor(COLOR_HIGHLIGHT);
_crDefFore = ::GetSysColor(COLOR_WINDOWTEXT);
_crSelFore = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
}
/*
* CLstBxWinHost::UpdateViewArea()
*
* @mfunc
* Gets the height of each item and keeps an internal record of it
*
* #rdesc
* <none>
*/
void CLstBxWinHost::UpdateViewArea()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::UpdateViewArea");
Assert(_pserv);
_nyItem = 1; // set to default value for right now
//Set the range to the first item
ITextRange* pRange;
if (NOERROR != ((CTxtEdit*)_pserv)->Range(0, 0, &pRange))
return;
Assert(pRange);
// get rect of window
TxGetClientRect(&_rcViewport);
// calculate the height of each item
long x;
CHECKNOERROR(pRange->GetPoint(tomStart | TA_BOTTOM | TA_LEFT, &x, &_nyItem));
_nyItem -= _rcViewport.top;
CleanExit:
pRange->Release();
return;
}
/*
* CLstBxWinHost::SetCursor(HDC, int, BOOL)
*
* @mfunc
* Sets the cursor position, if it's valid and draws the focus rectangle if
* the control has focus. The BOOL is used to determine if the previous
* cursor drawing needs to be removed
*
* #rdesc
* <none>
*/
void CLstBxWinHost::SetCursor(HDC hdc, int idx, BOOL bErase)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetCursor");
Assert(idx >= -2 && idx < _nCount);
// Get the hdc if it wasn't passed in
BOOL bReleaseDC = (hdc == NULL);
if (bReleaseDC)
hdc = TxGetDC();
Assert(hdc);
RECT rc;
// don't draw outside the client rect draw the rectangle
TxGetClientRect(&rc);
IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
// Check if we have to remove the previous position
if ((idx != _nCursor && _fFocus && idx >= -1) || bErase)
{
if (_fOwnerDraw)
LbDrawItemNotify(hdc, _nCursor, ODA_FOCUS, (IsSelected(max(_nCursor, 0)) ? ODS_SELECTED : 0));
else if (IsItemViewable(max(0, _nCursor)))
{
LbGetItemRect(max(_nCursor, 0), &rc);
::DrawFocusRect(hdc, &rc);
}
}
// special flag meaning to set the cursor to the top index
// if there are items in the listbox
if (idx == -2)
{
if (GetCount())
{
idx = max(_nCursor, 0);
if (!IsItemViewable(idx))
idx = GetTopIndex();
}
else
idx = -1;
}
_nCursor = idx;
// Only draw the focus rect if the cursor item is
// visible in the list box
if (_fFocus)
{
if (_fOwnerDraw)
LbDrawItemNotify(hdc, max(0, _nCursor), ODA_FOCUS, ODS_FOCUS | (IsSelected(max(0, _nCursor)) ? ODS_SELECTED : 0));
else if (IsItemViewable(max(0, idx)))
{
// Now draw the rectangle
LbGetItemRect(max(0,_nCursor), &rc);
::DrawFocusRect(hdc, &rc);
}
}
if (bReleaseDC)
TxReleaseDC(hdc);
}
/*
* CLstBxWinHost::InitSearch()
*
* @mfunc
* Sets the array to its initial state
*
* #rdesc
* <none>
*/
void CLstBxWinHost::InitSearch()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::InitSearch");
_fSearching = 0;
_nidxSearch = 0;
if (_pwszSearch)
*_pwszSearch = 0;
}
/*
* CLstBxWinHost::PointInRect(const POINT*)
*
* @mfunc
* Determines if the given point is inside the listbox windows rect
* The point parameter should be in client coordinates.
*
* #rdesc
* BOOL = inside listbox window rectangle ? TRUE : FALSE
*/
BOOL CLstBxWinHost::PointInRect(const POINT * ppt)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::PointInRect");
Assert(ppt);
RECT rc;
::GetClientRect(_hwnd, &rc);
return PtInRect(&rc, *ppt);
}
/*
* CLstBxWinHost::GetItemFromPoint(POINT*)
*
* @mfunc
* Retrieves the nearest viewable item from a passed in point.
* The point should be in client coordinates.
*
* #rdesc
* int = item which is closest to the given in point, -1 if there
* are no items in the list box
*/
int CLstBxWinHost::GetItemFromPoint(const POINT * ppt)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetItemFromPoint");
// perform error checking first
if (_nCount == 0)
return -1;
int y = (signed short)ppt->y;
// make sure y is in a valid range
if (y < _rcViewport.top)
y = 0;
else if (y > _rcViewport.bottom)
y = _rcViewport.bottom - 1;
//need to factor in the possibility an item may not fit entirely into the window view
Assert(_nyItem);
int idx = GetTopIndex() + (int)(max(0,(y - 1)) / max(1,_nyItem));
Assert(IsItemViewable(idx));
return (idx < _nCount ? idx : _nCount - 1);
}
/*
* CLstBxWinHost::ResetContent()
*
* @mfunc
* Deselects all the items in the list box
*
* #rdesc
* BOOL = If everything went fine ? TRUE : FALSE
*/
BOOL CLstBxWinHost::ResetContent()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::ResetContent");
Assert(_fOwnerDraw == 0);
// lets try to be smart about reseting the colors by only select a range
// from the first selection found to the last selection found
int nStart = _nCount - 1;
int nEnd = -1;
for (int i = 0; i < _nCount; i++)
{
if (_rgData[i]._fSelected)
{
_rgData[i]._fSelected = 0;
if (nStart > i)
nStart = i;
if (nEnd < i)
nEnd = i;
}
}
Assert(nStart <= nEnd || ((nStart == _nCount - 1) && (nEnd == -1)));
if (nStart > nEnd)
return TRUE;
return (_nCount > 0) ? SetColors((unsigned)tomAutoColor, (unsigned)tomAutoColor, nStart, nEnd) : FALSE;
}
/*
* CLstBxWinHost::GetString(long, PWCHAR)
*
* @mfunc
* Retrieve the string at the requested index. PWSTR can be null
* if only the text length is requires
*
* #rdesc
* long = successful ? length of string : -1
*/
long CLstBxWinHost::GetString(long nIdx, PWCHAR szOut)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetString");
Assert(0 <= nIdx && nIdx < _nCount);
if (nIdx < 0 || _nCount <= nIdx)
return -1;
long l = -1;
long lStart;
long lEnd;
ITextRange* pRange;
BSTR bstr;
if (!GetRange(nIdx, nIdx, &pRange))
return -1;
// Need to move one character to the left to unselect the paragraph marker.
Assert(pRange);
CHECKNOERROR(pRange->MoveEnd(tomCharacter, -1, &lEnd));
CHECKNOERROR(pRange->GetStart(&lStart));
CHECKNOERROR(pRange->GetEnd(&lEnd));
// Get the string
if (szOut)
{
if (_dwStyle & LBS_HASSTRINGS)
{
CHECKNOERROR(pRange->GetText(&bstr));
if (bstr)
{
wcscpy(szOut, bstr);
SysFreeString(bstr);
}
else
wcscpy(szOut, L""); // we got an empty string!
}
else
(*(long*)szOut) = GetData(nIdx);
}
l = lEnd - lStart;
CleanExit:
pRange->Release();
return l;
}
/*
* CLstBxWinHost::InsertString(long, LPCTSTR)
*
* @mfunc
* Insert the string at the requested location. If the
* requested index is larger than _nCount then the function
* will fail. The string is inserted with CR appended to
* to the front and back of the string
*
* #rdesc
* BOOL = successfully inserted ? TRUE : FALSE
*/
BOOL CLstBxWinHost::InsertString(long nIdx, LPCTSTR szInsert)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::InsertString");
Assert(szInsert);
Assert(0 <= nIdx && nIdx <= _nCount);
// allocate string buffer into stack
WCHAR sz[1024];
WCHAR *psz = sz;
if ( (wcslen(szInsert) + 3 /* 2 paragraphs and a NULL*/) > 1024)
psz = new WCHAR[wcslen(szInsert) + 3 /* 2 paragraphs and a NULL*/];
Assert(psz);
if (psz == NULL)
{
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return FALSE;
}
*psz = NULL;
if (nIdx == _nCount && _nCount)
wcscpy(psz, szCR);
// copy string and add <CR> at the end
wcscat(psz, szInsert);
// don't add the carriage return if the entry point is the end
if (nIdx < _nCount)
wcscat(psz, szCR);
BOOL bRet = FALSE;
ITextRange * pRange = NULL;
int fFocus = _fFocus;
long idx = nIdx;
BSTR bstr = SysAllocString(psz);
if (!bstr)
goto CleanExit;
Assert(bstr);
if (psz != sz)
delete [] psz;
// Set the range to the point where we want to insert the string
// make sure the requested range is a valid one
if (nIdx == _nCount)
idx = max(idx - 1, 0);
if (!GetRange(idx, idx, &pRange))
{
SysFreeString(bstr);
return FALSE;
}
// Collapse the range to the start if insertion is in the middle or top
// of list, collapse range to the end if we are inserting at the end of the list
CHECKNOERROR(pRange->Collapse((idx == nIdx)));
// Need to assume the item was successfully added because during SetText TxEnable(show)Scrollbar
// gets called which looks at the count to determine if we should display the scroll bar
_nCount++;
//bug fix #5411
// Check if we have focus, if so we need to remove the focus rect first and update the cursor positions
_fFocus = 0;
SetCursor(NULL, (idx > GetCursor() || GetCursor() < 0) ? GetCursor() : GetCursor() + 1, fFocus);
_fFocus = fFocus;
//For ownerdraw cases where the item height is less than the font we need to manually
//enable the scrollbar if we need the scrollbar and the scrollbar is disabled.
if ((_nyItem < _nyFont) && (_fDisableScroll) && (_nCount - 1 == _nViewSize))
TxEnableScrollBar(SB_VERT, ESB_ENABLE_BOTH);
#ifdef _DEBUG
if (bstr && wcslen(bstr))
Assert(FALSE);
#endif
if (NOERROR != (pRange->SetText(bstr)))
{
_nCount--;
//Unsuccessful in adding the string so disable the scrollbar if we enabled it
if ((_nyItem < _nyFont) && (_fDisableScroll) && (_nCount == _nViewSize))
TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH);
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
goto CleanExit;
}
//We need to update the top index after a string is inserted
if (idx < GetTopIndex())
_nTopIdx++;
bRet = TRUE;
CleanExit:
if (bstr)
SysFreeString(bstr);
if (pRange)
pRange->Release();
return bRet;
}
/*
* BOOL CLstBxWinHost::RemoveString(long, long)
*
* @mfunc
* Prevents TOM from drawing
*
* #rdesc
* BOOL = Successful ? TRUE : FALSE
*/
BOOL CLstBxWinHost::RemoveString(long nStart, long nEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::RemoveString");
Assert(nStart <= nEnd);
Assert(nStart < _nCount && nEnd < _nCount);
// Remove item from richedit
Freeze();
ITextRange* pRange;
if (!GetRange(nStart, nEnd, &pRange))
{
Unfreeze();
return FALSE;
}
long l;
// Since we can't erase the last paragraph marker we will erase
// the paragraph marker before the item if it's not the first item
HRESULT hr;
if (nStart != 0)
{
hr = pRange->MoveStart(tomCharacter, -1, &l);
Assert(hr == NOERROR);
hr = pRange->MoveEnd(tomCharacter, -1, &l);
Assert(hr == NOERROR);
}
if (NOERROR != pRange->Delete(tomCharacter, 0, &l) && _nCount > 1)
{
Unfreeze();
pRange->Release();
return FALSE;
}
pRange->Release();
int nOldCt = _nCount;
_nCount -= (nEnd - nStart) + 1;
// Because we delete the paragraph preceeding the item
// rather than following the item we need to update
// the paragraph which followed the item. bug fix #4074
long nFmtPara = max(nStart -1, 0);
if (!_fOwnerDraw && (IsSelected(nEnd) != IsSelected(nFmtPara) || _nCount == 0))
{
DWORD dwFore = (unsigned)tomAutoColor;
DWORD dwBack = (unsigned)tomAutoColor;
if (IsSelected(nFmtPara) && _nCount)
{
dwFore = _crSelFore;
dwBack = _crSelBack;
}
SetColors(dwFore, dwBack, nFmtPara, nFmtPara);
}
// update our internal listbox records
int j = nEnd + 1;
for(int i = nStart; j < nOldCt; i++, j++)
{
_rgData[i]._fSelected = _rgData.Get(j)._fSelected;
_rgData[i]._dwData = _rgData.Get(j)._dwData;
}
//bug fix #5397
//we need to reset the internal array containing information
//about previous items
while (--j >= _nCount)
{
_rgData[j]._fSelected = 0;
_rgData[j]._dwData = 0;
}
if (_nCount > 0)
{
// update the cursor
if (nStart <= _nCursor)
_nCursor--;
_nCursor = min(_nCursor, _nCount - 1);
if (_fLstType == kExtended)
{
if (_nCursor < 0)
{
_nOldCursor = min(_nAnchor, _nCount - 1);
_nAnchor = -1;
}
else if (_nAnchor >= 0)
{
if (nStart <= _nAnchor && _nAnchor <= nEnd)
{
// Store the old anchor for future use
_nOldCursor = min(_nAnchor, _nCount - 1);
_nAnchor = -1;
}
}
}
if (_fOwnerDraw)
{
RECT rcStart;
RECT rcEnd;
LbGetItemRect(nStart, &rcStart);
LbGetItemRect(nEnd, &rcEnd);
rcStart.bottom = rcEnd.bottom;
if (IntersectRect(&rcStart, &rcStart, &_rcViewport))
{
// the list will get bumped up so we need to redraw
// everything from the top to the bottom
rcStart.bottom = _rcViewport.bottom;
::InvalidateRect(_hwnd, &rcStart, TRUE);
}
}
}
else
{
SetTopViewableItem(0);
_nAnchor = -1;
_nCursor = -1;
}
//For ownerdraw cases where the item height is less than the font we need to manually
//enable the scrollbar if we need the scrollbar and the scrollbar is disabled.
if ((_nyItem < _nyFont) && (_fDisableScroll) &&
(_nCount <= _nViewSize) && (nOldCt > _nViewSize))
TxEnableScrollBar(SB_VERT, ESB_DISABLE_BOTH);
LbDeleteItemNotify(nStart, nEnd);
Assert(GetTopIndex() >= 0);
if (_nCount)
LbShowIndex(min(GetTopIndex(), _nCount - 1), FALSE);
Unfreeze();
return TRUE;
}
/*
* inline CLstBxWinHost::Freeze()
*
* @mfunc
* Prevents TOM from drawing
*
* #rdesc
* <none>
*/
void CLstBxWinHost::Freeze()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::Freeze");
long l;
((CTxtEdit*)_pserv)->Freeze(&l);
}
/*
* inline CLstBxWinHost::FreezeCount()
*
* @mfunc
* Returns the current freeze count
*
* #rdesc
* <none>
*/
short CLstBxWinHost::FreezeCount() const
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetFreezeCount");
return ((CTxtEdit*)_pserv)->GetFreezeCount();
}
/*
* inline CLstBxWinHost::Unfreeze()
*
* @mfunc
* Allows TOM to update itself
*
* #rdesc
* <none>
*/
void CLstBxWinHost::Unfreeze()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::Unfreeze");
long l;
((CTxtEdit*)_pserv)->Unfreeze(&l);
// HACK ALERT!
// When ITextRange::ScrollIntoView starts caching the scroll position
// in cases where the display is frozen the following code can be removed
// We could have failed in ITextRange::ScrollIntoView
// Check if we did and try calling it again
if (!l && _stvidx >= 0)
{
ScrollToView(_stvidx);
_stvidx = -1;
}
}
/*
* CLstBxWinHost::ScrollToView(long)
*
* @mfunc
* Sets the given index to be at the top of
* the viewable window space
*
* #rdesc
* BOOL = if function succeeded ? TRUE : FALSE
*/
BOOL CLstBxWinHost::ScrollToView(long nTop)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetTopViewableItem");
//Get the range which contains the item desired
BOOL bVal = FALSE;
ITextRange* pRange = NULL;
if (!GetRange(nTop, nTop, &pRange))
return bVal;
Assert(pRange);
CHECKNOERROR(pRange->Collapse(1));
CHECKNOERROR(pRange->ScrollIntoView(tomStart + /* TA_STARTOFLINE */ 32768));
bVal = TRUE;
CleanExit:
pRange->Release();
// HACK ALERT!
// When ITextRange::ScrollIntoView starts caching the scroll position
// in cases where the display is frozen the following code can be removed
//if we failed record the index we failed to scroll to
if (!bVal && FreezeCount())
_stvidx = nTop;
return bVal;
}
/*
* CLstBxWinHost::SetTopViewableItem(long)
*
* @mfunc
* Sets the given index to be at the top of
* the viewable window space
*
* #rdesc
* BOOL = if function succeeded ? TRUE : FALSE
*/
BOOL CLstBxWinHost::SetTopViewableItem(long nTop)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetTopViewableItem");
// if we don't have any items in the list box then just set the topindex to
// zero
if (_nCount == 0)
{
Assert(nTop == 0);
_nTopIdx = 0;
return TRUE;
}
// don't do anything if the requested top index is greater
// then the amount of items in the list box
Assert(nTop < _nCount);
if (nTop >= _nCount)
return FALSE;
// Don't do this if it's ownerdraw
if (!_fOwnerDraw)
{
// Since we erase and draw the focus rect here
// cache the focus rect info and don't bother with the
// focus rect stuff until later
int fFocus = _fFocus;
_fFocus = 0;
if (fFocus && IsItemViewable(GetCursor()))
SetCursor(NULL, GetCursor(), TRUE);
//Get the range which contains the item desired
long nOldIdx = _nTopIdx;
_nTopIdx = nTop;
if (!ScrollToView(nTop))
{
// HACK ALERT!
// When ITextRange::ScrollIntoView starts caching the scroll position
// in cases where the display is frozen the following code can be removed
if (_stvidx >= 0)
return TRUE;
// Something went wrong and we weren't able to display the index requested
// reset top index
_nTopIdx = nOldIdx;
}
// Note:
// If the cursor was not viewable then we don't attempt
// to display the focus rect because we never erased it
_fFocus = fFocus;
if (_fFocus & IsItemViewable(GetCursor()))
{
// Now we need to redraw the focus rect which we erased
SetCursor(NULL, GetCursor(), FALSE);
}
}
else
{
int dy = (_nTopIdx - nTop) * _nyItem;
RECT rc;
TxGetClientRect(&rc);
_nTopIdx = nTop;
TxScrollWindowEx(0, dy, NULL, &rc, NULL, NULL,
SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN);
SetScrollInfo(SB_VERT, TRUE); // we update the scrollbar manually if we are in ownerdraw mode
UpdateWindow(_hwnd);
}
return TRUE;
}
/*
* CLstBxWinHost::GetRange(long, long, ITextRange**)
*
* @mfunc
* Sets the range given the top and bottom index
* by storing the range into ITextRange
*
* #rdesc
* BOOL = if function succeeded ? TRUE : FALSE
*/
BOOL CLstBxWinHost::GetRange(long nTop, long nBottom, ITextRange** ppRange)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetRange");
// do some error checking
if (nTop < 0 || nTop > _nCount || nBottom < 0 || nBottom > _nCount)
return FALSE;
Assert(ppRange);
if (NOERROR != ((CTxtEdit*)_pserv)->Range(0, 0, ppRange))
{
Assert(FALSE);
return FALSE;
}
Assert(*ppRange);
// convert index to a 1-based index
nTop++;
nBottom++;
long l;
CHECKNOERROR((*ppRange)->SetIndex(tomParagraph, nTop, 1));
if (nBottom > nTop)
{
CHECKNOERROR((*ppRange)->MoveEnd(tomParagraph, nBottom - nTop, &l));
}
return TRUE;
CleanExit:
Assert(FALSE);
(*ppRange)->Release();
*ppRange = NULL;
return FALSE;
}
/*
* CLstBxWinHost::SetColors(DWORD, DWORD, long, long)
*
* @mfunc
* Sets the background color for the givin range of paragraphs. This
* only operates in terms of paragraphs.
*
* #rdesc
* BOOL = if function succeeded in changing different color
*/
BOOL CLstBxWinHost::SetColors(DWORD dwFgColor, DWORD dwBgColor, long nParaStart, long nParaEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::SetColors");
Assert(_fOwnerDraw == 0);
//Get the range of the index
ITextRange* pRange;
if (!GetRange(nParaStart, nParaEnd, &pRange))
return FALSE;
BOOL bRet = FALSE;
ITextFont* pFont;
#ifdef DEBUG
// Check if the background and foreground really is different
// for debugging purposes
CHECKNOERROR(pRange->GetFont(&pFont));
Assert(pFont);
if (nParaStart == nParaEnd && _fLstType != kCombo)
{
long lColor;
CHECKNOERROR(pFont->GetBackColor(&lColor));
Assert((DWORD)lColor != dwBgColor || _nCount == 0);
CHECKNOERROR(pFont->GetForeColor(&lColor));
Assert((DWORD)lColor != dwFgColor || _nCount == 0);
}
pFont->Release();
#endif //_DEBUG
// Set the background and forground color
if (NOERROR != pRange->GetFont(&pFont))
{
pRange->Release();
return FALSE;
}
Assert(pFont);
CHECKNOERROR(pFont->SetBackColor(dwBgColor));
CHECKNOERROR(pFont->SetForeColor(dwFgColor));
bRet = TRUE;
CleanExit:
// Release pointers
pFont->Release();
pRange->Release();
return bRet;
}
///////////////////////////// Message Map Functions ////////////////////////////////
/*
* void CLstBxWinHost::OnSetCursor()
*
* @mfunc
* Handles the WM_SETCURSOR message.
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnSetCursor()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnSetCursor");
// Just make sure the cursor is an arrow if it's over us
TxSetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)), NULL);
return 1;
}
/*
* void CLstBxWinHost::OnSysColorChange()
*
* @mfunc
* Handles the WM_SYSCOLORCHANGE message.
*
* #rdesc
* LRESULT = return value after message is processed
*/
void CLstBxWinHost::OnSysColorChange()
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnSysColorChange");
if (!_fOwnerDraw)
{
// set the new colors
COLORREF crDefBack = _crDefBack;
COLORREF crDefFore = _crDefFore;
COLORREF crSelBack = _crSelBack;
COLORREF crSelFore = _crSelFore;
// update colors
UpdateSysColors();
// optimization check; don't do anything if there are no elements
if (_nCount <= 0)
return;
// Only update the list box if colors changed
if (crDefBack != _crDefBack || crDefFore != _crDefFore ||
crSelBack != _crSelBack || crSelFore != _crSelFore)
{
//Bug fix #4847
// notify parent first
CTxtWinHost::OnSysColorChange();
int nStart = 0;
int nEnd = 0;
BOOL bSelection = _rgData.Get(0)._fSelected;
for (int i = 1; i < _nCount; i++)
{
if (_rgData.Get(i)._fSelected != (unsigned)bSelection)
{
// Update the colors only for selections
if (bSelection)
SetColors(_crSelFore, _crSelBack, nStart, nStart + nEnd);
// Update our cache to reflect the value of our current index
bSelection = _rgData.Get(i)._fSelected;
nStart = i;
nEnd = 0;
}
else
nEnd++;
}
// there was some left over so change the color for these
if (bSelection)
SetColors(_crSelFore, _crSelBack, nStart, nStart + nEnd);
}
}
}
/*
* LRESULT CLstBxWinHost::OnChar(WORD, DWORD)
*
* @mfunc
* Handles the WM_CHAR message.
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnChar(WORD vKey, DWORD lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnChar");
// don't do anything if list box is empty or in the middle of
// a mouse down
if (_fMouseDown || _nCount == 0)
return 0;
BOOL fControl = (GetKeyState(VK_CONTROL) < 0);
int nSel = -1;
switch (vKey)
{
case VK_ESCAPE:
InitSearch();
return 0;
case VK_BACK:
if (_pwszSearch && _nidxSearch)
{
if (_nidxSearch > 0)
_nidxSearch--;
_pwszSearch[_nidxSearch] = NULL;
break; // we break out of case because we still want to perform the search
}
return 0;
case VK_SPACE:
if (_fLstType == kMultiple)
return 0;
/* Fall through case */
default:
// convert CTRL+char to char
if (fControl && vKey < 0x20)
vKey += 0x40;
// don't go beyond the search array size
if (_nidxSearch >= LBSEARCH_MAXSIZE)
{
((CTxtEdit*)_pserv)->Beep();
return 0;
}
// allocate string if not already allocated
if (_pwszSearch == NULL)
_pwszSearch = new WCHAR[LBSEARCH_MAXSIZE];
// error checking
if (_pwszSearch == NULL)
{
((CTxtEdit*)_pserv)->Beep();
Assert(FALSE && "Unable to allocate search string");
return 0;
}
// put the input character into string array
_pwszSearch[_nidxSearch++] = (WCHAR)vKey;
_pwszSearch[_nidxSearch] = NULL;
}
if (_fSort)
{
nSel = (_fSearching) ? _nCursor + 1 : 0;
// Start the search for a string
TxSetTimer(ID_LB_SEARCH, ID_LB_SEARCH_DEFAULT);
_fSearching = 1;
}
else
{
_nidxSearch = 0;
nSel = _nCursor + 1;
}
// Make sure our index isn't more than the items we have
if (nSel >= _nCount)
nSel = 0;
int nRes = LbFindString(nSel, _pwszSearch, FALSE);
if (nRes < 0)
{
if (_pwszSearch)
{
if (_nidxSearch > 0)
_nidxSearch--;
if (_nidxSearch == 1 && _pwszSearch[0] == _pwszSearch[1])
{
_pwszSearch[1] = NULL;
nRes = LbFindString(nSel, _pwszSearch, FALSE);
}
}
}
// If a matching string is found then select it
if (nRes >= 0)
OnKeyDown(nRes, 0, 1);
// If Hi-Ansi need to send a wm_syskeyup message to ITextServices to
// stabalize the state
if (0x80 <= vKey && vKey <= 0xFF && !HIWORD(GetKeyState(VK_MENU)))
{
LRESULT lres;
_pserv->TxSendMessage(WM_SYSKEYUP, VK_MENU, 0xC0000000, &lres);
}
return 0;
}
/*
* LRESULT CLstBxWinHost::OnKeyDown(WPARAM, LPARAM, INT)
*
* @mfunc
* Handles the WM_KEYDOWN message. The BOOL ff is used as a flag for calls
* made internally and not responsive to the WM_KEYDOWN message. Since this
* function is used for other things, ie helper to dealing with the WM_CHAR message.
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnKeyDown(WPARAM vKey, LPARAM lparam, int ff)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnKeyDown");
// 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.
if (_fMouseDown || (_nCount == 0 && vKey != VK_F4))
return 1;
// Check if the shift key is down for Extended listbox style only
int ffShift = 0;
if (_fLstType == kExtended)
ffShift = HIWORD(GetKeyState(VK_SHIFT));
// Special case!
// Check if this function is called as a helper
int nSel = (ff) ? vKey : -1;
#if 0
if (_fNotify && ff == 0)
{
// NOTE: LBS_WANTKEYBOARDINPUT
// To support LBS_WANTKEYBOARDINPUT the following comment has to be done
// Need to send parentwindow the keydown message
// According to documenation we notify the parent the key was pressed
// if the parent returns -2 then we don't do anything and immediately exit out
// if the parent returns >=0 then we just jump to that index else
// we just continue with the default procedure.
}
#endif
TxKillTimer(ID_LB_CAPTURE);
if (nSel < 0)
{
// Need to set the selection so find the new selection
// based on the virtual key pressed
switch (vKey)
{
// UNDONE: Later, not language independent!!!
// Need to find-out how NT5.0 determines the slash issue??
case VERKEY_BACKSLASH:
// Deselect everything if we are in extended mode
if (HIWORD(GetKeyState(VK_CONTROL)) && _fLstType == kExtended)
{
// NOTE:
// Winnt loses the anchor and performing a shift+<vkey>
// doesn't select any items. Instead, it just moves the
// cursor w/o selecting the current cursor
_nAnchor = -1;
LbSetSelection(_nCursor, _nCursor, LBSEL_RESET | LBSEL_SELECT, 0, 0);
TxNotify(LBN_SELCHANGE, NULL);
}
return 1;
case VK_DIVIDE:
case VERKEY_SLASH:
// Select everything if we are in extended mode
if (HIWORD(GetKeyState(VK_CONTROL)) && _fLstType == kExtended)
{
// NOTE:
// Winnt behaves as we expect. In other words the anchor
// isn't changed and neither is the cursor
LbSetSelection(0, _nCount - 1, LBSEL_SELECT, 0, 0);
TxNotify(LBN_SELCHANGE, NULL);
}
return 1;
case VK_SPACE:
// just get out if there is nothing to select
if (_nCursor < 0 && !GetCount())
return 1;
// Just select current item
nSel = _nCursor;
break;
case VK_PRIOR:
// move the cursor up enough so the current item which the cursor
// is pointing to is at the bottom and the new cursor position is at the top
nSel = _nCursor - _nViewSize + 1;
if (nSel < 0)
nSel = 0;
break;
case VK_NEXT:
// move the cursor down enough so the current item which the cursor
// is point is at the top and the new cursor position is at the bottom
nSel = _nCursor + _nViewSize - 1;
if (nSel >= _nCount)
nSel = _nCount - 1;
break;
case VK_HOME:
// move to the top of the list
nSel = 0;
break;
case VK_END:
// move to the bottom of the list
nSel = _nCount - 1;
break;
case VK_LEFT:
case VK_UP:
nSel = (_nCursor > 0) ? _nCursor - 1 : 0;
break;
case VK_RIGHT:
case VK_DOWN:
nSel = (_nCursor < _nCount - 1) ? _nCursor + 1 : _nCount - 1;
break;
case VK_RETURN:
case VK_F4:
case VK_ESCAPE:
if (_fLstType == kCombo)
{
Assert(_pcbHost);
int nCursor = (vKey == VK_RETURN) ? GetCursor() : _nOldCursor;
_pcbHost->SetSelectionInfo(vKey == VK_RETURN, nCursor);
LbSetSelection(nCursor, nCursor, LBSEL_RESET |
((nCursor == -1) ? 0 : LBSEL_NEWCURSOR | LBSEL_SELECT), nCursor, nCursor);
OnCBTracking(LBCBM_END, 0); // we need to do this because we may have some extra messages
// in our message queue which can change the selections
::SendMessage(_hwndParent, LBCB_TRACKING, 0, 0);
}
// NOTE:
// We differ from Winnt here in that we expect the
// combobox window handler to do all the positioning and
// showing of the list box. So when we get this message
// and we are part of a combobox we should notify the
// combobox and in turn the combobox should immediately close us.
//return 1;
//case VK_F8: // not suppported
// We need to return this to pserv to process these keys
/*
case VK_MENU:
case VK_CONTROL:
case VK_SHIFT:
return 1;
*/
default:
return 1;
}
}
// There can be cases where nSel = -1; _nCursor = -1 && _nViewSize = 1
// make sure the selection index is valid
if (nSel < 0)
nSel = 0;
// Should the cursor be set at the top or bottom of the list box??
BOOL bTop = (_nCursor > nSel) ? TRUE : FALSE;
Freeze();
if (_fLstType == kMultiple)
{
if (vKey == VK_SPACE)
{
BOOL fSel = IsSelected(nSel);
if (LbSetSelection(nSel, nSel, LBSEL_NEWCURSOR | (IsSelected(nSel) ? 0 : LBSEL_SELECT), nSel, 0))
{
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_FOCUS;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
if (fSel)
_dwWinEvent = EVENT_OBJECT_SELECTIONREMOVE;
#endif
}
}
else
{
SetCursor(NULL, nSel, TRUE);
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_FOCUS;
#endif
}
}
else
{
if (ffShift && _fLstType == kExtended)
{
// Set the anchor if it already isn't set
_nOldCursor = -1;
if (_nAnchor < 0)
_nAnchor = nSel;
LbSetSelection(_nAnchor, nSel, LBSEL_RESET | LBSEL_SELECT | LBSEL_NEWCURSOR, nSel, 0);
}
else
{
// if the selected item is already selected then
// just exit out
if (_nCursor == nSel && IsSelected(_nCursor))
{
Unfreeze();
return 1;
}
LbSetSelection(nSel, nSel, LBSEL_DEFAULT, nSel, nSel);
}
}
// LbShowIndex eventually calls ScrollToView which fails if display is frozen
Unfreeze();
// Make sure the selection is visible
LbShowIndex(nSel, bTop);
// key presses qualify as ok selections so we have to update the old cursor position
TxNotify(LBN_SELCHANGE, NULL);
_nOldCursor = _nCursor;
return 1;
}
/*
* LRESULT CLstBxWinHost::OnTimer(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_TIMER message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnTimer(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnTimer");
// Check which timer we have
switch (wparam)
{
case ID_LB_CAPTURE:
// for mouse movements let mousemove handler deal with it
if (_fCapture)
{
POINT pt;
::GetCursorPos(&pt);
// Must convert to client coordinates to mimic the mousemove call
TxScreenToClient(&pt);
OnMouseMove(0, MAKELONG(pt.x, pt.y));
}
break;
case ID_LB_SEARCH:
// for type search. If we get here means > 2 seconds elapsed before last
// character was typed in so reset type search and kill the timer
InitSearch();
TxKillTimer(ID_LB_SEARCH);
break;
default:
return 1;
}
return 0;
}
/*
* LRESULT CLstBxWinHost::OnVScroll(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_VSCROLL message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnVScroll(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnVScroll");
if (_nCount <= _nViewSize)
return 0;
int nCmd = LOWORD(wparam);
int nIdx = 0;
switch (nCmd)
{
case SB_TOP:
nIdx = 0;
break;
case SB_BOTTOM:
nIdx = _nCount - _nViewSize;
if (nIdx < 0)
nIdx = 0;
break;
case SB_LINEDOWN:
nIdx = GetTopIndex() + 1;
break;
case SB_LINEUP:
nIdx = GetTopIndex() - 1;
if (nIdx < 0)
nIdx = 0;
break;
case SB_PAGEDOWN:
nIdx = GetTopIndex() + _nViewSize;
if (nIdx > (_nCount - _nViewSize))
nIdx = _nCount - _nViewSize;
break;
case SB_PAGEUP:
nIdx = GetTopIndex() - _nViewSize;
if (nIdx < 0)
nIdx = 0;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
// NOTE:
// if the list box is expected to hold more that 0xffff items
// then we need to modify this code to call GetScrollInfo.
nIdx = HIWORD(wparam) / _nyItem;
break;
// Don't need to do anything for this case
case SB_ENDSCROLL:
return 0;
}
LbSetTopIndex(nIdx);
return 0;
}
/*
* LRESULT CLstBxWinHost::OnCaptureChanged(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_CAPTURECHANGED message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnCaptureChanged(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnCaptureChanged");
if (_fCapture)
{
POINT pt;
::GetCursorPos(&pt);
::ScreenToClient(_hwnd, &pt);
// prevent us from trying to release capture since we don't have
// it anyways by set flag and killing timer
_fCapture = 0;
TxKillTimer(ID_LB_CAPTURE);
OnLButtonUp(0, MAKELONG(pt.y, pt.x), LBN_SELCANCEL);
}
return 0;
}
//FUTURE:
// Do we need to support ReadModeHelper?
/*
* LRESULT CLstBxWinHost::OnMouseWheel(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_MOUSEWHEEL message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnMouseWheel(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnMouseWheel");
// we don't to any zooms or anything of the sort
if ((wparam & MK_CONTROL) == MK_CONTROL)
return 1;
// Check if the scroll is ok w/ the listbox requirements
LRESULT lReturn = 1;
short delta = (short)(HIWORD(wparam));
_cWheelDelta -= delta;
if ((abs(_cWheelDelta) >= WHEEL_DELTA) && (_nCount > _nViewSize) && (_dwStyle & WS_VSCROLL ))
{
// shut-off timer for right now
TxKillTimer(ID_LB_CAPTURE);
Assert(delta != 0);
int nlines = W32->GetRollerLineScrollCount();
if (nlines == -1)
{
OnVScroll(MAKELONG((delta < 0) ? SB_PAGEUP : SB_PAGEDOWN, 0), 0);
}
else
{
//Calculate the number of lines to scroll
nlines *= _cWheelDelta/WHEEL_DELTA;
//Perform some bounds checking
nlines = min(_nViewSize - 1, nlines);
int nIdx = max(0, nlines + GetTopIndex());
nIdx = min(nIdx, _nCount - _nViewSize);
if (nIdx != GetTopIndex())
{
// Scroll bar is based in pixels so figure-out the pixel value
OnVScroll(MAKELONG(SB_THUMBPOSITION, nIdx * _nyItem), 0);
}
}
OnVScroll(MAKELONG(SB_ENDSCROLL, 0), 0);
_cWheelDelta %= WHEEL_DELTA;
}
return lReturn;
}
/*
* LRESULT CLstBxWinHost::OnLButtonUp(WPARAM, LPARAM, int)
*
* @mfunc
* Handles the WM_LBUTTONUP and WM_CAPTURECHANGED message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnLButtonUp(WPARAM wparam, LPARAM lparam, int ff)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnLButtonUp");
// if mouse wasn't down then exit out
if (!_fMouseDown)
return 0;
_fMouseDown = 0;
POINT pt;
POINTSTOPOINT(pt, lparam);
if (_fLstType == kCombo)
{
Assert(_fCapture);
// Check if user clicked outside the list box
// if so this signifies the user cancelled and we
// should send a message to the parentwindow
if (!PointInRect(&pt))
{
//User didn't click in listbox so reselect old item
LbSetSelection(_nOldCursor, _nOldCursor, LBSEL_DEFAULT, _nOldCursor, _nOldCursor);
ff = 0;
}
else
ff = LBN_SELCHANGE; //item changed so notify parent
_pcbHost->SetSelectionInfo(ff == LBN_SELCHANGE, GetCursor());
OnCBTracking(LBCBM_END, 0);
::PostMessage(_hwndParent, LBCB_TRACKING, LBCBM_END, 0);
}
else
{
// Kill any initializations done by mouse down...
_fMouseDown = 0;
_nOldCursor = -1;
}
if (_fCapture)
{
TxKillTimer(ID_LB_CAPTURE);
_fCapture = 0;
TxSetCapture(FALSE);
}
if (ff)
{
#ifndef NOACCESSIBILITY
if (ff == LBN_SELCHANGE)
{
_dwWinEvent = EVENT_OBJECT_FOCUS;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
if (!IsSelected(_nCursor))
{
_dwWinEvent = EVENT_OBJECT_SELECTIONREMOVE;
}
}
#endif
// Send notification if a notification exists
TxNotify(ff, NULL);
}
return 1;
}
/*
* LRESULT CLstBxWinHost::OnMouseMove(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_MOUSEMOVE message and possibly the
* WM_TIMER message for tracking mouse movements
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnMouseMove(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnMouseMove");
// bug fix #4998
// Check if previous mouse position is the same as current, if it is
// then this is probably a bogus message from PPT.
POINT pt;
POINTSTOPOINT(pt, lparam);
if (_nPrevMousePos == lparam && PtInRect(&_rcViewport, pt))
return 0;
_nPrevMousePos = lparam;
// This routine will only start the autoscrolling of the listbox
// The autoscrolling is done using a timer where the and the elapsed
// time is determined by how far the mouse is from the top and bottom
// of the listbox. The farther from the listbox the faster the timer
// will be. This function relies on the timer to scroll and select
// items.
// We get here if mouse cursor is in the list box.
int idx = GetItemFromPoint(&pt);
// We only do the following if mouse is down
if (_fMouseDown)
{
int y = (short)pt.y;
if (y < 0 || y > _rcViewport.bottom - 1)
{
// calculate the new timer settings
int dist = y < 0 ? -y : (y - _rcViewport.bottom + 1);
int nTimer = ID_LB_CAPTURE_DEFAULT - (int)((WORD)dist << 4);
// Scroll up or down depending on the mouse pos relative
// to the list box
idx = (y <= 0) ? max(0, idx - 1) : min(_nCount - 1, idx + 1);
if (idx >= 0 && idx < _nCount)
{
// The ordering of this is VERY important to prevent screen
// flashing...
if (idx != _nCursor)
MouseMoveHelper(idx, (_fLstType == kCombo) ? FALSE : TRUE);
OnVScroll(MAKELONG((y < 0) ? SB_LINEUP : SB_LINEDOWN, 0), 0);
}
// reset timer
TxSetTimer(ID_LB_CAPTURE, (5 > nTimer) ? 5 : nTimer);
return 0;
}
// Don't select if we are part of a combo box and mouse is outside client area
else if (_fLstType == kCombo && (pt.x < 0 || pt.x > _rcViewport.right - 1))
return 0;
}
else if (!PointInRect(&pt))
{
return 0;
}
if (idx != _nCursor || (_fLstType == kCombo && idx >= 0 && !IsSelected(idx)))
{
// Prevent flashing by not redrawing if index
// didn't change
Assert(idx >= 0);
MouseMoveHelper(idx, TRUE);
}
return 0;
}
/*
* LRESULT CLstBxWinHost::OnLButtonDown(WPARAM, LPARAM)
*
* @mfunc
* Handles the WM_LBUTTONDOWN message
*
* #rdesc
* LRESULT = return value after message is processed
*/
LRESULT CLstBxWinHost::OnLButtonDown(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnLButtonDown");
POINT pt;
POINTSTOPOINT(pt, lparam);
if (_fCapture)
{
// Need to check if the listbox is part of a combobox, if so
// then we need to notify the parent class.
if (_fLstType == kCombo)
{
// Need to perform the following
// - check if click is within client area of combo box if not then
// behave as if user cancelled
if (!PointInRect(&pt))
{
// reset our double click flag because we could be double clicking on the scrollbar
_fDblClick = 0;
// check if the scroll bar was clicked
// mouse message won't get posted unless we release it
// for a short while
TxClientToScreen(&pt);
// check if user clicked on the scrollbar
if (HTVSCROLL == SendMessage(_hwnd, WM_NCHITTEST, 0, MAKELONG(pt.x, pt.y)))
{
if (_fCapture)
{
_fCapture = 0;
TxSetCapture(FALSE);
}
SendMessage(_hwnd, WM_NCLBUTTONDOWN, HTVSCROLL, MAKELONG(pt.x, pt.y));
TxSetCapture(TRUE);
_fCapture = 1;
}
else
{
// if user didn't click the scrollbar then notify parent and stop
// tracking else just get out
Assert(_pcbHost);
_pcbHost->SetSelectionInfo(FALSE, _nOldCursor);
LbSetSelection(_nOldCursor, _nOldCursor, LBSEL_RESET |
((_nOldCursor == -1) ? 0 : LBSEL_NEWCURSOR | LBSEL_SELECT),
_nOldCursor, _nOldCursor);
OnCBTracking(LBCBM_END, 0);
SendMessage(_hwndParent, LBCB_TRACKING, 0, 0);
}
return 0;
}
}
}
int idx = GetItemFromPoint(&pt);
if (idx <= -1)
{
_fDblClick = 0;
return 0;
}
_fMouseDown = 1;
// if the message was a double click message than don't need to go
// any further just fake a mouseup message to get back to a normal
// state
if (_fDblClick)
{
_fDblClick = 0;
OnLButtonUp(wparam, lparam, LBN_DBLCLK);
return 0;
}
// Set the timer in case the user scrolls outside the listbox
if (!_fCapture)
{
TxSetCapture(TRUE);
_fCapture = 1;
TxSetTimer(ID_LB_CAPTURE, ID_LB_CAPTURE_DEFAULT);
}
int ffVirtKey = LBKEY_NONE;
if (_fLstType == kExtended)
{
if (HIWORD(GetKeyState(VK_SHIFT)))
ffVirtKey |= LBKEY_SHIFT;
if (HIWORD(GetKeyState(VK_CONTROL)))
ffVirtKey |= LBKEY_CONTROL;
}
int ff = 0;
int i = 0;
int nStart = idx;
int nEnd = idx;
int nAnchor = _nAnchor;
switch (ffVirtKey)
{
case LBKEY_NONE:
// This case accounts for listbox styles with kSingle, kMultiple, and
// kExtended w/ no keys pressed
if (_fLstType == kMultiple)
{
ff = (IsSelected(idx) ? 0 : LBSEL_SELECT) | LBSEL_NEWANCHOR | LBSEL_NEWCURSOR;
}
else
{
// keep a copy of the old cursor position around for combo cancells
ff = LBSEL_DEFAULT;
}
nAnchor = idx;
break;
case LBKEY_SHIFT:
// Now select all the items between the anchor and the current selection
// The problem is LbSetSelection expects the first index to be less then
// or equal to the second index so we have to manage the Anchor and index
// ourselves..
ff = LBSEL_SELECT | LBSEL_RESET | LBSEL_NEWCURSOR;
i = !(IsSelected(_nAnchor));
if (_nAnchor == -1)
{
ff |= LBSEL_NEWANCHOR;
nAnchor = idx;
}
else if (_nAnchor > idx)
{
nEnd = _nAnchor - i;
}
else if (_nAnchor < idx)
{
nEnd = _nAnchor + i;
}
else if (i) // _nAnchor == idx && idx IS selected
{
ff = LBSEL_RESET;
nStart = 0;
nEnd = 0;
}
break;
case LBKEY_CONTROL:
// Toggle the selected item and set the new anchor and cursor
// positions
ff = LBSEL_NEWCURSOR | LBSEL_NEWANCHOR | (IsSelected(idx) ? 0 : LBSEL_SELECT);
nAnchor = idx;
break;
case LBKEY_SHIFTCONTROL:
// De-select any items between the cursor and the anchor (excluding the anchor)
// and select or de-select the new items between the anchor and the cursor
// Set the anchor if it already isn't set
if (_nAnchor == -1)
_nAnchor = (_nOldCursor >= 0) ? _nOldCursor : idx;
// Just deselect all items between the cursor and the anchor
if (_nCursor != _nAnchor)
{
// remove selection from old cursor position to the current anchor position
LbSetSelection(_nCursor, (_nCursor > _nAnchor) ? _nAnchor + 1 : _nAnchor - 1, 0, 0, 0);
}
// Check if we used a temporary anchor if so then set the anchor to
// idx because we don't want the temporary anchor to be the actual anchor
if (_nOldCursor >= 0)
{
_nOldCursor = -1;
_nAnchor = idx;
}
// Set the state of all items between the new Cursor (idx) and
// the anchor to the state of the anchor
ff = LBSEL_NEWCURSOR | (IsSelected(_nAnchor) ? LBSEL_SELECT : 0);
nEnd = _nAnchor;
break;
default:
Assert(FALSE && "Should not be here!!");
}
if (LbSetSelection(nStart, nEnd, ff, idx, nAnchor))
{
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_FOCUS;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
#endif
}
return 0;
}
/////////////////////////// ComboBox Helper Functions //////////////////////////////
/*
* void CLstBxWinHost::OnCBTracking(WPARAM, LPARAM)
*
* @mfunc
* This should be only called by the combo box. This is a general message used
* to determine the state the listbox should be in
* #rdesc
* void
*/
void CLstBxWinHost::OnCBTracking(WPARAM wparam, LPARAM lparam)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::OnCBTracking");
Assert(_pcbHost);
Assert(_hwndParent);
switch (wparam)
{
// lparam = Set focus to listbox
case LBCBM_PREPARE:
Assert(IsWindowVisible(_hwnd));
_fMouseDown = FALSE;
if (lparam & LBCBM_PREPARE_SAVECURSOR)
_nOldCursor = GetCursor();
if (lparam & LBCBM_PREPARE_SETFOCUS)
{
_fFocus = 1;
TxSetFocus();
}
InitWheelDelta();
break;
// lparam = mouse is down
case LBCBM_START:
Assert(IsWindowVisible(_hwnd));
_fMouseDown = !!lparam;
TxSetCapture(TRUE);
_fCapture = 1;
break;
// lparam = Keep capture
case LBCBM_END:
TxKillTimer(ID_LB_CAPTURE);
_fFocus = 0;
if (_fCapture)
{
_fCapture = FALSE;
TxSetCapture(FALSE);
}
break;
default:
AssertSz(FALSE, "ALERT: Custom message being used by someone else");
}
}
/////////////////////////////// ListBox Functions //////////////////////////////////
/*
* void CLstBxWinHost::LbDeleteItemNotify(int, int)
*
* @mfunc
* Sends message to the parent an item has been deleted. This function should be
* called whenever the LB_DELETESTRING message is recieved or if the listbox is
* being destroyed and the listbox is owner draw
*
* #rdesc
* void
*/
void CLstBxWinHost::LbDeleteItemNotify(int nStart, int nEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbDeleteItemNotify");
// Initialize structure
UINT ctlType;
DELETEITEMSTRUCT ds;
switch (_fLstType)
{
case kSingle:
case kMultiple:
case kExtended:
ctlType = ODT_LISTBOX;
break;
default:
ctlType = ODT_COMBOBOX;
}
for(long i = nStart; i <= nEnd; i++)
{
// We do this just in case the user decides to change
// the structure
ds.CtlType = ctlType;
ds.CtlID = _idCtrl;
ds.hwndItem = _hwnd;
ds.itemData = GetData(i);
ds.itemID = i;
::SendMessage(_hwndParent, WM_DELETEITEM, _idCtrl, (LPARAM)&ds);
}
}
/*
* void CLstBxWinHost::LbDrawItemNotify(HDC, int, UINT, UINT)
*
* @mfunc
* 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.
*
* #rdesc
* void
*/
void CLstBxWinHost::LbDrawItemNotify(HDC hdc, int nIdx, UINT itemAction, UINT itemState)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbDrawItemNotify");
// Only send the message if the item is viewable
if (!IsItemViewable(nIdx))
return;
//Fill the DRAWITEMSTRUCT with the unchanging constants
DRAWITEMSTRUCT dis;
dis.CtlType = ODT_LISTBOX;
dis.CtlID = _idCtrl;
// 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)(nIdx < _nCount ? nIdx : -1);
dis.itemAction = itemAction;
dis.hwndItem = _hwnd;
dis.hDC = hdc;
dis.itemState = itemState |
(UINT)(_fDisabled ? ODS_DISABLED : 0);
// Set the app supplied data
if (_nCount == 0)
{
// If no items, just use 0 for data. This is so that we
// can display a caret when there are no items in the listbox.
dis.itemData = 0L;
}
else
{
Assert(nIdx < _nCount);
dis.itemData = GetData(nIdx);
}
LbGetItemRect(nIdx, &(dis.rcItem));
/*
* 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(_hwndParent, WM_DRAWITEM, _idCtrl, (LPARAM)&dis);
}
/*
* BOOL CLstBxWinHost::LbSetItemHeight(int)
*
* @mfunc
* Sets the height of the items within the given range [0, _nCount -1]
*
* #rdesc
* BOOL = Successful ? TRUE : FALSE
*/
BOOL CLstBxWinHost::LbSetItemHeight(int nHeight)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbSetItemHeight");
// Set the height of the items if there are between [1,255] : bug fix #4783
if (nHeight < 256 && nHeight > 0)
{
if (SetItemsHeight(nHeight, FALSE))
{
//bug fix #4214
//need to recalculate how many items are viewable, IN ITS ENTIRETY,
//using the current window size
RECT rc;
TxGetClientRect(&rc);
_nViewSize = max(rc.bottom / max(_nyItem, 1), 1);
return TRUE;
}
}
return FALSE;
}
/*
* BOOL CLstBxWinHost::LbGetItemRect(int, RECT*)
*
* @mfunc
* Returns the rectangle coordinates of a requested index
* The coordinates will be in client coordinates
*
* #rdesc
* BOOL = Successful ? TRUE : FALSE
*/
BOOL CLstBxWinHost::LbGetItemRect(int idx, RECT* prc)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbGetItemRect");
Assert(prc);
Assert(idx >= -1);
#ifdef _DEBUG
if (_nCount > 0)
Assert(idx < _nCount);
else
Assert(idx == _nCount);
#endif //_DEBUG
if (idx == -1)
idx = 0;
TxGetClientRect(prc);
prc->top = (idx - GetTopIndex()) * _nyItem + _rcViewport.top;
prc->left = 0;
prc->bottom = prc->top + _nyItem;
return TRUE;
}
/*
* BOOL CLstBxWinHost::LbSetItemData(long, long, long)
*
* @mfunc
* Given a range [nStart,nEnd] the data for these items
* will be set to nValue
* #rdesc
* void
*/
void CLstBxWinHost::LbSetItemData(long nStart, long nEnd, long nValue)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbSetItemData");
Assert(nStart >= 0 && nStart < _nCount);
Assert(nEnd >= 0 && nEnd < _nCount);
Assert(nStart <= nEnd);
int nMin = min(nEnd + 1, _nCount);
for (int i = nStart; i < nMin; i++)
_rgData[i]._dwData = nValue;
}
/*
* long CLstBxWinHost::LbDeleteString(long, long)
*
* @mfunc
* Delete the string at the requested range.
* #rdesc
* long = # of items in the list box. If failed -1
*/
long CLstBxWinHost::LbDeleteString(long nStart, long nEnd)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbDeleteString");
if ((nStart > nEnd) || (nStart < 0) || (nEnd >= _nCount))
return -1;
if (!RemoveString(nStart, nEnd))
return -1;
// set the top index to fill the window
LbSetTopIndex(max(nStart -1, 0));
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_DESTROY;
_fNotifyWinEvt = TRUE;
TxNotify(_dwWinEvent, NULL);
#endif
return _nCount;
}
/*
* CLstBxWinHost::LbInsertString(long, LPCTSTR)
*
* @mfunc
* Insert the string at the requested index. If long >= 0 then the
* string insertion is at the requested index. If long == -2 insertion
* is at the position which the string would be alphabetically in order.
* If long == -1 then string is added to the bottom of the list
*
* #rdesc
* long = If inserted, the index (paragraph) which the string
* was inserted. If not inserted returns -1;
*/
long CLstBxWinHost::LbInsertString(long nIdx, LPCTSTR szText)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbInsertString");
Assert(nIdx >= -2);
Assert(szText);
if (nIdx == -2)
{
if (_nCount > 0)
nIdx = GetSortedPosition(szText, 0, _nCount - 1);
else
nIdx = 0; //nothing inside listbox
}
else if (nIdx == -1)
nIdx = GetCount(); // Insert string to the bottom of list if -1
if (InsertString(nIdx, szText))
{
// If the index was previously selected unselect the newly
// added item
for (int i = _nCount - 1; i > nIdx; i--)
{
_rgData[i]._fSelected = _rgData.Get(i - 1)._fSelected;
// bug fix #4916
_rgData[i]._dwData = _rgData.Get(i - 1)._dwData;
}
_rgData[nIdx]._fSelected = 0;
_rgData[nIdx]._dwData = 0; // Need to Initialize data back to zero
if (!_fOwnerDraw)
{
// if we inserted at the middle or top then check 1 index down to see if the item
// was selected, if we inserted at the bottom then check 1 index up to see if the item
// was selected. If the item was selected we need to change the colors to default
// because we inherit the color properties from the range which we inserted into
if (_nCount > 1)
{
if (((nIdx < _nCount - 1) && _rgData.Get(nIdx + 1)._fSelected) ||
(nIdx == (_nCount - 1) && _rgData.Get(nIdx - 1)._fSelected))
SetColors((unsigned)tomAutoColor, (unsigned)tomAutoColor, nIdx, nIdx);
}
}
else
{
// Force redraw of items if owner draw and new item is viewable
if (IsItemViewable(nIdx))
{
RECT rc;
LbGetItemRect(nIdx, &rc);
rc.bottom = _rcViewport.bottom;
InvalidateRect(_hwnd, &rc, TRUE);
}
}
#ifndef NOACCESSIBILITY
_dwWinEvent = EVENT_OBJECT_CREATE;
_fNotifyWinEvt = TRUE;
_nAccessibleIdx = nIdx + 1;
TxNotify(_dwWinEvent, NULL);
#endif
return nIdx;
}
else
{
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return -1;
}
}
/*
* CLstBxWinHost::LbFindString(long, LPCTSTR, BOOL)
*
* @mfunc
* Searches the story for a given string. The
* starting position will be determined by the index nStart.
* This routine expects the units to be in tomParagraph.
* If bExact is TRUE then the paragraph must match the BSTR.
*
* #rdesc
* long = If found, the index (paragraph) which the string
* was found in. If not found returns -1;
*/
long CLstBxWinHost::LbFindString(long nStart, LPCTSTR szSearch, BOOL bExact)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbFindString");
Assert(szSearch);
Assert(nStart <= _nCount);
int nSize = wcslen(szSearch);
// If string is empty and not finding exact match then just return -1 like
// the system control. We don't have to worry about the exact match case
// because it will work properly
if (nStart >= _nCount || (nSize == 0 && !bExact))
return -1;
// allocate string buffer into stack
WCHAR sz[1024];
WCHAR *psz = sz;
if ((nSize + 3) > 1024)
psz = new WCHAR[nSize + 3 /* 2 paragraphs and a NULL*/];
Assert(psz);
if (psz == NULL)
{
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return FALSE;
}
// format the string the way we need it
wcscpy(psz, szCR);
wcscat(psz, szSearch);
if (bExact)
wcscat(psz, szCR);
long lRet = -1;
long l, cp;
ITextRange *pRange = NULL;
BSTR bstrQuery = SysAllocString(psz);
if(!bstrQuery)
goto CleanExit;
if (psz != sz)
delete [] psz;
// Set starting position for the search
if (!GetRange(nStart, _nCount - 1, &pRange))
{
SysFreeString(bstrQuery);
return lRet;
}
CHECKNOERROR(pRange->GetStart(&cp));
if (cp > 0)
{
// We need to use the paragraph marker from the previous
// paragraph when searching for a string
CHECKNOERROR(pRange->SetStart(--cp));
}
else
{
// Special case:
// Check if the first item matchs
if (FindString(0, szSearch, bExact))
{
lRet = 0;
goto CleanExit;
}
}
if (NOERROR != pRange->FindTextStart(bstrQuery, 0, FR_MATCHALEFHAMZA | FR_MATCHKASHIDA | FR_MATCHDIAC, &l))
{
// Didn't find the string...
if (nStart > 0)
{
if (!FindString(0, szSearch, bExact))
{
// Start the search from top of list to the point where
// we last started the search
CHECKNOERROR(pRange->SetRange(0, ++cp));
CHECKNOERROR(pRange->FindTextStart(bstrQuery, 0, 0, &l));
}
else
{
// First item was a match
lRet = 0;
goto CleanExit;
}
}
else
goto CleanExit;
}
// If we got down here then we have a match.
// Get the index and convert to listbox index
CHECKNOERROR(pRange->MoveStart(tomCharacter, 1, &l));
CHECKNOERROR(pRange->GetIndex(tomParagraph, &lRet));
lRet--; // index is 1 based so we need to changed it to zero based
CleanExit:
if (bstrQuery)
SysFreeString(bstrQuery);
if (pRange)
pRange->Release();
return lRet;
}
/*
* CLstBxWinHost::LbShowIndex(int, BOOL)
*
* @mfunc
* Makes sure the requested index is within the viewable space.
* In cases where the item is not in the viewable space bTop is
* used to determine the requested item should be at the top
* of the list else list box will scrolled enough to display the
* item.
* NOTE:
* There can be situations where bTop will fail. These
* situations occurr of the top index requested prevents the list
* box from being completely filled with items. For more info
* read the comments for LBSetTopIndex.
*
* #rdesc
* BOOL = Successfully displays the item ? TRUE : FALSE
*/
BOOL CLstBxWinHost::LbShowIndex(long nIdx, BOOL bTop)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbShowIndex");
// Make sure the requested item is within valid bounds
Assert(nIdx >= 0 && nIdx < _nCount);
int delta = nIdx - GetTopIndex();
// If item is already visible then just return TRUE
if (0 <= delta && delta < _nViewSize)
return TRUE;
if ((delta) >= _nViewSize && !bTop && _nViewSize)
{
nIdx = nIdx - _nViewSize + 1;
}
return (LbSetTopIndex(nIdx) < 0) ? FALSE : TRUE;
}
/*
* CLstBxWinHost::LbSetTopIndex(long)
*
* @mfunc
* Tries to make the requested item the top index in the list box.
* If making the requested item the top index prevents the list box
* from using the viewable region to its fullest then and alternative
* top index will be used which will display the requested index
* but NOT as the top index. This ensures conformancy with the system
* list box and makes full use of the dislayable region.
*
* #rdesc
* long = returns the new top index if successful. If failed returns -1
*/
long CLstBxWinHost::LbSetTopIndex(long nIdx)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbSetTopIndex");
// Make sure the requested item is within valid bounds
if (nIdx < 0 || nIdx >= _nCount)
return -1;
// Always try to display a full list of items in the list box
// This may mean we have to adjust the requested top index if
// the requested top index will leave blanks at the end of the
// viewable space
if (_nCount - _nViewSize < nIdx)
nIdx = max(0, _nCount - _nViewSize);
// Just check to make sure we not already at the top
if (GetTopIndex() == nIdx)
return nIdx;
if (!SetTopViewableItem(nIdx))
nIdx = -1;
return nIdx;
}
/*
* CLstBxWinHost::LbBatchInsert(WCHAR* psz)
*
* @mfunc
* Inserts the given list of items into listbox. The listbox is reset prior to adding
* the items into the listbox
*
* #rdesc
* int = # of items in the listbox if successful else LB_ERR
*/
int CLstBxWinHost::LbBatchInsert(WCHAR* psz)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbBatchInsert");
// make sure we get some sort of string
if (!psz)
return LB_ERR;
WCHAR* pszOut = psz;
LRESULT nRet = LB_ERR;
BSTR bstr = NULL;
ITextRange* pRange = NULL;
int nCount = 0;
if (_fSort)
{
pszOut = new WCHAR[wcslen(psz) + 1];
Assert(pszOut);
if (!pszOut)
{
TxNotify((unsigned long)LBN_ERRSPACE, NULL);
return LB_ERR;
}
nCount = SortInsertList(pszOut, psz);
if (nCount == LB_ERR)
goto CleanExit;
}
else
{
//bug fix #5130 we need to know how much we are going to insert
//prior to inserting because we may be getting showscrollbar message
//during insertion
WCHAR* pszTemp = psz;
while(*pszTemp)
{
if (*pszTemp == L'\r')
nCount++;
pszTemp++;
}
nCount++;
}
//clear listbox and insert new list into listbox
LbDeleteString(0, GetCount() - 1);
bstr = SysAllocString(pszOut);
if(!bstr)
goto CleanExit;
// Insert string into list
CHECKNOERROR(((CTxtEdit*)_pserv)->Range(0, 0, &pRange));
//bug fix #5130
// preset our _nCount for scrollbar purposes
_nCount = nCount;
CHECKNOERROR(pRange->SetText(bstr));
#ifdef DEBUG
// We can't trust the code below because ITextServices performs a background recalc
// and so returns the incorrect line count
// update our count
// I'm leaving it here for debugging purposes of ITextServices
_pserv->TxSendMessage(EM_GETLINECOUNT, 0, 0, &nRet);
AssertSz(_nCount == nRet, "Textserv line count doesn't match listbox interal line count");
#endif
nRet = nCount;
CleanExit:
if (pszOut != psz)
delete [] pszOut;
if (bstr)
SysFreeString(bstr);
if (pRange)
pRange->Release();
return nRet;
}
/*
* CLstBxWinHost::LbSetSelection(long, long, int)
*
* @mfunc
* Given the range of nStart to nEnd set the selection state of each item
* This function will also update the anchor and cursor position
* if requested.
*
* #rdesc
* BOOL = If everything went fine ? TRUE : FALSE
*/
BOOL CLstBxWinHost::LbSetSelection(long nStart, long nEnd, int ffFlags, long nCursor, long nAnchor)
{
TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::LbSetSelection");
if (!_fOwnerDraw)
{
Freeze();
// de-select all items
if ((ffFlags & LBSEL_RESET))
{
if (!ResetContent())
{
Unfreeze();
return FALSE;
}
// Reset, check if anything else needs to be done
// else just exit out
if (ffFlags == LBSEL_RESET)
{
Unfreeze();
return TRUE;
}
}
}
// NOTE:
// This should be one big critical section because we rely on certain
// member variables not changing during the process of this function
// Check if we are changing the selection and if we have focus
// if we do then we first need to xor out the focus rect from
// old cursor
RECT rc;
HDC hdc;
hdc = TxGetDC();
Assert(hdc);
// don't draw outside the client rect draw the rectangle
TxGetClientRect(&rc);
IntersectClipRect(hdc, rc.left, rc.top, rc.right, rc.bottom);
if ((ffFlags & LBSEL_NEWCURSOR) && _fFocus)
{
// If owner draw notify parentwindow
if (_fOwnerDraw)
LbDrawItemNotify(hdc, max(_nCursor, 0), ODA_FOCUS, IsSelected(_nCursor) ? ODS_SELECTED : 0);
else
{
LbGetItemRect(_nCursor, &rc);
::DrawFocusRect(hdc, &rc);
}
}
// check if all item should be selected
if (nStart == -1 && nEnd == 0)
{
nStart = 0;
nEnd = _nCount - 1;
}
else if (nStart > nEnd)
{
// reshuffle so nStart is <= nEnd;
long temp = nEnd;
nEnd = nStart;
nStart = temp;
}
// Check for invalid values
if (nStart < -1 || nEnd >= _nCount)
{
if (!_fOwnerDraw)
Unfreeze();
// mimic system listbox behaviour
if (nEnd >= _nCount)
return FALSE;
else
return TRUE;
}
// Prepare the state we want to be in
unsigned int bState;
DWORD dwFore;
DWORD dwBack;
if (ffFlags & LBSEL_SELECT)
{
bState = ODS_SELECTED; //NOTE ODS_SELECTED must equal 1
dwFore = _crSelFore;
dwBack = _crSelBack;
if (_fSingleSel)
nEnd = nStart;
}
else
{
bState = 0;
dwFore = (unsigned)tomAutoColor;
dwBack = (unsigned)tomAutoColor;
}
// A little optimization check
// Checks to see if the state is really being changed if not then don't bother
// calling SetColor, works only when nStart == nEnd;
// The list box will not change the background color if nSame is true
int nSame = (nStart == nEnd && nStart != -1) ? (_rgData.Get(nStart)._fSelected == bState) : FALSE;
BOOL bRet = TRUE;
if (_fOwnerDraw)
{
if (ffFlags & LBSEL_RESET || !bState)
{
// There are cases where we don't necessarily reset all the items
// in the list but rather the range which was given. The following
// takes care of this case
int ff = ffFlags & LBSEL_RESET;
int i = (ff) ? 0 : nStart;
int nStop = (ff) ? _nCount : nEnd + 1;
for (; i < nStop; i++)
{
// Don't unselect an item which is going to be
// selected in the next for loop
if (!bState || (i < nStart || i > nEnd) &&
(_rgData.Get(i)._fSelected != 0))
{
// Only send a unselect message if the item
// is viewable
_rgData[i]._fSelected = 0;
if (IsItemViewable(i))
LbDrawItemNotify(hdc, i, ODA_SELECT, 0);
}
}
}
if (bState)
{
// We need to loop through and notify the parent
// The item has been deselected or selected
for (int i = max(0, nStart); i <= nEnd; i++)
{
if (_rgData.Get(i)._fSelected != 1)
{
_rgData[i]._fSelected = 1;
if (IsItemViewable(i))
LbDrawItemNotify(hdc, i, ODA_SELECT, ODS_SELECTED);
}
}
}
}
else if (!nSame)
{
// Update our internal records
for (int i = max(0, nStart); i <= nEnd; i++)
_rgData[i]._fSelected = bState;
bRet = SetColors(dwFore, dwBack, nStart, nEnd);
}
// Update the cursor and anchor positions
if (ffFlags & LBSEL_NEWANCHOR)
_nAnchor = nAnchor;
// Update the cursor position
if (ffFlags & LBSEL_NEWCURSOR)
_nCursor = nCursor;
// Draw the focus rect
if (_fFocus)
{
if (_fOwnerDraw)
LbDrawItemNotify(hdc, _nCursor, ODA_FOCUS, ODS_FOCUS |
(IsSelected(_nCursor) ? ODS_SELECTED : 0));
else
{
LbGetItemRect(_nCursor, &rc);
::DrawFocusRect(hdc, &rc);
}
}
TxReleaseDC(hdc);
// This will automatically update the window
if (!_fOwnerDraw)
{
Unfreeze();
// We need to do this because we are making so many changes
// ITextServices might get confused
ScrollToView(GetTopIndex());
}
return bRet;
}