//// LPK_EDIT - Edit control support - C interface // // Handles all callouts from the standard US edit control. // // David C Brown (dbrown) 17th Nov 1996. // // Copyright (c) 1996-1997 Microsoft Corporation. All right reserved. /* * Core NT headers */ #include #include #include #include #include #define NONTOSPINTERLOCK #include /* * Standard C runtime headers */ #include #include #include #include #include /* * NtUser Client specific headers */ #include "usercli.h" #include #include #include #include #include /* * Complex script language pack */ #include "lpk.h" #include "lpk_glob.h" // Don't link directly to NtUserCreateCaret #undef CreateCaret /// Unicode control characters // #define U_TAB 0x0009 #define U_FS 0x001C #define U_GS 0x001D #define U_RS 0x001E #define U_US 0x001F #define U_ZWNJ 0x200C #define U_ZWJ 0x200D #define U_LRM 0x200E #define U_RLM 0x200F #define U_LRE 0x202A #define U_RLE 0x202B #define U_PDF 0x202C #define U_LRO 0x202D #define U_RLO 0x202E #define U_ISS 0x206A #define U_ASS 0x206B #define U_IAFS 0x206C #define U_AAFS 0x206D #define U_NADS 0x206E #define U_NODS 0x206F #define TRACE(a,b) #define ASSERTS(a,b) #define ASSERTHR(a,b) /***************************************************************************\ * BOOL ECIsDBCSLeadByte( PED ped, BYTE cch ) * * IsDBCSLeadByte for Edit Control use only. * * History: 18-Jun-1996 Hideyuki Nagase \***************************************************************************/ BOOL ECIsDBCSLeadByte(PED ped, BYTE cch) { int i; if (!ped->fDBCS || !ped->fAnsi) return (FALSE); for (i = 0; ped->DBCSVector[i]; i += 2) { if ((ped->DBCSVector[i] <= cch) && (ped->DBCSVector[i+1] >= cch)) return (TRUE); } return (FALSE); } //// GetEditAnsiConversionCharset - Figure a proper charset to MBTWC ANSI edit control's data // // In some Far East settings, they associate the symbol font to their ANSI codepage for // backward compatibility (this is not the case for Japanese though), otherwise we convert // using page 0. Currently Uniscribe glyph table maps SYMBOLIC_FONT to 3 pages - U+00xx, // U+F0xx and the system's ACP page. // // For Unicode control returns -1 int GetEditAnsiConversionCharset(PED ped) { int iCharset = ped->fAnsi ? ped->charSet : -1; if (iCharset == SYMBOL_CHARSET || iCharset == OEM_CHARSET) { iCharset = ANSI_CHARSET; // assume page U+00xx } if (iCharset == ANSI_CHARSET && ped->fDBCS) { // In Chinese system, there is font association to map symbol to ACP // (QueryFontAssocStatus returns non-null). More detail please refer // to USER's ECGetDBCSVector(...) CHARSETINFO csi; if (TranslateCharsetInfo((DWORD*)UIntToPtr(g_ACP), &csi, TCI_SRCCODEPAGE)) iCharset = csi.ciCharset; } return iCharset; } //// MBCPtoWCCP - Translate multi-byte caret position to wide char caret position // // Translates from a multibyte caret position specified as a byte offset // into an 8 bit string, to a widechar caret position, returned as a // word offset into a 16 bit character string. // // If the codepage isn't a DBCS, the imput offset is returned unchanged. // // Returns E_FAIL if icpMbStr addresses the second byte of a double byte character HRESULT MBCPtoWCCP( PED ped, // In - Edit control structure BYTE *pbMbStr, // In - Multi byte string int icpMbStr, // In - Byte offset of caret in multibyte string int *picpWcStr) { // Out - Wide char caret position if (!ped->fDBCS || !ped->fAnsi) { *picpWcStr = icpMbStr; return S_OK; } // Scan through DBCS string counting characters *picpWcStr = 0; while (icpMbStr > 0) { if (ECIsDBCSLeadByte(ped, *pbMbStr)) { // Character takes two bytes icpMbStr -= 2; pbMbStr += 2; } else { // Character takes one byte icpMbStr--; pbMbStr++; } (*picpWcStr)++; } return icpMbStr == 0 ? S_OK : E_FAIL; } //// WCCPtoMBCP - Translate wide char caret position to multi-byte caret position // // Translates from a widechar caret position specified as a word offset // into a 16 bit string, to a multibyte caret position, returned as a // byte offset into an 8 bit character string. HRESULT WCCPtoMBCP( PED ped, // In - Edit control structure BYTE *pbMbStr, // In - Multi byte string int icpWcStr, // In - Wide char caret position int *picpMbStr) { // Out - Byte offset of caret in multibyte string if (!ped->fDBCS || !ped->fAnsi) { *picpMbStr = icpWcStr; return S_OK; } // Scan through DBCS string counting characters *picpMbStr = 0; while (icpWcStr > 0) { if (ECIsDBCSLeadByte(ped, *pbMbStr)) { // Character takes two bytes (*picpMbStr) += 2; pbMbStr += 2; } else { // Character takes one byte (*picpMbStr)++; pbMbStr++; } icpWcStr--; } return S_OK; } //// LeftEdgeX // // Returns the visual x offset (i.e. from the left edge of the window) // to the left edge of a line of width iWidth given the current // formatting state of the edit control, .format and .xOffset. int LeftEdgeX(PED ped, INT iWidth) { INT iX; // First generate logical iX - offset forward from leading margin. iX = 0; switch (ped->format) { case ES_LEFT: // leading margin alignment if (ped->fWrap) { iX = 0; } else { iX = -(INT)ped->xOffset; } break; case ES_CENTER: iX = (ped->rcFmt.right - ped->rcFmt.left - iWidth) / 2; break; case ES_RIGHT: // far margin alignment iX = ped->rcFmt.right - ped->rcFmt.left - iWidth; break; } // iX is logical offset from leading margin to leading edge of string if (ped->format != ES_LEFT && iX < 0) { iX = !ped->fWrap ? -(INT)ped->xOffset : 0; } // Now adjust for right to left origin and incorporate left margin if (ped->fRtoLReading) { iX = ped->rcFmt.right - (iX+iWidth); } else { iX += ped->rcFmt.left; } TRACE(EDIT, ("LeftEdgeX iWidth=%d, format=%d, xOffset=%d, fWrap=%d, fRtoLReading=%d, right-left=%d, returning %d", iWidth, ped->format, ped->xOffset, ped->fWrap, ped->fRtoLReading, ped->rcFmt.right - ped->rcFmt.left, iX)); return iX; } ///// Shaping engins IDs. #define BIDI_SHAPING_ENGINE_DLL 1<<0 #define THAI_SHAPING_ENGINE_DLL 1<<1 #define INDIAN_SHAPING_ENGINE_DLL 1<<4 /// //// EditCreate // // Called from edecrare.c ECCreate. // // Return TRUE if create succeeded BOOL EditCreate(PED ped, HWND hWnd) { LONG_PTR dwExStyle, dwStyle; TRACE(EDIT, ("EditCreate called.")); // Check if BIDI shaping engine is loaded then // allow the edit control to switch its direction. if (g_dwLoadedShapingDLLs & BIDI_SHAPING_ENGINE_DLL) { ped->fAllowRTL = TRUE; } else { ped->fAllowRTL = FALSE; } // Process WS_EX flags dwExStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE); if (dwExStyle & WS_EX_LAYOUTRTL) { dwExStyle = dwExStyle & ~WS_EX_LAYOUTRTL; dwExStyle = dwExStyle ^ (WS_EX_RTLREADING | WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR); SetWindowLongPtr(hWnd, GWL_EXSTYLE, dwExStyle); dwStyle = GetWindowLongPtr(hWnd, GWL_STYLE); if (!(dwStyle & ES_CENTER)) { dwStyle = dwStyle ^ ES_RIGHT; SetWindowLongPtr(hWnd, GWL_STYLE, dwStyle); } } if (dwExStyle & WS_EX_RIGHT && ped->format == ES_LEFT) { ped->format = ES_RIGHT; } if (dwExStyle & WS_EX_RTLREADING) { ped->fRtoLReading = TRUE; switch (ped->format) { case ES_LEFT: ped->format = ES_RIGHT; break; case ES_RIGHT: ped->format = ES_LEFT; break; } } return TRUE; } //// EditStringAnalyse // // Creates standard analysis parameters from the PED HRESULT EditStringAnalyse( HDC hdc, PED ped, PSTR pText, int cch, DWORD dwFlags, int iMaxExtent, STRING_ANALYSIS **ppsa){ HRESULT hr; SCRIPT_TABDEF std; int iTabExtent; if (!ped->pTabStops) { std.cTabStops = 1; std.iScale = 4; std.pTabStops = &iTabExtent; std.iTabOrigin = 0; iTabExtent = ped->aveCharWidth * 8; } else { std.cTabStops = *ped->pTabStops; std.iScale = 4; // Tabstops are already in device units std.pTabStops = ped->pTabStops + 1; std.iTabOrigin = 0; } hr = LpkStringAnalyse( hdc, ped->charPasswordChar ? (char*)&ped->charPasswordChar : pText, cch, 0, GetEditAnsiConversionCharset(ped), dwFlags | SSA_FALLBACK | SSA_TAB | (ped->fRtoLReading ? SSA_RTL : 0) | (ped->fDisplayCtrl ? SSA_DZWG : 0) | (ped->charPasswordChar ? SSA_PASSWORD : 0), -1, iMaxExtent, NULL, NULL, // Control, State NULL, // Overriding Dx array &std, // Tab definition NULL, // Input class overrides ppsa); if (FAILED(hr)) { ASSERTHR(hr, ("EditStringAnalyse - LpkStringAnalyse")); } return hr; } //// HScroll // // Checks wether the cursor is visible withing the rcFormat // area, and if not, updates xOffset so that it's 1/4 of the // way from the edge it was closest to. // // However, it never leaves whitespace between the leading margin // and the leading edge of the string, nor will it leave whitespace // between the trailing edge and the trailing margin when there's // enough text in the string to fill the whole window. // // Implemented for the single line edit control. BOOL EditHScroll(PED ped, HDC hdc, PSTR pText) { int ichCaret; int dx; // Distance to move RIGHTWARDS (i.e. visually) int cx; // Original visual cursor position int tw; // Text width int rw; // Rectangle width int ix; // ScriptCPtoX result UINT uOldXOffset; HRESULT hr; STRING_ANALYSIS *psa; if (!ped->cch || ped->ichCaret > ped->cch) { ped->xOffset = 0; return FALSE; } hr = EditStringAnalyse(hdc, ped, pText, ped->cch, SSA_GLYPHS, 0, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditHScroll - EditStringAnalyse")); return FALSE; } MBCPtoWCCP(ped, pText, ped->ichCaret, &ichCaret); uOldXOffset = ped->xOffset; tw = psa->size.cx; // Text width rw = ped->rcFmt.right-ped->rcFmt.left; // Window rectangle width #ifdef CURSOR_ABSOLUTE_HOME_AND_END if (ichCaret <= 0) { cx = ped->fRtoLReading ? psa->size.cx : 0; } else if (ichCaret >= psa->cInChars) { cx = ped->fRtoLReading ? 0: psa->size.cx; } else { cx = ScriptCursorX(psa, ichCaret-1); } cx += LeftEdgeX(ped, tw); #else if (ichCaret <= 0) { hr = ScriptStringCPtoX(psa, ichCaret, FALSE, &ix); } else { hr = ScriptStringCPtoX(psa, ichCaret-1, TRUE, &ix); } if (FAILED(hr)) { ASSERTHR(hr, ("EditHScroll - ScriptStringCPtoX")); ScriptStringFree(&psa); return FALSE; } cx = LeftEdgeX(ped, tw) + ix; #endif if (cx < ped->rcFmt.left) { // Bring cursor position to left quartile dx = rw/4 - cx; } else if (cx > ped->rcFmt.right) { // Bring cursor position to right quartile dx = (3*rw)/4 - cx; } else dx = 0; // Adjust visual position change to logical - relative to reading order if (ped->fRtoLReading) { dx = - dx; } // Avoid unnecessary leading or trailing whitespace if (tw - ((INT)ped->xOffset - dx) < rw && tw > rw ) { // No need to have white space at the end if there's enough text ped->xOffset = (UINT)(tw-rw); } else if ((INT)ped->xOffset < dx) { // No need to have white space at beginning of line ped->xOffset = 0; } else { // Move cursor directly to chosen quartile ped->xOffset -= (UINT) dx; } TRACE(EDIT, ("HScroll format=%d, fWrap=%d, fRtoLReading=%d, right-left=%d, new xOffset %d", ped->format, ped->fWrap, ped->fRtoLReading, ped->rcFmt.right - ped->rcFmt.left, ped->xOffset)); ScriptStringFree(&psa); return ped->xOffset != uOldXOffset ? TRUE : FALSE; } //// IchToXY // // Converts a character position to the corresponding x coordinate // offset from the left end of the text of the line. int EditIchToXY(PED ped, HDC hdc, PSTR pText, ICH ichLength, ICH ichPos) { INT iResult; HRESULT hr; STRING_ANALYSIS *psa; if (ichLength == 0) { return LeftEdgeX(ped, 0); } hr = EditStringAnalyse(hdc, ped, pText, ichLength, SSA_GLYPHS, 0, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditIchToXY - EditStringAnalyse")); return LeftEdgeX(ped, 0); } MBCPtoWCCP(ped, pText, ichPos, &ichPos); #ifdef CURSOR_ABSOLUTE_HOME_AND_END if (ichPos <= 0) { iResult = ped->fRtoLReading ? psa->size.cx : 0; } else if (ichPos >= psa->cInChars) { iResult = ped->fRtoLReading ? 0 : psa->size.cx; } else { iResult = ScriptStringCPtoX(psa, ichPos-1); } #else if (ichPos <= 0) { hr = ScriptStringCPtoX(psa, ichPos, FALSE, &iResult); } else { hr = ScriptStringCPtoX(psa, ichPos-1, TRUE, &iResult); } if (FAILED(hr)) { ASSERTHR(hr, ("EditIchToXY - ScriptStringCPtoX")); iResult = 0; } #endif iResult += LeftEdgeX(ped, psa->size.cx); ScriptStringFree(&psa); return (int) iResult; } //// EditDrawText - draw one line for MLDrawText // // Draws the text at offset ichStart from pText length ichLength. // // entry pwText - points to beginning of line to display // iMinSel, - range of characters to be highlighted. May // iMaxSel be -ve or > cch. // // Uses ED structure fields as follows: // // rcFmt - drawing area // xOffset - distance from leading margin to leading edge of text. // (May be negative if the text is horizontally scrolled). // RtoLReading - determines leading margin/edge. // format - ES_LEFT - leading edge aligned (may be scrolled horizontally) // - ES_CENTRE - centred between margins. (can't be scrolled horizontally) // - ES_RIGHT - trailing margin aligned (can't be scrolled horizontally) void EditDrawText(PED ped, HDC hdc, PSTR pText, INT iLength, INT iMinSel, INT iMaxSel, INT iY) { INT iX; // x position to start display at INT iWidth; // Width of line RECT rc; // Locally updated copy of rectangle int xFarOffset; HRESULT hr; STRING_ANALYSIS *psa; // Establish where to display the line rc = ped->rcFmt; rc.top = iY; rc.bottom = iY + ped->lineHeight; xFarOffset = ped->xOffset + rc.right - rc.left; // Include left or right margins in display unless clipped // by horizontal scrolling. if (ped->wLeftMargin) { if (!( ped->format == ES_LEFT // Only ES_LEFT (Nearside alignment) can get clipped && ( (!ped->fRtoLReading && ped->xOffset > 0) // LTR and first char not fully in view || ( ped->fRtoLReading && xFarOffset < ped->maxPixelWidth)))) { //RTL and last char not fully in view rc.left -= ped->wLeftMargin; } } if (ped->wRightMargin) { if (!( ped->format == ES_LEFT // Only ES_LEFT (Nearside alignment) can get clipped && ( ( ped->fRtoLReading && ped->xOffset > 0) // RTL and first char not fully in view || (!ped->fRtoLReading && xFarOffset < ped->maxPixelWidth)))) { // LTR and last char not fully in view rc.right += ped->wRightMargin; } } if (iMinSel < 0) iMinSel = 0; if (iMaxSel > iLength) iMaxSel = iLength; if (ped->fSingle) { // The single line edit control always applies the background color SetBkMode(hdc, OPAQUE); } if (iLength <= 0) { if ((iMinSel < iMaxSel) || (GetBkMode(hdc) == OPAQUE)) { // Empty line, just clear it on screen ExtTextOutW(hdc, 0,iY, ETO_OPAQUE, &rc, NULL, 0, NULL); } return; } hr = EditStringAnalyse(hdc, ped, pText, iLength, SSA_GLYPHS, 0, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditDrawText - EditStringAnalyse")); return; } MBCPtoWCCP(ped, pText, iMinSel, &iMinSel); MBCPtoWCCP(ped, pText, iMaxSel, &iMaxSel); iWidth = psa->size.cx; iX = LeftEdgeX(ped, iWidth); // Visual x where left edge of string should be. ScriptStringOut( psa, iX, iY, ETO_CLIPPED | (GetBkMode(hdc) == OPAQUE ? ETO_OPAQUE : 0), &rc, iMinSel, ped->fNoHideSel || ped->fFocus ? iMaxSel : iMinSel, ped->fDisabled); ScriptStringFree(&psa); } //// EditMouseToIch // // Returns the logical character offset corresponding to // a specified x offset. // // entry iX - Window (visual) x position ICH EditMouseToIch(PED ped, HDC hdc, PSTR pText, ICH ichCount, INT iX) { ICH iCh; BOOL fTrailing; int iWidth; HRESULT hr; STRING_ANALYSIS *psa; if (ichCount == 0) { return 0; } hr = EditStringAnalyse(hdc, ped, pText, ichCount, SSA_GLYPHS, 0, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditMouseToIch - EditStringAnalyse")); return 0; } iWidth = psa->size.cx; // Take horizontal scroll position into consideration. iX -= LeftEdgeX(ped, iWidth); // If the user clicked beyond the edge of the string, treat it as a logical // start or end of string request. if (iX < 0) { iCh = ped->fRtoLReading ? ichCount : 0; } else if (iX > iWidth) { TRACE(POSN, ("LpkEditMouseToIch iX beyond right edge: iX %d, psa->piOutVW %x, psa->nOutGlyphs %d, psa->piDx[psa->nOutGlyphs-1] %d", iX, psa->piOutVW, psa->nOutGlyphs, iWidth)); iCh = ped->fRtoLReading ? 0 : ichCount; } else { // Otherwise it's in the string. Find the logical character whose centre is nearest. ScriptStringXtoCP(psa, iX, &iCh, &fTrailing); iCh += fTrailing; // Snap to nearest character edge WCCPtoMBCP(ped, pText, iCh, &iCh); } ScriptStringFree(&psa); TRACE(POSN, ("EditMouseToIch iX %d returns ch %d", iX, iCh)); return iCh; } //// EditGetLineWidth // // Returns width of line in pixels INT EditGetLineWidth(PED ped, HDC hdc, PSTR pText, ICH cch) { INT iResult; HRESULT hr; STRING_ANALYSIS *psa; if (cch == 0) { return 0; } if (cch > MAXLINELENGTH) { cch = MAXLINELENGTH; } hr = EditStringAnalyse(hdc, ped, pText, cch, SSA_GLYPHS, 0, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditGetLineWidth - EditStringAnalyse")); return 0; } iResult = psa->size.cx; ScriptStringFree(&psa); TRACE(EDIT, ("EditGetLineWidth width %d returns %d", cch, iResult)) return iResult; } //// EditCchInWidth // // Returns number of characters that will fit in width pixels. ICH EditCchInWidth(PED ped, HDC hdc, PSTR pText, ICH cch, int width) { ICH ichResult; HRESULT hr; STRING_ANALYSIS *psa; if (cch > MAXLINELENGTH) { cch = MAXLINELENGTH; } else if (cch == 0) { return 0; } hr = EditStringAnalyse(hdc, ped, pText, cch, SSA_GLYPHS | SSA_CLIP, width, &psa); if (FAILED(hr)) { ASSERTHR(hr, ("EditCchInWidth - EditStringAnalyse")); return 0; } ichResult = psa->cOutChars; WCCPtoMBCP(ped, pText, ichResult, &ichResult); ScriptStringFree(&psa); TRACE(EDIT, ("EditCchInWidth width %d returns %d", width, ichResult)) return ichResult; } //// EditMoveSelection // // Returns nearest character position backward or forward from current position. // // Position is restricted according to language rules. For example, in Thai it is // not possible to position the cursor between a base consonant and it's // associated vowel or tone mark. ICH EditMoveSelection(PED ped, HDC hdc, PSTR pText, ICH ich, BOOL fBackward) { #define SP 0x20 #define TAB 0x09 #define CR 0x0D #define LF 0x0A #define EDWCH(ich) (ped->fAnsi ? (WCHAR)pText[ich] : ((PWSTR)pText)[ich]) #define EDWCBLANK(ich) ((BOOL) (EDWCH(ich) == SP || EDWCH(ich) == TAB)) #define EDWCCR(ich) ((BOOL) (EDWCH(ich) == CR)) #define EDWCLF(ich) ((BOOL) (EDWCH(ich) == LF)) #define EDSTARTWORD(ich) ( (ich == 0) \ || ( ( EDWCBLANK(ich-1) \ || EDWCLF(ich-1)) \ && !EDWCBLANK(ich)) \ || ( !EDWCCR(ich-1) \ && EDWCCR(ich))) ICH ichNonblankStart; // Leading character of nonblank run containing potential caret position ICH ichNonblankLimit; // First character beyond nonblank run containing potential caret position int iOffset; // Offset into nonblank run of ich measued in logical characters STRING_ANALYSIS *psa; HRESULT hr; // Handle simple special cases: // o At very beginning or end of buffer // o When target position is blank or start or end of line if (fBackward) { if (ich <= 1) { return 0; } ich--; if (EDWCBLANK(ich)) { return ich; } if (EDWCLF(ich)) { while ( ich > 0 && EDWCCR(ich-1)) { ich--; } return ich; } } else { if (ich >= ped->cch-1) { return ped->cch; } ich++; if (EDWCBLANK(ich)) { return ich; } if (EDWCCR(ich-1)) { // Moving forward from a CR. if ( ich < ped->cch && EDWCCR(ich)) { ich++; } if ( ich < ped->cch && EDWCLF(ich)) { ich++; } return ich; } } // Identify nonblank run containing target position ichNonblankStart = ich; ichNonblankLimit = ich+1; // Move ichNonblankStart back to real start of blank delimited run while ( ichNonblankStart > 0 && !(EDSTARTWORD(ichNonblankStart))) { ichNonblankStart--; } // Include one leading space if any if ( ichNonblankStart > 0 && EDWCBLANK(ichNonblankStart - 1)) { ichNonblankStart--; } // Move ichNonblankLimit on to real end of blank delimited run while ( ichNonblankLimit < ped->cch && !EDWCBLANK(ichNonblankLimit) && !EDWCCR(ichNonblankLimit)) { ichNonblankLimit++; } // Obtain a break analysis of the identified nonblank run hr = LpkStringAnalyse( hdc, pText + ichNonblankStart * ped->cbChar, ichNonblankLimit - ichNonblankStart, 0, GetEditAnsiConversionCharset(ped), SSA_BREAK, -1, 0, NULL, NULL, NULL, NULL, NULL, &psa); if (SUCCEEDED(hr)) { // Use the charstop flags in the logical attributes to correct ich if (ich <= ichNonblankStart) { iOffset = 0; } else { hr = MBCPtoWCCP(ped, pText+ichNonblankStart*ped->cbChar, ich-ichNonblankStart, &iOffset); if (hr == E_FAIL) { // ich was the second byte of a double byte character. // In this case MBCPtoWCCP has returned the subsequent character if (fBackward) { iOffset--; } } } if (fBackward) { while ( iOffset > 0 && !psa->pLogAttr[iOffset].fCharStop) { iOffset--; } } else { while ( iOffset < psa->cInChars && !psa->pLogAttr[iOffset].fCharStop) { iOffset++; } } ScriptStringFree(&psa); WCCPtoMBCP(ped, pText+ichNonblankStart*ped->cbChar, iOffset, &ich); return ichNonblankStart + ich; } else { ASSERTHR(hr, ("EditMoveSelection - LpkStringAnalyse")); // Analysis not possible - ignore content of complex scripts. return ich; } } void EditGetNextBoundaries( PED ped, HDC hdc, PSTR pText, ICH ichStart, BOOL fLeft, ICH *pichMin, ICH *pichMax, BOOL fWordStop) { ICH sd,ed; // Start and end of blank delimited run ICH sc,ec; // Star and end of complex script word within sd,se HRESULT hr; STRING_ANALYSIS *psa; // Identify left end of nearest delimited word (see diagram above) sd = ichStart; if (fLeft) { // Going left if (sd) { sd--; while (!(EDSTARTWORD(sd))) { sd--; } } } else { // Going right if (EDWCBLANK(sd)) { // Move right to first character of word if (sd < ped->cch) { sd++; while (sd < ped->cch && !EDSTARTWORD(sd)) { sd++; } } } else { // Move left to first character of this word while (!EDSTARTWORD(sd)) { sd--; } } } // Position 'e' on first character of next word ed = sd; if (ed < ped->cch) { ed++; while (edcch && !EDSTARTWORD(ed)) { ed++; } } // Obtain an analysis of the identified word hr = LpkStringAnalyse( hdc, pText + sd * ped->cbChar, ed - sd, 0, GetEditAnsiConversionCharset(ped), SSA_BREAK, -1, 0, NULL, NULL, NULL, NULL, NULL, &psa); if (SUCCEEDED(hr)) { // Use the start of word (linebreak) flags in the logical attribute // to narrow the word where appropriate to complex script handling if (ichStart > sd) { MBCPtoWCCP(ped, pText+sd*ped->cbChar, ichStart-sd, &sc); } else { sc = 0; } // Change ed from byte offset to codepoint index relative to sd MBCPtoWCCP(ped, pText+sd*ped->cbChar, ed-sd, &ed); if (fLeft && sc) // Going left sc--; if (fWordStop) { while (sc && !psa->pLogAttr[sc].fSoftBreak) sc--; } else { while (sc && !psa->pLogAttr[sc].fCharStop) sc--; } // Set ichMax to next stop ec = sc; if (ec < ed) { ec++; if (fWordStop) { while (ec < ed && !psa->pLogAttr[ec].fSoftBreak) ec++; } else { while (ec < ed && !psa->pLogAttr[ec].fCharStop) ec++; } } WCCPtoMBCP(ped, pText+sd*ped->cbChar, sc, &sc); WCCPtoMBCP(ped, pText+sd*ped->cbChar, ec, &ec); if (pichMin) *pichMin = sd + sc; if (pichMax) *pichMax = sd + ec; ScriptStringFree(&psa); } else { ASSERTHR(hr, ("EditGetNextBoundaries - LpkStringAnalyse")); // Analysis not possible - ignore content of complex scripts. if (pichMin) *pichMin = sd; if (pichMax) *pichMax = ed; } } //// EditNextWord - find adjacent word start and end points // // Duplicates the behaviour of US notepad. // // First stage identify word range using standard // blank/tab as delimiter. // // Second stage - analyse this run and use the logical // attributes to narrow down on words identified by // contextual processing in the complex script shaping // engines. // // // The following diagram describes the identification // of the initial character of the nearest word: // // GOING LEFT: // // Words WWWW WWWW WWWW // from any of xxxxxxxx // to x // // (Notice that the result is always to the left of the initial // position). // // // GOING RIGHT: // // Words WWWW WWWW WWWW // from any of xxxxxxxx // to x // // // Note that CRLF and CRCRLF are treated as words even if not // delimited by blanks. void EditNextWord( PED ped, HDC hdc, PSTR pText, ICH ichStart, BOOL fLeft, ICH *pichMin, ICH *pichMax) { EditGetNextBoundaries(ped, hdc, pText, ichStart, fLeft, pichMin, pichMax, TRUE); } //// IsVietnameseSequenceValid // // Borrow this code from richedit. The logic was provided by Chau Vu. // // April 26, 1999 [wchao] BOOL IsVietnameseSequenceValid (WCHAR ch1, WCHAR ch2) { #define IN_RANGE(n1, b, n2) ((unsigned)((b) - (n1)) <= (unsigned)((n2) - (n1))) int i; static const BYTE vowels[] = {0xF4, 0xEA, 0xE2, 'y', 'u', 'o', 'i', 'e', 'a'}; if (!IN_RANGE(0x300, ch2, 0x323) || // Fast out !IN_RANGE(0x300, ch2, 0x301) && ch2 != 0x303 && ch2 != 0x309 && ch2 != 0x323) { return TRUE; // Not Vietnamese tone mark } for(i = sizeof(vowels) / sizeof(vowels[0]); i--;) if((ch1 | 0x20) == vowels[i]) // Vietnamese tone mark follows return TRUE; // vowel return IN_RANGE(0x102, ch1, 0x103) || // A-breve, a-breve IN_RANGE(0x1A0, ch1, 0x1A1) || // O-horn, o-horn IN_RANGE(0x1AF, ch1, 0x1B0); // U-horn, u-horn } //// EditStringValidate // // Validate the string sequence from the insertion point onward. // Return S_FALSE if any character beyond the insertion point produces fInvalid. // // April 5,1999 [wchao] HRESULT EditStringValidate (STRING_ANALYSIS* psa, int ichInsert) { BOOL fVietnameseCheck = PRIMARYLANGID(THREAD_HKL()) == LANG_VIETNAMESE; int iItem; int i; int l; if (!psa->pLogAttr) return E_INVALIDARG; for (iItem = 0; iItem < psa->cItems; iItem++) { if (g_ppScriptProperties[psa->pItems[iItem].a.eScript]->fRejectInvalid) { i = psa->pItems[iItem].iCharPos; l = psa->pItems[iItem + 1].iCharPos - i; while (l) { if (i >= ichInsert && psa->pLogAttr[i].fInvalid) return S_FALSE; i++; l--; } } else if (fVietnameseCheck && g_ppScriptProperties[psa->pItems[iItem].a.eScript]->fCDM) { // Vietnamese specific sequence check i = psa->pItems[iItem].iCharPos; l = psa->pItems[iItem + 1].iCharPos - i; while (l) { if (i > 0 && i >= ichInsert && !IsVietnameseSequenceValid(psa->pwInChars[i-1], psa->pwInChars[i])) return S_FALSE; i++; l--; } } } return S_OK; } //// EditVerifyText // // Verify the sequence of input text at the insertion point by calling // shaping engine that will return output flag pLogAttr->fInvalid in // the invalid position of text. Return 0 if the insert text has invalid // combination. // // Mar 31,1997 [wchao] INT EditVerifyText (PED ped, HDC hdc, PSTR pText, ICH ichInsert, PSTR pInsertText, ICH cchInsert) { ICH ichRunStart; ICH ichRunEnd; ICH ichLineStart; ICH ichLineEnd; ICH cchVerify; PSTR pVerify; INT iResult; UINT cbChar; BOOL fLocateChar; HRESULT hr; STRING_ANALYSIS *psa; ASSERTS(cchInsert > 0 && pInsertText != NULL && pText != NULL, ("Invalid parameters!")); if (cchInsert > 1) // What we concern here is about how should we handle a series of characters // forming invalid combination(s) that gets updated to the backing store as one // operation (e.g. pasting). We chose to not handle it and the logic is that to // -only- validate a given input char against the current state of the backing store. // // Notice that i use the value 1 so we're safe if inserted text is a DBCS character. // // [Dec 10, 98, wchao] return TRUE; if (ped->fSingle) { ichLineStart = 0; ichLineEnd = ped->cch; } else { ichLineStart = ped->chLines[ped->iCaretLine]; ichLineEnd = ped->iCaretLine == ped->cLines-1 ? ped->cch : ped->chLines[ped->iCaretLine+1]; } ichRunEnd = ichRunStart = ichInsert; // insertion point // Are we at the space? fLocateChar = EDWCH(ichInsert) == SP ? TRUE : FALSE; // Locate a valid char while ( ichRunStart > ichLineStart && fLocateChar && EDWCH(ichRunStart) == SP ) { ichRunStart--; } // Locate run starting point // Find space while (ichRunStart > ichLineStart && EDWCH(ichRunStart - 1) != SP) { ichRunStart--; } // Cover leading spaces while (ichRunStart > ichLineStart && EDWCH(ichRunStart - 1) == SP) { ichRunStart--; } // Locate a valid char while ( ichRunEnd < ichLineEnd && fLocateChar && EDWCH(ichRunEnd) == SP ) { ichRunEnd++; } // Locate run ending point while (ichRunEnd < ichLineEnd && EDWCH(ichRunEnd) != SP) { ichRunEnd++; } ASSERTS(ichRunStart <= ichRunEnd, "Invalid run length!"); // Merge insert text with insertion point run. cchVerify = ichRunEnd - ichRunStart + cchInsert; cbChar = ped->cbChar; pVerify = (PSTR) GlobalAlloc(GMEM_FIXED, cchVerify * cbChar); if (pVerify) { PSTR pv; UINT cbCopy; pv = pVerify; cbCopy = (ichInsert - ichRunStart) * cbChar; memcpy (pv, pText + ichRunStart * cbChar, cbCopy); pv += cbCopy; cbCopy = cchInsert * cbChar; memcpy (pv, pInsertText, cbCopy); pv += cbCopy; cbCopy = (ichRunEnd - ichInsert) * cbChar; memcpy (pv, pText + ichInsert * cbChar, cbCopy); } else { ASSERTS(pVerify, "EditVerifyText: Assertion failure: Could not allocate merge text buffer"); return 1; // can do nothing now just simply accept it. } psa = NULL; iResult = TRUE; // assume verification pass. // Do the real work. // This will call to shaping engine and proceed item by item. // hr = LpkStringAnalyse( hdc, pVerify, cchVerify, 0, GetEditAnsiConversionCharset(ped), SSA_BREAK | (ped->charPasswordChar ? SSA_PASSWORD : 0) | (ped->fRtoLReading ? SSA_RTL : 0), -1, 0, NULL, NULL, NULL, NULL, NULL, &psa); if (SUCCEEDED(hr)) { MBCPtoWCCP(ped, pVerify, ichInsert - ichRunStart, &ichInsert); hr = EditStringValidate(psa, ichInsert); if (hr == S_FALSE) { MessageBeep((UINT)-1); iResult = FALSE; } else if (FAILED(hr)) { ASSERTHR(hr, ("EditVerifyText - EditStringValidate")); } ScriptStringFree(&psa); } else { ASSERTHR(hr, ("EditVerifyText - LpkStringAnalyse")); } GlobalFree((HGLOBAL) pVerify); return iResult; } //// EditProcessMenu // // Process LPK context menu commands. // // April 18, 1997 [wchao] // INT EditProcessMenu (PED ped, UINT idMenuItem) { HWND hwnd; INT iResult; iResult = TRUE; switch (idMenuItem) { case ID_CNTX_RTL: hwnd = ped->hwnd; SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~ES_FMTMASK); if (!ped->fRtoLReading) { SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | (WS_EX_RTLREADING | WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR)); } else { SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) & ~(WS_EX_RTLREADING | WS_EX_RIGHT | WS_EX_LEFTSCROLLBAR)); } break; case ID_CNTX_DISPLAYCTRL: hwnd = ped->hwnd; ped->fDisplayCtrl = !ped->fDisplayCtrl; if (ped->fFlatBorder) { RECT rcT; int cxBorder, cyBorder; GetClientRect(hwnd, &rcT); cxBorder = GetSystemMetrics (SM_CXBORDER); cyBorder = GetSystemMetrics (SM_CYBORDER); InflateRect(&rcT, -cxBorder, -cyBorder); InvalidateRect(hwnd, &rcT, TRUE); } else { InvalidateRect(hwnd, NULL, TRUE); } break; case ID_CNTX_ZWNJ: if (ped->fAnsi) { SendMessageA(ped->hwnd, WM_CHAR, 0x9D, 0); } else { SendMessageW(ped->hwnd, WM_CHAR, U_ZWNJ, 0); } break; case ID_CNTX_ZWJ: if (ped->fAnsi) { SendMessageA(ped->hwnd, WM_CHAR, 0x9E, 0); } else { SendMessageW(ped->hwnd, WM_CHAR, U_ZWJ, 0); } break; case ID_CNTX_LRM: if (ped->fAnsi) { SendMessageA(ped->hwnd, WM_CHAR, 0xFD, 0); } else { SendMessageW(ped->hwnd, WM_CHAR, U_LRM, 0); } break; case ID_CNTX_RLM: if (ped->fAnsi) { SendMessageA(ped->hwnd, WM_CHAR, 0xFE, 0); } else { SendMessageW(ped->hwnd, WM_CHAR, U_RLM, 0); } break; case ID_CNTX_LRE: SendMessageW(ped->hwnd, WM_CHAR, U_LRE, 0); break; case ID_CNTX_RLE: SendMessageW(ped->hwnd, WM_CHAR, U_RLE, 0); break; case ID_CNTX_LRO: SendMessageW(ped->hwnd, WM_CHAR, U_LRO, 0); break; case ID_CNTX_RLO: SendMessageW(ped->hwnd, WM_CHAR, U_RLO, 0); break; case ID_CNTX_PDF: SendMessageW(ped->hwnd, WM_CHAR, U_PDF, 0); break; case ID_CNTX_NADS: SendMessageW(ped->hwnd, WM_CHAR, U_NADS, 0); break; case ID_CNTX_NODS: SendMessageW(ped->hwnd, WM_CHAR, U_NODS, 0); break; case ID_CNTX_ASS: SendMessageW(ped->hwnd, WM_CHAR, U_ASS, 0); break; case ID_CNTX_ISS: SendMessageW(ped->hwnd, WM_CHAR, U_ISS, 0); break; case ID_CNTX_AAFS: SendMessageW(ped->hwnd, WM_CHAR, U_AAFS, 0); break; case ID_CNTX_IAFS: SendMessageW(ped->hwnd, WM_CHAR, U_IAFS, 0); break; case ID_CNTX_RS: SendMessageW(ped->hwnd, WM_CHAR, U_RS, 0); break; case ID_CNTX_US: SendMessageW(ped->hwnd, WM_CHAR, U_US, 0); break; } return iResult; } //// EditSetMenu - Set menu state // // void EditSetMenu(PED ped, HMENU hMenu) { EnableMenuItem(hMenu, ID_CNTX_RTL, MF_BYCOMMAND | MF_ENABLED); CheckMenuItem (hMenu, ID_CNTX_RTL, MF_BYCOMMAND | (ped->fRtoLReading ? MF_CHECKED : MF_UNCHECKED)); if (!ped->fAnsi || ped->charSet == ARABIC_CHARSET || ped->charSet == HEBREW_CHARSET) { // It's unicode, Arabic or Hebrew - we can display and enter at least some control characters EnableMenuItem(hMenu, ID_CNTX_DISPLAYCTRL, MF_BYCOMMAND | MF_ENABLED); CheckMenuItem (hMenu, ID_CNTX_DISPLAYCTRL, MF_BYCOMMAND | (ped->fDisplayCtrl ? MF_CHECKED : MF_UNCHECKED)); EnableMenuItem(hMenu, ID_CNTX_INSERTCTRL, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_LRM, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_RLM, MF_BYCOMMAND | MF_ENABLED); if (!ped->fAnsi || ped->charSet == ARABIC_CHARSET) { // Controls characters in Unicode and ANSI Arabic only EnableMenuItem(hMenu, ID_CNTX_ZWJ, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_ZWNJ, MF_BYCOMMAND | MF_ENABLED); } if (!ped->fAnsi) { // These control characters are specific to the Unicode bidi algorithm EnableMenuItem(hMenu, ID_CNTX_LRE, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_RLE, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_LRO, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_RLO, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_PDF, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_NADS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_NODS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_ASS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_ISS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_AAFS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_IAFS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_RS, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(hMenu, ID_CNTX_US, MF_BYCOMMAND | MF_ENABLED); } } else { // No opportunity to enter control characters EnableMenuItem(hMenu, ID_CNTX_INSERTCTRL, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, ID_CNTX_DISPLAYCTRL, MF_BYCOMMAND | MF_GRAYED); } } //// EditCreateCaretFromFont // // Create one of the special caret shapes for complex script languages. // // returns FALSE if it couldn't create the caret for example in low // memory situations. #define CURSOR_USA 0xffff #define CURSOR_LTR 0xf00c #define CURSOR_RTL 0xf00d #define CURSOR_THAI 0xf00e BOOL EditCreateCaretFromFont( PED ped, HDC hdc, INT nWidth, INT nHeight, WCHAR wcCursorCode ) { BOOL fResult = FALSE; // Assume the worst HBITMAP hbmBits; HDC hcdcBits; HFONT hArrowFont; ABC abcWidth; COLORREF clrBk; WORD gidArrow; UINT uiWidthBits; HBITMAP hOldBitmap; // Create caret from the Arial font hcdcBits = CreateCompatibleDC (hdc); if (!hcdcBits) { return FALSE; } // create Arrow font then select into compatible DC // Bitmap will be XORed with the background color before caret starts blinking. // Therefore, we need to set our bitmap background to be opposite with the DC // actual background in order to generate caret properly. clrBk = GetBkColor(hdc); SetBkColor (hcdcBits, ~clrBk); // Creating the caret with white pattern to be consistant with User-edit field. clrBk = RGB(255, 255 , 255); // White SetTextColor (hcdcBits, clrBk); hArrowFont = CreateFontW ( nHeight, 0, 0, 0, nWidth > 1 ? 700 : 400, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, L"Microsoft Sans Serif" ); if (!hArrowFont) { goto error; } SelectObject (hcdcBits, hArrowFont); // textout the Arrow char to get its bitmap if (!GetCharABCWidthsW (hcdcBits, wcCursorCode, wcCursorCode, &abcWidth)) { goto error; } if (!GetGlyphIndicesW (hcdcBits, &wcCursorCode, 1, &gidArrow, 0)) { goto error; } uiWidthBits = (((abcWidth.abcB)+15)/16)*16; // bitmap width must be WORD-aligned hbmBits = CreateCompatibleBitmap (hdc, uiWidthBits, nHeight); if (!hbmBits) { goto error; } hOldBitmap = SelectObject(hcdcBits, hbmBits); if (!ExtTextOutW (hcdcBits, -abcWidth.abcA, 0, ETO_OPAQUE | ETO_GLYPH_INDEX, NULL, &gidArrow, 1, NULL)) { DeleteObject(SelectObject(hcdcBits, hOldBitmap)); goto error; } // free current caret bitmap handle if we have one if (ped->hCaretBitmap) { DeleteObject (ped->hCaretBitmap); } ped->hCaretBitmap = hbmBits; if (wcCursorCode == CURSOR_RTL) { // RTL cursor has vertical stroke on right hand side. Overlap LTR and RTL // positions by one pixel so cursor doesn't go outside the edit control // at small sizes. ped->iCaretOffset = 1 - (int) abcWidth.abcB; // (Allow one pixel overlap between ltr & rtl) } else { ped->iCaretOffset = 0; } fResult = CreateCaret (ped->hwnd, hbmBits, 0, 0); error: // release allocated objects if (hArrowFont) { DeleteObject(hArrowFont); } if (hcdcBits) { DeleteDC(hcdcBits); } return fResult; } //// EditCreateCaret // // Create locale specific caret shape // // April 25, 1997 [wchao] // May 1st, 1997 [samera] Added traditional BiDi cursor under #ifdef // Aug 15th, 2000 [dbrown] Fix prefix bug 43057 - no handling for low memory // // Note // Complex script carets are mapped in the private area of Unicode in the font. // LTR cursor 0xf00c // RTL cursor 0xf00d // Thai cursor 0xf00e // #define LANG_ID(x) ((DWORD)(DWORD_PTR)x & 0x000003ff); INT EditCreateCaret( PED ped, HDC hdc, INT nWidth, INT nHeight, UINT hklCurrent) { UINT uikl; ULONG ulCsrCacheCount; WCHAR wcCursorCode; ped->iCaretOffset = 0; if (!hklCurrent) { uikl = LANG_ID(GetKeyboardLayout(0L)); } else { uikl = LANG_ID(hklCurrent); } // Choose caret shape - use either the standard US caret, or a // special shape from the Arial font. wcCursorCode = CURSOR_USA; switch (uikl) { case LANG_THAI: wcCursorCode = CURSOR_THAI; break; // // we may need to call GetLocaleInfo( FONT_SIGNATURE ...) to // properly detect RTL languages. // case LANG_ARABIC: case LANG_FARSI: case LANG_URDU: case LANG_HEBREW: wcCursorCode = CURSOR_RTL; break; default: // Make sure the NLS settings are cached before checking on g_UserBidiLocale. it happens! if ( g_ulNlsUpdateCacheCount==-1 && (ulCsrCacheCount = NlsGetCacheUpdateCount()) != g_ulNlsUpdateCacheCount) { TRACE(NLS, ("LPK : Updating NLS cache from EditCreateCaret, lpkNlsCacheCount=%ld, CsrssCacheCount=%ld", g_ulNlsUpdateCacheCount ,ulCsrCacheCount)); g_ulNlsUpdateCacheCount = ulCsrCacheCount; // Update the cache now ReadNLSScriptSettings(); } if (g_UserBidiLocale) { // Other keyboards have a left-to-right pointing caret // in Bidi locales. wcCursorCode = CURSOR_LTR; } } if (wcCursorCode != CURSOR_USA) { // Try to create a caret from the Arial font if (!EditCreateCaretFromFont(ped, hdc, nWidth, nHeight, wcCursorCode)) { // Caret from font failed - low memory perhaps wcCursorCode = CURSOR_USA; // Fall back to USA cursor } } if (wcCursorCode == CURSOR_USA) { // Use Windows default caret return CreateCaret (ped->hwnd, NULL, nWidth, nHeight); } else { return TRUE; } } //// EditAdjustCaret // // Adjust caret after insertion/deletion to avoid the caret in between a cluster, // very common in Indic e.g. // // 1. Deleting the space "X| Y" and it becomes "X|y". // 2. Inserting 'X' at "|Y" and it becomes "X|y". // // May 3,1999 [wchao] // INT EditAdjustCaret ( PED ped, HDC hdc, PSTR pText, ICH ich) { #if 0 // // Indiannt unanimously request this feature to be removed for final product. // (wchao - 7/12/99) // ICH ichMin; ICH ichMax; if (ich < ped->cch) { EditGetNextBoundaries(ped, hdc, pText, ich, FALSE, &ichMin, &ichMax, FALSE); if (ich > ichMin) ich = ichMax; } #endif UNREFERENCED_PARAMETER(ped); UNREFERENCED_PARAMETER(hdc); UNREFERENCED_PARAMETER(pText); return ich; } //// Edit callout // // The LPK edit support functions are acessed by the edit // control code through a struct defined in user.h. // // Here we initialise that struct with the addresses // of the callout functions. LPKEDITCALLOUT LpkEditControl = { EditCreate, EditIchToXY, EditMouseToIch, EditCchInWidth, EditGetLineWidth, EditDrawText, EditHScroll, EditMoveSelection, EditVerifyText, EditNextWord, EditSetMenu, EditProcessMenu, EditCreateCaret, EditAdjustCaret };