/* * @doc INTERNAL * * @module LBHOST.CPP -- Text Host for CreateWindow() Rich Edit * List Box Control | * Implements CLstBxWinHost message * * Original Author: * Jerry Kim * * History: * 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 CLbData CDynamicArray::_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 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 *

is filled with a boolean combination of the * window styles related to scroll bars. Specifically, these are: * * WS_VSCROLL * WS_HSCROLL * ES_AUTOVSCROLL * ES_AUTOHSCROLL * ES_DISABLENOSCROLL */ 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 * 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. * * user double-clicks an item in teh list box * * The list box cannot allocate enough memory to * fulfill a request * * The list box loses the keyboard focus * * The user cancels te selection of an item in the list * box * * The selection in a list box is about to change * * 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

{ 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 * */ 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 * */ 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 * */ 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 * */ 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 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 * */ void CLstBxWinHost::Freeze() { TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::Freeze"); long l; ((CTxtEdit*)_pserv)->Freeze(&l); } /* * inline CLstBxWinHost::FreezeCount() * * @mfunc * Returns the current freeze count * * #rdesc * */ short CLstBxWinHost::FreezeCount() const { TRACEBEGIN(TRCSUBSYSHOST, TRCSCOPEINTERN, "CLstBxWinHost::GetFreezeCount"); return ((CTxtEdit*)_pserv)->GetFreezeCount(); } /* * inline CLstBxWinHost::Unfreeze() * * @mfunc * Allows TOM to update itself * * #rdesc * */ 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+ // 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; }