// DSAddIn.cpp : header file
//
// Written by Christoph Weber (cpwech@oopsi.de)
// Copyright (c) 2001.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included. If
// the source code in this file is used in any commercial application
// then a simple email would be nice.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to your computer.
//
// Please let me know of any bugs/mods/improvements that you have found/fixed
// and I will fix/incorporate them into this file.
//
#include "stdafx.h"
#include "DeveloperStudioAutoText.h"
#include "DSAddIn.h"
#include "Commands.h"
#include "ObjModel\TextGuid.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CDSAddIn::CDSAddIn() : m_wndDevStudioMainWindow( &m_wndActiveMdiChild )
{
}
CDSAddIn::~CDSAddIn()
{
}
// This is called when the user first loads the add-in, and on start-up
// of each subsequent Developer Studio session
STDMETHODIMP CDSAddIn::OnConnection(IApplication* pApp, VARIANT_BOOL bFirstTime,
long dwCookie, VARIANT_BOOL* OnConnection)
{
HWND hWnd;
HWND hDesktopWnd = ::GetDesktopWindow();
char szWindowTitle[512];
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// Store info passed to us
IApplication* pApplication = NULL;
if (FAILED(pApp->QueryInterface(IID_IApplication, (void**) &pApplication))
|| pApplication == NULL)
{
*OnConnection = VARIANT_FALSE;
return S_OK;
}
m_dwCookie = dwCookie;
// Create command dispatch, send info back to DevStudio
CCommandsObj::CreateInstance(&m_pCommands);
m_pCommands->AddRef();
//First find the main window
hWnd = ::GetActiveWindow();
while( hWnd && hWnd != hDesktopWnd )
{
m_hDevStudioWnd = hWnd;
hWnd = ::GetParent(hWnd);
}
GetWindowText( m_hDevStudioWnd, szWindowTitle, 512 );
if( strstr( szWindowTitle, "Microsoft Visual C++" ) )
{
m_wndDevStudioMainWindow.SubclassWindow( m_hDevStudioWnd );
// The MDIArea always has ID 0xe900
m_hDevStudioMDIArea = GetDlgItem( m_hDevStudioWnd, 0xe900 );
m_wndMDIAreaManager.SubclassWindow( m_hDevStudioMDIArea );
}
else
{
ASSERT( FALSE );
MessageBox( NULL, "Main window title not matching!\nInstallation failed.", "WARNING!", MB_OK|MB_ICONWARNING );
}
m_wndActiveMdiChild.SetCommandObject( m_pCommands );
m_pCommands->SetAddIn( this );
// The QueryInterface above AddRef'd the Application object. It will
// be Release'd in CCommand's destructor.
m_pCommands->SetApplicationObject(pApplication);
// (see stdafx.h for the definition of VERIFY_OK)
VERIFY_OK(pApplication->SetAddInInfo((long) AfxGetInstanceHandle(),
(LPDISPATCH) m_pCommands, IDR_TOOLBAR_MEDIUM, IDR_TOOLBAR_LARGE, m_dwCookie));
// Inform DevStudio of the commands we implement
// TODO: Replace the AddCommand call below with a series of calls,
// one for each command your add-in will add.
// The command name should not be localized to other languages. The
// tooltip, command description, and other strings related to this
// command are stored in the string table (IDS_CMD_STRING) and should
// be localized.
LPCTSTR szCommand = _T("DeveloperStudioAutoTextCommand");
VARIANT_BOOL bRet;
CString strCmdString;
strCmdString.LoadString(IDS_CMD_STRING);
strCmdString = szCommand + strCmdString;
CComBSTR bszCmdString(strCmdString);
CComBSTR bszMethod(_T("DeveloperStudioAutoTextCommandMethod"));
CComBSTR bszCmdName(szCommand);
VERIFY_OK(pApplication->AddCommand(bszCmdString, bszMethod, 0, m_dwCookie, &bRet));
if (bRet == VARIANT_FALSE)
{
// AddCommand failed because a command with this name already
// exists. You may try adding your command under a different name.
// Or, you can fail to load as we will do here.
*OnConnection = VARIANT_FALSE;
return S_OK;
}
// Add toolbar buttons only if this is the first time the add-in
// is being loaded. Toolbar buttons are automatically remembered
// by Developer Studio from session to session, so we should only
// add the toolbar buttons once.
if (bFirstTime == VARIANT_TRUE)
{
VERIFY_OK(pApplication->
AddCommandBarButton(dsGlyph, bszCmdName, m_dwCookie));
}
if( bRet )
{
/////
szCommand = _T("DeveloperStudioOpenHeaderCommand");
CString strCmdString;
strCmdString.LoadString(IDS_OPEN_HEADER_CMD_STRING);
strCmdString = szCommand + strCmdString;
bszCmdString = strCmdString;
bszMethod =_T("DeveloperStudioOpenHeaderCommandMethod");
bszCmdName =szCommand;
VERIFY_OK(pApplication->AddCommand(bszCmdString, bszMethod, 1, m_dwCookie, &bRet));
if (bRet == VARIANT_FALSE)
{
// AddCommand failed because a command with this name already
// exists. You may try adding your command under a different name.
// Or, you can fail to load as we will do here.
*OnConnection = VARIANT_FALSE;
return S_OK;
}
// Add toolbar buttons only if this is the first time the add-in
// is being loaded. Toolbar buttons are automatically remembered
// by Developer Studio from session to session, so we should only
// add the toolbar buttons once.
if (bFirstTime == VARIANT_TRUE)
{
VERIFY_OK(pApplication->
AddCommandBarButton(dsGlyph, bszCmdName, m_dwCookie));
}
}
*OnConnection = VARIANT_TRUE;
return S_OK;
}
// This is called on shut-down, and also when the user unloads the add-in
STDMETHODIMP CDSAddIn::OnDisconnection(VARIANT_BOOL bLastTime)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_pCommands->UnadviseFromEvents();
m_pCommands->Release();
m_pCommands = NULL;
//disconnect our subclassed windows. We don't need them anymore
if( m_wndActiveMdiChild.m_hWnd )
{
m_wndActiveMdiChild.UnsubclassWindow();
}
if( m_wndDevStudioMainWindow.m_hWnd )
{
m_wndDevStudioMainWindow.UnsubclassWindow();
}
if( m_wndMDIAreaManager.m_hWnd )
{
m_wndMDIAreaManager.UnsubclassWindow();
}
return S_OK;
}
void CDSAddIn::SetMDIWindowActive( IDispatch* pITheWindow, IDispatch* pIDocument, BOOL bActivate )
{
CMDIChildWnd* pMdi = m_wndMDIAreaManager.GetActiveMDI();
m_wndActiveMdiChild.SetActive( pMdi, pITheWindow, pIDocument );
}
//Special hack, because cursor movements and some other special keys
//are translated by the mainframe into WM_COMMAND-messages
BEGIN_MESSAGE_MAP( C_DevStudioMainWindowFilter, CWnd)
ON_COMMAND_RANGE( 0x18808, 0x1880b, OnKeyLeftRightUpDown )
ON_COMMAND( 0x18781, OnEnterPressed )
ON_COMMAND( 0x18763, OnTabPressed )
ON_COMMAND( 0x1878b, OnBackspacePressed )
ON_COMMAND( 0x1e120, OnDelPressed )
ON_COMMAND( ID_EDIT_UNDO, OnUndo )
END_MESSAGE_MAP()
C_DevStudioMainWindowFilter::C_DevStudioMainWindowFilter( C_ActiveMDIWindowFilter* pActiveMdiWindow )
{
m_pActiveMdiWindow = pActiveMdiWindow;
}
C_DevStudioMainWindowFilter::~C_DevStudioMainWindowFilter()
{
if( m_hWnd )
{
UnsubclassWindow();
}
}
//route these to the active MDI window
LRESULT C_DevStudioMainWindowFilter::OnKeyLeftRightUpDown( UINT nMsg )
{
m_pActiveMdiWindow->OnKeyLeftRightUpDown( nMsg-0x8808 ); //left=0,right=1
return CWnd::Default();
}
void C_DevStudioMainWindowFilter::OnEnterPressed()
{
m_pActiveMdiWindow->OnSpecialKeyPressed( VK_RETURN );
CWnd::Default();
}
void C_DevStudioMainWindowFilter::OnTabPressed()
{
m_pActiveMdiWindow->OnSpecialKeyPressed( VK_TAB );
CWnd::Default();
}
void C_DevStudioMainWindowFilter::OnBackspacePressed()
{
m_pActiveMdiWindow->OnSpecialKeyPressed( VK_BACK );
CWnd::Default();
}
void C_DevStudioMainWindowFilter::OnDelPressed()
{
m_pActiveMdiWindow->OnSpecialKeyPressed( VK_DELETE );
CWnd::Default();
}
void C_DevStudioMainWindowFilter::OnUndo()
{
m_pActiveMdiWindow->OnSpecialKeyPressed( ID_EDIT_UNDO );
CWnd::Default();
}
//We also subclass the mdiclientarea, so we easily can get the active
//MDIChildFrame and to determine when to replace text
C_MDIAreaManagerFilter::C_MDIAreaManagerFilter()
{
}
C_MDIAreaManagerFilter::~C_MDIAreaManagerFilter()
{
if( m_hWnd )
{
UnsubclassWindow();
}
}
CMDIChildWnd* C_MDIAreaManagerFilter::GetActiveMDI()
{
CMDIChildWnd* pErg;
BOOL bMaximized;
HWND hWnd = (HWND)::SendMessage(m_hWnd, WM_MDIGETACTIVE, 0, (LPARAM)&bMaximized);
pErg = (CMDIChildWnd*)CWnd::FromHandle(hWnd);
return pErg;
}
//Finally we subclass the active MDIWindow's view to geht all
//Keyboard-Messages
BEGIN_MESSAGE_MAP(C_ActiveMDIWindowFilter, CWnd)
ON_WM_KEYDOWN()
ON_WM_CHAR()
ON_COMMAND( ID_DONT_CORRECT_IT, OnDummy )
ON_COMMAND( ID_REPLACE_BY, OnDummy )
END_MESSAGE_MAP()
C_ActiveMDIWindowFilter::C_ActiveMDIWindowFilter()
{
m_pIActiveTextDocument = NULL;
m_bEnableAutoCorrect = TRUE;
}
C_ActiveMDIWindowFilter::~C_ActiveMDIWindowFilter()
{
if( m_hWnd )
{
UnsubclassWindow();
}
if( m_pIActiveTextDocument )
{
m_pIActiveTextDocument->Release();
}
}
void C_ActiveMDIWindowFilter::SetCommandObject( CCommands* pCommands )
{
m_pCommands = pCommands;
}
void C_ActiveMDIWindowFilter::SetActive( CWnd* pActiveMDIWindow, IDispatch* pWindow, IDispatch* pIDocument )
{
if( m_hWnd )
{
UnsubclassWindow();
}
if( m_pIActiveTextDocument )
{
m_pIActiveTextDocument->Release();
m_pIActiveTextDocument = NULL;
}
if( pActiveMDIWindow && pIDocument )
{
pActiveMDIWindow = pActiveMDIWindow->GetDlgItem( 0xe900 ); //View-Frame
if( pActiveMDIWindow )
{
pActiveMDIWindow = pActiveMDIWindow->GetDlgItem( 0xe900 ); //eigentlicher View
}
if( pActiveMDIWindow )
{
if( SUCCEEDED( pIDocument->QueryInterface( IID_ITextDocument, (void**)&m_pIActiveTextDocument ) ) )
{
SubclassWindow( pActiveMDIWindow->GetSafeHwnd() );
}
}//endif pActiveMDIWindow
}
}
void C_ActiveMDIWindowFilter::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
OnSpecialKeyPressed( nChar );
CWnd::OnKeyDown( nChar,nRepCnt,nFlags );
}
void C_ActiveMDIWindowFilter::OnChar( UINT nChar, UINT nRepCnt, UINT nFlags )
{
OnSpecialKeyPressed( 0x0100 | nChar );
CWnd::OnChar( nChar,nRepCnt,nFlags );
}
void C_ActiveMDIWindowFilter::OnDummy()
{
}
void C_ActiveMDIWindowFilter::OnSpecialKeyPressed( UINT nKey )
{
IDispatch* pSelection;
CMenu mnuMenu;
UINT uID = 0;
CWnd* pWnd;
CString strNewText;
CPoint ptCaretPos;
try
{
if( m_pIActiveTextDocument && m_pCommands->m_bEnableSystem )
{
if( nKey==0x10 )
{
return;///SHIFT
}
switch( nKey )
{
case 0x0100 | ' ':
case 0x0100 | ',':
case 0x0100 | ';':
case 0x0100 | '(':
case VK_TAB:
case VK_RETURN:
if( SUCCEEDED( m_pIActiveTextDocument->get_Selection( &pSelection ) ) )
{
if( !m_bEnableAutoCorrect )
{
//Do we have a replacement text?
strNewText = CheckAutoText( pSelection, FALSE );
if( !strNewText.IsEmpty() )
{
//yes we have a new text
mnuMenu.LoadMenu( IDR_AUTO_CORRECT_CONTEXTMENU );
pWnd = this;
if( pWnd )
{
::GetCaretPos( &ptCaretPos );
ClientToScreen( &ptCaretPos );
ptCaretPos.x += 8; //etwas abstand halten
SetMenuDefaultItem( mnuMenu.GetSubMenu(0)->m_hMenu, ID_REPLACE_BY, FALSE );
mnuMenu.GetSubMenu(0)->ModifyMenu( ID_REPLACE_BY, MF_BYCOMMAND|MF_STRING|MF_ENABLED, ID_REPLACE_BY,
"Replace with: " + strNewText );
//Select first menu item trough faking a cursor down (keydown and up)
PostMessage( WM_KEYDOWN, VK_DOWN, 0 );
PostMessage( WM_KEYUP, VK_DOWN, 0 );
uID = TrackPopupMenu( mnuMenu.GetSubMenu(0)->m_hMenu, TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY,
ptCaretPos.x, ptCaretPos.y, NULL, pWnd->m_hWnd, NULL );
}
switch( uID )
{
case 0:
case ID_DONT_CORRECT_IT:
break;
case ID_REPLACE_BY:
m_bEnableAutoCorrect = TRUE;
break;
}
}//endif !strNewText.IsEmpty()
}//endif( !m_bEnableAutoCorrect )
if( m_bEnableAutoCorrect )
{
CheckAutoText( pSelection, TRUE );
}
//release selection object
pSelection->Release();
}
//In future do it again
m_bEnableAutoCorrect = TRUE;
break;
case VK_DELETE:
case VK_BACK:
//temporarily disable auto replacing
m_bEnableAutoCorrect = FALSE;
break;
case ID_EDIT_UNDO:
//temporarily disable auto replacing
m_bEnableAutoCorrect = FALSE;
break;
default:
break;
}
}//endif m_pIDispTextWindow
}
catch( ... )
{
TRACE( "Unhandled exception in OnSpecialKeyPressed\n" );
}
}
void C_ActiveMDIWindowFilter::OnKeyLeftRightUpDown( UINT nMoveCmd )
{
switch( nMoveCmd )
{
case 0: //left
case 1: //right
case 2: //up
case 3: //down
//after cursor moves allow auto correction again
m_bEnableAutoCorrect = TRUE;
break;
}
}
//If bAutoReplace is TRUE the text will immediately be replaced if possible.
//Otherwise only the replacement text (if available) will be returned.
//If there is nothing replaceable type by the user the return value will
//be empty and the text won't be changed.
CString C_ActiveMDIWindowFilter::CheckAutoText( IDispatch* pIDispSelection, BOOL bAutoReplace )
{
ITextSelection* pSelection;
long lFirstLine;
long lStartColumn;
long lLastLine;
BSTR bstrText = NULL;
CString strNewText;
COleVariant varDsExtend( long(1), VT_I4 );
COleVariant varDsMove( long(0), VT_I4 );
COleVariant varEmpty( DISP_E_PARAMNOTFOUND, VT_ERROR );
CString strErg;
CString strText;
//get selection from text editor
if( SUCCEEDED( pIDispSelection->QueryInterface( IID_ITextSelection, (void**)&pSelection ) ) )
{
//remember position because we need to change it
pSelection->get_CurrentLine( &lFirstLine );
pSelection->get_BottomLine( &lLastLine );
pSelection->get_CurrentColumn( &lStartColumn );
pSelection->get_Text( &bstrText );
strText = bstrText;
SysFreeString( bstrText );
//only try to replace if selection start and end line are the same
//and the text is at least 2 chars long
if( lFirstLine == lLastLine &&
strText.GetLength() < 2 )
{
//select previous typed word
pSelection->WordLeft( varDsExtend, COleVariant( 1L, VT_I4 ) );
//and get the text of it
pSelection->get_Text( &bstrText );
//I prefer CStrings... remeber last typed word
m_pCommands->m_strLastTypedWord = bstrText;
SysFreeString( bstrText );
//try to find the word
m_pCommands->m_strmapAutoText.Lookup( m_pCommands->m_strLastTypedWord, strNewText );
//Better suggestion from file?
if( !strNewText.IsEmpty() )
{
strErg = strNewText;
//Yes! Now check if we only want to know or to really do it.
if( bAutoReplace )
{
//Replace last word
bstrText = strNewText.AllocSysString();
pSelection->put_Text( bstrText );
SysFreeString( bstrText );
//set cursor behind new text if it's longer
lStartColumn+= strNewText.GetLength() - m_pCommands->m_strLastTypedWord.GetLength();
}
}
//Back to the position we started
pSelection->MoveTo( lFirstLine, lStartColumn, varDsMove );
}
//cleanup
pSelection->Release();
}
return strErg;
}