// Edit.cpp : Implementation of cEdit #include "stdafx.h" #include "DecalControls.h" #include "Edit.h" ///////////////////////////////////////////////////////////////////////////// // cEdit #define TIMER_CARET 1 #define TIMER_REPEAT 2 #define END -1 #ifndef MIN #define MIN(x,y) (((x) < (y)) ? (x) : (y)) #endif #ifndef MAX #define MAX(x,y) (((x) > (y)) ? (x) : (y)) #endif cEdit::cEdit() : m_bAllowCapture( true ), // cyn -- 15/10/2002 m_bCapture( false ), m_bDrawCaret( false ), m_bSelecting( false ), m_nSelStartChar( 0 ), m_nSelStart( 0 ), m_nCaretChar( 0 ), m_nCaret( 0 ), m_nOffset( 0 ), m_nTextColor( 0 ), m_nCaretHeight( 0 ) { m_szMargin.cx = 3; m_szMargin.cy = 1; } void cEdit::checkFont() { if( m_pFont.p == NULL ) { CComPtr< IPluginSite > pPlugin; m_pSite->get_PluginSite( &pPlugin ); BSTR bstrFontName; pPlugin->get_FontName(&bstrFontName); pPlugin->CreateFont( bstrFontName /*_bstr_t( _T( "Times New Roman" ) )*/, 14, 0, &m_pFont ); } } void cEdit::sendTextChange() { long nID; m_pSite->get_ID( &nID ); Fire_Change( nID, _bstr_t( m_strText.c_str() ) ); } void cEdit::Delete() { if (m_nCaretChar == m_nSelStartChar) { return; } m_strText.assign( m_strText.substr(0, MIN(m_nCaretChar, m_nSelStartChar)) + m_strText.substr(MAX(m_nCaretChar, m_nSelStartChar), m_strText.length()) ); if (m_bPassword) { /* This is bad, someone fix it */ m_strPass.assign( m_strPass.substr(0, MIN(m_nCaretChar, m_nSelStartChar)) + m_strPass.substr(MAX(m_nCaretChar, m_nSelStartChar), m_strPass.length()) ); } m_nSelStartChar = MIN(m_nCaretChar, m_nSelStartChar); put_Caret( m_nSelStartChar ); } void cEdit::Copy() { HGLOBAL hMem; HRESULT hr; long hWnd; IPluginSite *Plug; char *lpMem; int Start, Length; if (m_bPassword) { return; } hr = m_pSite->get_PluginSite(&Plug); if (FAILED(hr)) { return; } hr = Plug->get_HWND(&hWnd); Plug->Release(); Plug = NULL; if (FAILED(hr)) { return; } if (m_nCaretChar < m_nSelStartChar) { Start = m_nCaretChar; Length = m_nSelStartChar - Start; } else { Start = m_nSelStartChar; Length = m_nCaretChar - Start; } hMem = GlobalAlloc(GMEM_MOVEABLE, Length+1); if (!hMem) { return; } lpMem = (char *)GlobalLock(hMem); if (!lpMem) { GlobalFree(hMem); return; } /* This is gauranteed to nul-terminate */ lstrcpyn( lpMem, m_strText.c_str() + Start, Length + 1); GlobalUnlock(hMem); if (!OpenClipboard((HWND)hWnd)) { GlobalFree(hMem); return; } EmptyClipboard(); GetLastError(); SetClipboardData(CF_TEXT, hMem); GetLastError(); CloseClipboard(); } void cEdit::Cut() { if (!m_bPassword) { Copy(); Delete(); } } // OK, this paste code isn't pretty, but it's functional for now. Look for a full rewrite later! // cyn, 22/10/2002 void cEdit::Paste() { HANDLE hClipboardData; char *sData; int Count, Length; Delete(); if (!OpenClipboard(NULL)) { return; } hClipboardData = GetClipboardData(CF_TEXT); if (hClipboardData) { sData = (char *)GlobalLock(hClipboardData); if (sData) { Length = lstrlen(sData); for (Count=0;Count Length) { return Length; } return Offset; } STDMETHODIMP cEdit::Reformat() { // Just a typical post-resize type of behavior, make sure everythign is still visible as // the user likes. if( m_nCaretChar > m_strText.length() ) put_Caret( m_strText.length() ); else // Force a recalculation for scrolling put_Caret( m_nCaretChar ); //put_Caret( m_nCaretChar ); return S_OK; } STDMETHODIMP cEdit::Render( ICanvas *pCanvas ) { HDC RenderDC; HRESULT hr; checkFont(); // Draw the background RECT rcPos; m_pSite->get_Position( &rcPos ); RECT rcClient = { 0, 0, rcPos.right - rcPos.left, rcPos.bottom - rcPos.top }; if( m_pBackground.p != NULL ) { static POINT ptOff = { 0, 0 }; m_pBackground->PatBlt( pCanvas, &rcClient, &ptOff ); } RECT rcText = { m_szMargin.cx, m_szMargin.cy, rcClient.right - m_szMargin.cx, rcClient.bottom - m_szMargin.cy }; VARIANT_BOOL bVisible; pCanvas->SetClipRect( &rcText, &bVisible ); if( !bVisible ) // Nothing to draw huzzah! return S_OK; long lFlags = 0; if( m_bAA ) lFlags |= eAA; if( m_bOutline ) lFlags |= eOutlined; // Draw the text POINT ptText = { -m_nOffset, 0 }; if( m_strText.length() > 0 ) if (m_bPassword) { m_pFont->DrawTextEx( &ptText, _bstr_t( m_strPass.c_str() ), m_nTextColor, m_nOutlineColor, lFlags, pCanvas ); } else { m_pFont->DrawTextEx( &ptText, _bstr_t( m_strText.c_str() ), m_nTextColor, m_nOutlineColor, lFlags, pCanvas ); } if( m_bCapture && m_bDrawCaret ) { // Draw the caret RECT rcCaret = { m_nCaret - m_nOffset, m_szMargin.cy, m_nCaret - m_nOffset + 1, m_szMargin.cy + m_nCaretHeight }; pCanvas->Fill( &rcCaret, m_nTextColor ); } if( m_bCapture ) // - Haz, fix for the annoying multiple edit selection ghosts { if (m_nCaretChar != m_nSelStartChar) { hr = pCanvas->GetDC(&RenderDC); if (SUCCEEDED(hr)) { RECT rcSelect; rcSelect.top = m_szMargin.cy; rcSelect.bottom = m_szMargin.cy + m_nCaretHeight; rcSelect.left = m_nSelStart - m_nOffset; rcSelect.right = m_nCaret - m_nOffset; if (rcSelect.right < rcSelect.left) { int t; t = rcSelect.left; rcSelect.left = rcSelect.right; rcSelect.right = t; } InvertRect(RenderDC, &rcSelect); pCanvas->ReleaseDC(); } } } return S_OK; } STDMETHODIMP cEdit::MouseDown( MouseState *pMS ) { if( !m_bCapture ) Capture(); else { _ASSERTE(!m_bSelecting); checkFont(); // Hit test the character and move to that position long nHitChar; if(m_bPassword) m_pFont->HitTest( _bstr_t( m_strPass.c_str() ), pMS->client.x + m_nOffset - m_szMargin.cx, &nHitChar ); else m_pFont->HitTest( _bstr_t( m_strText.c_str() ), pMS->client.x + m_nOffset - m_szMargin.cx, &nHitChar ); // Reset the timer put_Caret( nHitChar ); m_bSelecting = true; } return S_OK; } STDMETHODIMP cEdit::MouseUp(MouseState *pMS) { /* I assume it's possible to trigger a mouse up without a mousedown */ m_bSelecting = false; return S_OK; } STDMETHODIMP cEdit::MouseMove(MouseState *pMS) { long nHitChar; if (!m_bCapture) { _ASSERTE(!m_bSelecting); return S_OK; } if (!m_bSelecting) { return S_OK; } checkFont(); if (m_bPassword) { m_pFont->HitTest( _bstr_t( m_strPass.c_str() ), pMS->client.x + m_nOffset - m_szMargin.cx, &nHitChar ); } else { m_pFont->HitTest( _bstr_t( m_strText.c_str() ), pMS->client.x + m_nOffset - m_szMargin.cx, &nHitChar ); } put_Caret(nHitChar); return S_OK; } STDMETHODIMP cEdit::KeyboardChar( KeyState *pKS ) { if (pKS->ctrl && pKS->vkey == 22) { Paste(); } else if (pKS->ctrl && pKS->vkey == 3) { Copy(); } else if (pKS->ctrl && pKS->vkey == 24) { Cut(); } else { // Look for special characters switch( pKS->vkey ) { case VK_BACK: if (m_nCaretChar != m_nSelStartChar) { Delete(); } else { if( m_nCaretChar > 0 ) { // Decrease the caret position put_Caret( m_nCaretChar - 1 ); // Remove the character m_strText.erase( m_strText.begin() + m_nCaretChar, m_strText.begin() + m_nCaretChar + 1 ); if(m_bPassword) m_strPass.erase( m_strPass.begin() + m_nCaretChar, m_strPass.begin() + m_nCaretChar + 1 ); } } break; default: // Assume every other character is being added if (m_nCaretChar != m_nSelStartChar) { Delete(); } char wc[ 2 ] = { pKS->vkey, L'\0' }; m_strText.insert( m_strText.begin() + m_nCaretChar, wc, wc + 1 ); if(m_bPassword) { wc[0] = L'*'; m_strPass.insert( m_strPass.begin() + m_nCaretChar, wc, wc + 1 ); } put_Caret( m_nCaretChar + 1 ); } } m_pSite->Invalidate(); sendTextChange(); return S_OK; } STDMETHODIMP cEdit::KeyboardEndCapture( VARIANT_BOOL bCancel ) { if (bCancel) { // cyn, 15/10/2002 m_bAllowCapture = false; } m_bCapture = false; m_pSite->EndTimer( TIMER_CARET ); m_pSite->Invalidate(); long nID; m_pSite->get_ID( &nID ); Fire_End( nID, ( bCancel ) ? VARIANT_FALSE : VARIANT_TRUE ); m_bAllowCapture = true; // cyn, 15/10/2002 return S_OK; } STDMETHODIMP cEdit::KeyboardEvent( long nMsg, long wParam, long lParam ) { const char *Start; int Search; bool Shift, OldSelecting; int Length; if( nMsg != WM_KEYDOWN ) // Only looking for keydown messages return S_OK; Shift = (GetAsyncKeyState(VK_SHIFT) < 0); OldSelecting = m_bSelecting; m_bSelecting |= Shift; /* Hrrmm.. */ // Look for special characters switch( wParam ) { case VK_DELETE: if (GetAsyncKeyState( VK_SHIFT ) < 0) { Cut(); } else { if (m_nCaretChar != m_nSelStartChar) { Delete(); } else { if( m_nCaretChar < m_strText.length() ) { // Remove the character m_strText.erase( m_strText.begin() + m_nCaretChar, m_strText.begin() + m_nCaretChar + 1 ); if(m_bPassword) m_strPass.erase( m_strPass.begin() + m_nCaretChar, m_strPass.begin() + m_nCaretChar + 1 ); } } } break; case VK_HOME: put_Caret( 0 ); break; case VK_END: put_Caret( END ); break; case VK_INSERT: // cyn, 22/10/2002 if (Shift) { m_bSelecting = false; Paste(); } else if (GetAsyncKeyState( VK_CONTROL) < 0 ) { Copy(); } break; case VK_LEFT: if( m_nCaretChar > 0 ) { if (GetAsyncKeyState( VK_CONTROL ) < 0) { if (m_bPassword) { Start = m_strPass.c_str(); } else { Start = m_strText.c_str(); } /* Loop until we find a non-space */ for (Search=m_nCaretChar-1;Search>0;Search--) { if (Start[Search] != ' ') { break; } } /* Loop until we find a space */ for (;Search>0;Search--) { if (Start[Search] == ' ') { Search++; break; } } if (Search < 0) { _ASSERTE( false ); Search = 0; } put_Caret( Search ); } else { put_Caret( m_nCaretChar - 1 ); } } break; case VK_RIGHT: Length = m_strText.length(); if( m_nCaretChar < Length ) { if (GetAsyncKeyState( VK_CONTROL ) < 0) { /* Possible change this, AC edit box is slightly different */ if (m_bPassword) { Start = m_strPass.c_str(); } else { Start = m_strText.c_str(); } /* Loop until we find a non-space */ for (Search=m_nCaretChar+1;Search Length) { _ASSERTE( false ); Search = END; } put_Caret( Search ); } else { put_Caret( m_nCaretChar + 1 ); } } break; default: // No processing m_bSelecting = OldSelecting; return S_OK; } m_bSelecting = OldSelecting; m_pSite->Invalidate(); sendTextChange(); return S_OK; } STDMETHODIMP cEdit::get_Text(BSTR *pVal) { _ASSERTE( pVal != NULL ); *pVal = T2BSTR( m_strText.c_str() ); return S_OK; } STDMETHODIMP cEdit::put_Text(BSTR newVal) { USES_CONVERSION; m_strText = OLE2T( newVal ); m_strPass.assign(""); if(m_bPassword) { m_strPass.append(m_strText.length(), '*'); } m_nSelStartChar = m_nCaretChar = 0; m_pSite->Invalidate(); return S_OK; } STDMETHODIMP cEdit::get_Caret(long *pVal) { _ASSERTE( pVal != NULL ); *pVal = m_nCaretChar; return S_OK; } // Only scroll within 10 pixels of the edge #define EDIT_HYSTERESIS 18 STDMETHODIMP cEdit::put_Caret(long newVal) { std::string strSample; checkFont(); m_nCaretChar = Normalize(newVal); if(m_bPassword) { _ASSERTE( m_strPass.length() == m_strText.length() ); strSample.assign(m_strPass.begin(), m_strPass.begin() + m_nCaretChar); } else { strSample.assign(m_strText.begin(), m_strText.begin() + m_nCaretChar); } // Calculate the actual character position SIZE szText; if (!SUCCEEDED (m_pFont->MeasureText( _bstr_t( strSample.c_str() ), &szText ))) { return E_FAIL; } m_nCaretHeight = szText.cy; m_nCaret = szText.cx; RECT rcPosition; m_pSite->get_Position( &rcPosition ); long nWidth = ( rcPosition.right - rcPosition.left ) - m_szMargin.cx * 2 - 2; // Check for offset updates if( m_nCaret < ( m_nOffset + EDIT_HYSTERESIS ) ) { // The caret has moved in front of the scroll region m_nOffset = m_nCaret - EDIT_HYSTERESIS; if( m_nOffset < 0 ) // The offset is less thant the beginning, correct it m_nOffset = 0; } else if( m_nCaret > nWidth + m_nOffset ) // The caret has moved off the end of the visible region // NOTE: the +1 is to account for the 1 pixel width of the caret m_nOffset = m_nCaret - nWidth; if( m_bCapture ) { // Force the caret on for a half second to make sure the user sees the new position m_pSite->EndTimer( TIMER_CARET ); m_pSite->StartTimer( TIMER_CARET, 500 ); m_bDrawCaret = true; } if (m_bSelecting) { if(m_bPassword) { strSample.assign(m_strPass.begin(), m_strPass.begin() + m_nSelStartChar); } else { strSample.assign(m_strText.begin(), m_strText.begin() + m_nSelStartChar); } if (!SUCCEEDED (m_pFont->MeasureText( _bstr_t( strSample.c_str() ), &szText))) { return E_FAIL; } m_nSelStart = szText.cx; } else { m_nSelStartChar = m_nCaretChar; m_nSelStart = m_nCaret; } m_pSite->Invalidate(); return S_OK; } /* nEnd < nStart is a valid condition */ STDMETHODIMP cEdit::Select(long nStart, long nEnd) { bool OldSelection; OldSelection = m_bSelecting; m_bSelecting = false; put_Caret(Normalize(nStart)); m_bSelecting = true; put_Caret(Normalize(nEnd)); m_bSelecting = OldSelection; return S_OK; } /* I think this is done */ STDMETHODIMP cEdit::get_SelectedText(BSTR *pVal) { int Start, Len; _ASSERTE(pVal); if (m_bPassword) { return S_FALSE; } if (m_nCaretChar < m_nSelStartChar) { Start = m_nCaretChar; Len = m_nSelStartChar - Start; } else { Start = m_nSelStartChar; Len = m_nCaretChar - Start; } *pVal = T2BSTR( m_strText.substr(Start, Len).c_str() ); return S_OK; } STDMETHODIMP cEdit::put_SelectedText(BSTR newVal) { USES_CONVERSION; std::string strInsert; int Length; if (m_nCaretChar != m_nSelStartChar) { Delete(); } strInsert = OLE2T(newVal); Length = strInsert.length(); m_strText.insert(m_strText.begin() + m_nCaretChar, strInsert.begin(), strInsert.begin() + Length); if (m_bPassword) { m_strPass.insert(m_strPass.begin(), Length, '*'); } put_Caret( m_nCaretChar + Length ); m_pSite->Invalidate(); return S_OK; } STDMETHODIMP cEdit::TimerTimeout(long nID, long, long, VARIANT_BOOL *pbContinue) { if( nID == TIMER_CARET ) { m_bDrawCaret = !m_bDrawCaret; m_pSite->Invalidate(); *pbContinue = VARIANT_TRUE; } return S_OK; } STDMETHODIMP cEdit::SchemaLoad(IView *pView, IUnknown *pSchema) { USES_CONVERSION; // Load the font and background image CComPtr< IPluginSite > pPlugin; m_pSite->get_PluginSite( &pPlugin ); pPlugin->CreateFontSchema( 14, 0, pSchema, &m_pFont ); pPlugin->LoadImageSchema( pSchema, &m_pBackground ); MSXML::IXMLDOMElementPtr pElement = pSchema; _variant_t vText = pElement->getAttribute( _T( "text" ) ), vTextColor = pElement->getAttribute( _T( "textcolor" ) ), vMarginX = pElement->getAttribute( _T( "marginx" ) ), vMarginY = pElement->getAttribute( _T( "marginy" ) ), vPassword = pElement->getAttribute( _T( "password" ) ), vOutline = pElement->getAttribute( _T( "outlinecolor" ) ), vAntialias = pElement->getAttribute( _T( "aa" ) ); if( vText.vt != VT_NULL ) { _ASSERTE( vText.vt == VT_BSTR ); m_strText = OLE2T( vText.bstrVal ); } if( vTextColor.vt != VT_NULL ) { try { m_nTextColor = static_cast< long >( vTextColor ); } catch( ... ) { // Type conversion error _ASSERTE( FALSE ); } } if( vMarginX.vt != VT_NULL ) { try { m_szMargin.cx = static_cast< long >( vMarginX ); } catch( ... ) { // Type conversion error _ASSERTE( FALSE ); } } if( vMarginY.vt != VT_NULL ) { try { m_szMargin.cy = static_cast< long >( vMarginY ); } catch( ... ) { // Type conversion error _ASSERTE( FALSE ); } } if( vPassword.vt != VT_NULL) { try { m_bPassword = static_cast< bool >( vPassword ); } catch( ... ) { _ASSERTE( FALSE ); } } else m_bPassword = false; if( vText.vt != VT_NULL ) { _ASSERTE( vText.vt == VT_BSTR ); m_strText = OLE2T( vText.bstrVal ); if(m_bPassword) for(int i=0; i( vOutline ); } catch( ... ) { // Type conversion error _ASSERTE( FALSE ); } } m_bAA = true; if( vAntialias.vt != VT_NULL ) { try { m_bAA = static_cast< bool >( vAntialias ); } catch( ... ) { // Type conversion error _ASSERTE( FALSE ); } } return S_OK; } STDMETHODIMP cEdit::get_Background(IImageCacheDisp **pVal) { _ASSERTE( pVal != NULL ); if( m_pBackground.p == NULL ) *pVal = NULL; else m_pBackground->QueryInterface( pVal ); return S_OK; } STDMETHODIMP cEdit::putref_Background(IImageCacheDisp *newVal) { if( m_pBackground.p ) m_pBackground.Release(); if( newVal != NULL ) { HRESULT hRes = newVal->QueryInterface( &m_pBackground ); _ASSERTE( SUCCEEDED( hRes ) ); } m_pSite->Invalidate(); return S_OK; } STDMETHODIMP cEdit::get_Font(IFontCacheDisp **pVal) { _ASSERTE( pVal != NULL ); if( m_pFont.p == NULL ) *pVal = NULL; else m_pFont->QueryInterface( pVal ); return S_OK; } STDMETHODIMP cEdit::putref_Font(IFontCacheDisp *newVal) { _ASSERTE( newVal != NULL ); if( m_pFont.p ) m_pFont.Release(); HRESULT hRes = newVal->QueryInterface( &m_pFont ); _ASSERTE( SUCCEEDED( hRes ) ); // Reformat put_Caret( m_nCaretChar ); return S_OK; } STDMETHODIMP cEdit::get_TextColor(long *pVal) { _ASSERTE( pVal != NULL ); *pVal = m_nTextColor; return S_OK; } STDMETHODIMP cEdit::put_TextColor(long newVal) { _ASSERTE( ( newVal & 0xFF000000 ) == 0 ); m_nTextColor = newVal; m_pSite->Invalidate(); return S_OK; } STDMETHODIMP cEdit::SetMargins(long nX, long nY) { _ASSERTE( nX >= 0 && nY >= 0 ); m_szMargin.cx = nX; m_szMargin.cy = nY; put_Caret( m_nCaretChar ); return S_OK; } STDMETHODIMP cEdit::Capture() { _ASSERTE( !m_bCapture ); if ( m_bAllowCapture ) { m_pSite->CaptureKeyboard(); /* Until we actually capture the keyboard, other code can still execute */ /* If that other code calls put_Caret (eg, put_SelectedText), the timer is set */ /* If that timer is set, and we then try to set it again, it has an error */ m_bCapture = true; m_bDrawCaret = true; m_pSite->Invalidate(); m_pSite->StartTimer( TIMER_CARET, 500 ); if (m_nCaretChar == m_nSelStartChar) { Select(0, END); } long nID; m_pSite->get_ID( &nID ); Fire_Begin( nID ); return S_OK; } else { return S_FALSE; } }