// Created by Larry Leonard, Definitive Solutions, Inc. #include "stdafx.h" #include "MyDevStudio.h" #include "Generic.h" #include "MyLog.h" // Use the "Add Class..., From a Type Library" button in ClassWizard to // create this header and corresponding CPP file from DEVSHL.DLL. #include "DevShl.h" // Use the "Add Class..., From a Type Library" button in ClassWizard to // create this header and corresponding CPP file from DEVEDIT.PKG. #include "DevEdit.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // MyDevStudio IMPLEMENT_DYNCREATE(MyDevStudio, CCmdTarget) // Constructor. MyDevStudio::MyDevStudio() : m_pIApp(NULL) , m_pIDocs(NULL) { } // Destructor. /* virtual */ MyDevStudio::~MyDevStudio() { ASSERT_NULL_OR_POINTER(m_pIApp, IApplication); ASSERT_NULL_OR_POINTER(m_pIDocs, IDocuments); Cleanup(); } BEGIN_MESSAGE_MAP(MyDevStudio, CCmdTarget) //{{AFX_MSG_MAP(MyDevStudio) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyDevStudio operations // Is the Microsoft Developer Studio installed on this machine? If so, // returns TRUE and populates the sent class id. bool MyDevStudio::IsDeveloperStudioInstalled(CLSID& idMsDev) const { ASSERT_NULL_OR_POINTER(m_pIApp, IApplication); ASSERT_NULL_OR_POINTER(m_pIDocs, IDocuments); USES_CONVERSION; CWaitCursor wc; return SUCCEEDED(::CLSIDFromProgID(T2COLE("MSDEV.APPLICATION"), &idMsDev)); } // Is there a currently running instance of the Developer Studio? bool MyDevStudio::IsRunningInstance(IUnknown** ppUnkSent /* = NULL */ ) const { ASSERT_NULL_OR_POINTER(m_pIApp, IApplication); ASSERT_NULL_OR_POINTER(m_pIDocs, IDocuments); ASSERT_NULL_OR_POINTER(ppUnkSent, IUnknown*); USES_CONVERSION; CWaitCursor wc; // Find the CLSID from the Registry. IUnknown* pUnk = NULL; CLSID idMsDev; if (IsDeveloperStudioInstalled(idMsDev)) { // See if the MSDV application is currently running. ::GetActiveObject(idMsDev, NULL, &pUnk); } else { // You should have called IsDeveloperStudioInstalled() to avoid getting. AfxMessageBox("Microsoft Developer Studio is not installed on this machine!", MB_ICONSTOP); } // Populate pointer if sent. if (ppUnkSent) { *ppUnkSent = pUnk; } return NULL != pUnk; } // Get the "Application" object for the currently running instantiation of // the Developer Studio. bool MyDevStudio::ConnectToInstance() { _ASSERTE(! m_pIApp && "Don't call this if you're already connected!"); _ASSERTE(! m_pIDocs && "Don't call this if you're already connected!"); USES_CONVERSION; CWaitCursor wc; bool bReturn(false); // Better not already be connected, obviously. if (! IsConnectedToInstance()) { CLSID idMsDev; if (IsDeveloperStudioInstalled(idMsDev)) { IUnknown* pUnk = NULL; if (IsRunningInstance(&pUnk)) { ASSERT_POINTER(pUnk, IUnknown); // Get the Application object. IDispatch* pDispApplication = NULL; HRESULT hr(pUnk->QueryInterface(IID_IDispatch, (void**) &pDispApplication)); pUnk->Release(); if (SUCCEEDED(hr)) { m_pIApp = new IApplication(pDispApplication); // Now get the Documents interface; we need it to be able to // open files. IDispatch* pDispDocuments = m_pIApp->GetDocuments(); m_pIDocs = new IDocuments(pDispDocuments); if (m_pIDocs) { // Populate the "Project"s list, and tell each Project to // load itself from its DSP file. if (LoadProjects()) { bReturn = true; } else { AfxMessageBox("Failed to load the Project object list " "via the Microsoft Developer Studio.", MB_ICONSTOP); Cleanup(); } } else { AfxMessageBox("Failed to create the IDocuments interface.", MB_ICONSTOP); Cleanup(); } } else { AfxMessageBox("Failed to get dispatch interface from IUnknown.", MB_ICONSTOP); Cleanup(); } } else { // You should have called IsRunningInstance() to avoid getting this. AfxMessageBox("There is no running instance of the Microsoft " "Developer Studio on this machine.", MB_ICONSTOP); } } else { // You should have called IsDeveloperStudioInstalled() to avoid this. AfxMessageBox("Microsoft Developer Studio is not installed on this machine.", MB_ICONSTOP); } } else { // You should have called IsConnectedToInstance()() to avoid this. But // we return TRUE since we are after all connected. bReturn = true; AfxMessageBox("Already connected to running instance of Microsoft Developer Studio!", MB_ICONSTOP); } return bReturn; } // Are we currently connected to a running instance of Developer Studio? // And is the currently running instance still running? bool MyDevStudio::IsConnectedToInstance() { ASSERT_NULL_OR_POINTER(m_pIApp, IApplication); ASSERT_NULL_OR_POINTER(m_pIDocs, IDocuments); bool bReturn(false); try { // See if we can still talk to the connection; if not 'bReturn' never // gets set to TRUE because an exception is thrown. if (m_pIApp) { m_pIApp->GetCurrentDirectory(); bReturn = true; } } catch(CException* e) { AfxMessageBox("The connection to the instance of Developer Studio was reset, possibly because it was shut down.", MB_ICONSTOP); Cleanup(); e->Delete(); } return bReturn; } // Make the Developer Studio active. void MyDevStudio::Activate() const { ASSERT_POINTER(m_pIApp, IApplication); ASSERT_POINTER(m_pIDocs, IDocuments); // "Restore" it, then activate it. if (m_pIApp) { // 1 = Maximize // 2 = Minimize // 3 = Restore // 4 = Won't restore if minimized m_pIApp->SetWindowState(1); m_pIApp->SetActive(true); } else { // You should have called IsConnectedToInstance() to avoid this. AfxMessageBox("Not currently connected to a running Developer Studio instance!", MB_ICONSTOP); } } // Open the sent file name, which is not a full path, just a "8.3" ("255.3", // whatever) file name only. bool MyDevStudio::OpenFile(const CString& sFileName) { ASSERT_POINTER(m_pIApp, IApplication); ASSERT_POINTER(m_pIDocs, IDocuments); _ASSERTE(! sFileName.IsEmpty()); CWaitCursor wc; bool bReturn(false); if (IsConnectedToInstance()) { // Iterate the projects until we find the one that contains the requested // file. We take the first one we get, as we have no choice. POSITION pos(m_olProjects.GetHeadPosition()); while (pos) { DSProject* pProject = static_cast (m_olProjects.GetNext(pos)); ASSERT_VALID(pProject); CString sFilePath; if (pProject->PathForFile(sFileName, sFilePath)) { // Now that we have a full pathname, we can open the file. VARIANT varType; V_VT(&varType) = VT_EMPTY; VARIANT varReadOnly; V_VT(&varReadOnly) = VT_BOOL; V_BOOL(&varReadOnly) = 0; try { m_pIDocs->Open(sFilePath, varType, varReadOnly); bReturn = true; } catch(CException* e) { e->Delete(); } break; } } } else { // You should have called IsConnectedToInstance() to avoid this. AfxMessageBox("Not currently connected to a running Developer Studio instance!", MB_ICONSTOP); } return bReturn; } // Populate the "Project"s list, and tell each Project to load itself from // its DSP file. This should not be called by the client programmer. bool MyDevStudio::LoadProjects() { ASSERT_POINTER(m_pIApp, IApplication); ASSERT_POINTER(m_pIDocs, IDocuments); CWaitCursor wc; bool bReturn(true); // Open the DSW file; we'll be using it to get the Projects' DSP paths // from their names. Unfortunately, there is no way to get the name of // the currently open DSW file from the DevStudio object model; I am not // making this up. So, we just assume that the currently attached // directory has only one DSW file in it, and that it's the currently open // one. The problem is that often the currently attached dir is not the // dir with the DSW in it. If anyone has a better idea, let me know. WIN32_FIND_DATA wfd; ::ZeroMemory(&wfd, sizeof(wfd)); CString sFileMask(m_pIApp->GetCurrentDirectory() + "\\*.dsw"); HANDLE hFind(::FindFirstFile(sFileMask, &wfd)); if (INVALID_HANDLE_VALUE != hFind && ! (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { m_sDswPath = m_pIApp->GetCurrentDirectory() + "\\" + wfd.cFileName; CStdioFile fileDsw; if (fileDsw.Open(m_sDswPath, CFile::modeRead | CFile::shareDenyWrite)) { // Get the Projects interface. IDispatch* pDispProjects = m_pIApp->GetProjects(); ASSERT_POINTER(pDispProjects, IDispatch); IProjects* pProjects = new IProjects(pDispProjects); ASSERT_POINTER(pProjects, IProjects); // Get the number of "Project"s. int nProjects(pProjects->GetCount()); if (0 < nProjects) { // Get each Project from the Projects object. for (int nProject = 1; nProject <= nProjects; ++nProject) { // Get the Project interface. VARIANT varProject; V_VT(&varProject) = VT_INT; V_INT(&varProject) = nProject; IDispatch* pDispProject = pProjects->Item(varProject); IGenericProject* pProject = new IGenericProject(pDispProject); // Get the Project's name, and create a new DSProject from it. DSProject* pDSProject = new DSProject; ASSERT_VALID(pDSProject); if (pDSProject->Initialize(pProject->GetName(), fileDsw)) { m_olProjects.AddTail(pDSProject); } else { delete pDSProject; pDSProject = NULL; AfxMessageBox("Failed to initialize Project: " + pProject->GetName(), MB_ICONSTOP); bReturn = false; delete pProject; pProject = NULL; break; } // Cleanup. delete pProject; pProject = NULL; } } else { AfxMessageBox("Failed to find any Projects in the Microsoft " "Developer Studio Workspace! Be sure that a Workspace is " "loaded.", MB_ICONSTOP); bReturn = false; } // Cleanup. delete pProjects; pProjects = NULL; fileDsw.Close(); } else { AfxMessageBox("Failed to Open the DSW file: " + m_sDswPath, MB_ICONSTOP); bReturn = false; } } else { AfxMessageBox("Failed to locate a DSW file using mask <" + sFileMask + ">! Make sure that the Developer Studio is attached to the " "directory that contains the DSW file (use the 'File, Open' " "command to verify what the current directory is).", MB_ICONSTOP); bReturn = false; } return bReturn; } // Move the cursor to the sent line in the currently open file. bool MyDevStudio::GoToLine(int nLine) { ASSERT_POINTER(m_pIApp, IApplication); ASSERT_POINTER(m_pIDocs, IDocuments); _ASSERTE(0 <= nLine); CWaitCursor wc; bool bReturn(false); // Get the currently active TextDocument object. IDispatch* pDispTextDocument = m_pIApp->GetActiveDocument(); ASSERT_POINTER(pDispTextDocument, IDispatch); if (pDispTextDocument) { // Get the TextSelection dispatch interface. I'm doing this by hand // because the helper invoke methods have the wrong dispid (at least for // MSDEV97). IDispatch* pDispSelection = NULL; DISPID dispidSelection = NULL; BSTR bsMethod = OLESTR("Selection"); HRESULT hr(pDispTextDocument->GetIDsOfNames(IID_NULL, &bsMethod, 1, LOCALE_SYSTEM_DEFAULT, &dispidSelection)); if (SUCCEEDED(hr)) { DISPPARAMS dispArg = { NULL, NULL, 0, 0 }; VARIANT varResult; V_VT(&varResult) = VT_DISPATCH; // Invoke the GetSelection method. hr = pDispTextDocument->Invoke(dispidSelection, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispArg, &varResult, NULL, NULL); if (SUCCEEDED(hr)) { pDispSelection = V_DISPATCH(&varResult); ASSERT_POINTER(pDispSelection, IDispatch); if (pDispSelection) { // Get the TextSelection object. ITextSelection* pSelection = new ITextSelection(pDispSelection); ASSERT_POINTER(pSelection, ITextSelection); // Now go to that line of code. VARIANT varSelect; V_VT(&varSelect) = VT_BOOL; V_BOOL(&varSelect) = 0; pSelection->GoToLine(nLine, varSelect); m_pIApp->SetActive(true); bReturn = true; // Cleanup. delete pSelection; pSelection = NULL; } else { AfxMessageBox("Error Invoking Selection from ITextDocument!", MB_ICONSTOP); Cleanup(); } } else { AfxMessageBox("Error Invoking Selection from ITextDocument!", MB_ICONSTOP); Cleanup(); } } else { AfxMessageBox("Error getting Selection from ITextDocument!", MB_ICONSTOP); Cleanup(); } } else { AfxMessageBox("Error getting Active Document from IApplication!", MB_ICONSTOP); Cleanup(); } return bReturn; } // Called by the dtor and whenever we lose connection. void MyDevStudio::Cleanup() { // Cleanup the interfaces. delete m_pIDocs; m_pIDocs = NULL; delete m_pIApp; m_pIApp = NULL; // Delete all the DSProjects. POSITION pos(m_olProjects.GetHeadPosition()); while (pos) { delete m_olProjects.GetNext(pos); } } ///////////////////////////////////////////////////////////////////////////// // DSProject IMPLEMENT_DYNCREATE(DSProject, CCmdTarget) // Constructor. DSProject::DSProject() { } // Destructor. /* virtual */ DSProject::~DSProject() { } BEGIN_MESSAGE_MAP(DSProject, CCmdTarget) //{{AFX_MSG_MAP(DSProject) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // DSProject implementation. // Load this object from it's DSP file. We get the name of the DSP file // from the DSW file, knowing the name of the 'Project'. bool DSProject::Initialize(const CString& sProjectName, CStdioFile& fileDsw) { _ASSERTE(! sProjectName.IsEmpty()); ASSERT_VALID(&fileDsw); DOMYLOG ("Initializing DSProject: %s\n", sProjectName); CWaitCursor wc; bool bReturn(false); m_sDspPath.Empty(); // The format for the Project line in the DSW file is this: // Project: "AprCalc"=.\AprCalc.dsp - Package Owner=<4> CString sTarget; sTarget.Format("Project: \"%s\"=", sProjectName); // Read lines from DSW file until we find the line that contains the sent // project name. CString sBuf; int nIndex(-1); fileDsw.SeekToBegin(); while (fileDsw.ReadString(sBuf)) { // Is this the project line? If so, build the pathname. if (-1 != (nIndex = sBuf.Find(sTarget))) { m_sDspPath = sBuf.Mid(sTarget.GetLength()); if (-1 != (nIndex = m_sDspPath.Find(".dsp"))) { m_sDspPath = m_sDspPath.Left(nIndex + 4); bReturn = true; } else { AfxMessageBox("Error parsing DSW - missing '.dsp'!", MB_ICONSTOP); } break; } } // Now that we know the path of the DSP (relative to the DSW file), we // open the DSP file and read in all the filenames it contains. if (bReturn) { _ASSERTE(! m_sDspPath.IsEmpty() && "It should be populated by here!"); // First we have to build the DSP path from the path relative to the DSW. // We make the bad assumption here that all file are in this dir, and // so the first char is a '.'. CString sDswPath(fileDsw.GetFilePath()); CString sDrive; CString sDir; CString sFName; CString sExt; _splitpath(sDswPath, sDrive.GetBuffer(MAX_PATH), sDir.GetBuffer(MAX_PATH), sFName.GetBuffer(MAX_PATH), sExt.GetBuffer(MAX_PATH)); sDrive.ReleaseBuffer(); sDir.ReleaseBuffer(); sFName.ReleaseBuffer(); sExt.ReleaseBuffer(); m_sDspPath = sDrive + sDir + m_sDspPath.Mid(2); // Now we can initialize this object from its DSP file. bReturn = ParseDspFile(); } else { AfxMessageBox("Error parsing DSW - didn't find entry for project: " + sProjectName, MB_ICONSTOP); } return bReturn; } // Load all the filenames from the DSP file. This is a separate function to // allow for future expansion of this class. bool DSProject::ParseDspFile() { _ASSERTE(! m_sDspPath.IsEmpty()); _ASSERTE(0 == m_slFiles.GetCount()); CWaitCursor wc; bool bReturn(true); CStdioFile fileDsp; CString sBuf; CString sFileName; if (fileDsp.Open(m_sDspPath, CFile::modeRead | CFile::shareDenyWrite)) { while (fileDsp.ReadString(sBuf)) { // Source file lines in the DSP have this format: // SOURCE=.\ApplyPhase.cpp CString sTarget("SOURCE=.\\"); // Is this a source line? If so, add it to this projects list. // Note that we convert it to all lowercase, to avoid case-confusion. if (-1 != sBuf.Find(sTarget)) { CString sFileName(sBuf.Mid(sTarget.GetLength())); sFileName.MakeLower(); m_slFiles.AddTail(sFileName); //DOMYLOG ("Added file %s\n", sFileName); } } } else { AfxMessageBox("Failed to open the DSP file: " + m_sDspPath, MB_ICONSTOP); bReturn = false; } return bReturn; } // Given a 255.3 file name (not a full path name), if this Project contains // that file, populate the returned CString with the full path name, and // return TRUE. Otherwise, empty the CString and return FALSE. Note that // we convert the sent file to lowercase, to avoid case-confusion. bool DSProject::PathForFile(const CString& sFileName, CString& sFilePath) const { _ASSERTE(! m_sDspPath.IsEmpty()); _ASSERTE(! sFileName.IsEmpty()); bool bReturn(false); CString sFileNameLower(sFileName); sFileNameLower.MakeLower(); sFilePath.Empty(); POSITION pos(m_slFiles.Find(sFileNameLower)); if (pos) { // First we have to build the file's path from the path relative to the // DSP. We make the bad assumption here that all file are in this dir, // and so the first char is a '.'. CString sDrive; CString sDir; CString sFName; CString sExt; _splitpath(m_sDspPath, sDrive.GetBuffer(MAX_PATH), sDir.GetBuffer(MAX_PATH), sFName.GetBuffer(MAX_PATH), sExt.GetBuffer(MAX_PATH)); sDrive.ReleaseBuffer(); sDir.ReleaseBuffer(); sFName.ReleaseBuffer(); sExt.ReleaseBuffer(); sFilePath = sDrive + sDir + sFileName; bReturn = true; } return bReturn; }