mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5048 lines
124 KiB
5048 lines
124 KiB
/*
|
|
* @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 *) > );
|
|
goto serv;
|
|
|
|
case WM_GETTEXTLENGTH:
|
|
GETTEXTLENGTHEX gtl;
|
|
if (W32->OnWin9x() || phost->_fANSIwindow)
|
|
W32->AnsiFilter( msg, wparam, lparam, (void *) >l );
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|