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