448 lines
14 KiB
C++
448 lines
14 KiB
C++
/******************************************************************************
|
|
|
|
Source File: Project Node.CPP
|
|
|
|
This implements the CProjectNode class which alows individual nodes in the
|
|
tree view to control their behavior without the control itself having to know
|
|
what all that behavior is.
|
|
|
|
The original header file (from the prototype) said this class didn't need an
|
|
implementation file, but this no longer makes sense, so it's bite the bullet
|
|
time here at Pretty Penny Enterprises...
|
|
|
|
Copyright (c) 1997 by Microsoft Corporation. All Rights Resreved.
|
|
|
|
A Pretty Penny Enterprises Production
|
|
|
|
Change History:
|
|
02-20-1997 Bob_Kjelgaard#Prodigy.Net Created it
|
|
|
|
******************************************************************************/
|
|
|
|
#include "StdAfx.H"
|
|
#include "..\Resource.H"
|
|
#if defined(LONG_NAMES)
|
|
#include "Project Node.H"
|
|
#else
|
|
#include "ProjNode.H"
|
|
#endif
|
|
|
|
IMPLEMENT_SERIAL(CBasicNode, CObject, 0)
|
|
|
|
CBasicNode::CBasicNode() {
|
|
m_pcmcwEdit = NULL;
|
|
m_pcdOwner = NULL;
|
|
m_pctcOwner = NULL;
|
|
m_hti = NULL;
|
|
m_pcbnWorkspace = NULL;
|
|
}
|
|
|
|
CBasicNode::~CBasicNode() {
|
|
if (m_pcmcwEdit)
|
|
m_pcmcwEdit -> DestroyWindow();
|
|
}
|
|
|
|
// Name ourselves and children- default to just our name, no children
|
|
|
|
void CBasicNode::Fill(CTreeCtrl *pctcWhere, HTREEITEM htiParent) {
|
|
m_pctcOwner = pctcWhere;
|
|
m_hti = pctcWhere -> InsertItem(m_csName, htiParent);
|
|
pctcWhere -> SetItemData(m_hti, (DWORD) this);
|
|
}
|
|
|
|
// Display a context menu using the ID array, if it has any members
|
|
|
|
void CBasicNode::ContextMenu(CWnd *pcw, CPoint cp) {
|
|
|
|
if (!m_cwaMenuID.GetSize())
|
|
return;
|
|
|
|
CMenu cmThis;
|
|
|
|
if (!cmThis.CreatePopupMenu())
|
|
return;
|
|
|
|
for (int i = 0; i < m_cwaMenuID.GetSize(); i++) {
|
|
|
|
if (m_cwaMenuID[i]) {
|
|
CString csWork;
|
|
|
|
csWork.LoadString(m_cwaMenuID[i]);
|
|
cmThis.AppendMenu(MF_STRING | MF_ENABLED, m_cwaMenuID[i], csWork);
|
|
}
|
|
else
|
|
cmThis.AppendMenu(MF_SEPARATOR);
|
|
}
|
|
|
|
cmThis.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, cp.x, cp.y, pcw);
|
|
}
|
|
|
|
|
|
|
|
// This override is called if our label is edited, or we are otherwise
|
|
// renamed...
|
|
BOOL CBasicNode::Rename(LPCTSTR lpstrNewName) {
|
|
if (!lpstrNewName)
|
|
return FALSE;
|
|
|
|
if (lpstrNewName == m_csName)
|
|
return TRUE;
|
|
|
|
// We'll return TRUE, unless the rename produces an exception
|
|
try {
|
|
m_csName = lpstrNewName;
|
|
}
|
|
catch (CException *pce) {
|
|
pce -> ReportError();
|
|
pce -> Delete();
|
|
return FALSE;
|
|
}
|
|
|
|
WorkspaceChange();
|
|
return TRUE;
|
|
}
|
|
|
|
void CBasicNode::Edit() {
|
|
if (!m_pcmcwEdit)
|
|
m_pcmcwEdit = CreateEditor();
|
|
else
|
|
if (IsWindow(m_pcmcwEdit -> m_hWnd))
|
|
m_pcmcwEdit -> ActivateFrame();
|
|
}
|
|
|
|
/******************************************************************************\
|
|
|
|
CBasicNode::Serialize
|
|
|
|
Pretty simple- the names the only field we will be keeping...
|
|
|
|
******************************************************************************/
|
|
|
|
void CBasicNode::Serialize(CArchive& car) {
|
|
CObject::Serialize(car);
|
|
if (car.IsLoading())
|
|
car >> m_csName;
|
|
else
|
|
car << m_csName;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFixedNode implementation
|
|
|
|
******************************************************************************/
|
|
|
|
IMPLEMENT_DYNAMIC(CFixedNode, CBasicNode)
|
|
|
|
CFixedNode::CFixedNode(unsigned uidName, CSafeObArray& csoa,
|
|
CMultiDocTemplate *pcmdt, CRuntimeClass *pcrc) :
|
|
m_csoaDescendants(csoa) {
|
|
m_uidName = uidName;
|
|
m_pcmdt = pcmdt;
|
|
m_pcrc = pcrc;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFixedNode::Zap
|
|
|
|
This method is called when an underlying object is to be destroyed. It finds
|
|
a matching pointer in the array, and then deletes that entry.
|
|
|
|
******************************************************************************/
|
|
|
|
void CFixedNode::Zap(CBasicNode *pcbn) {
|
|
for (unsigned u = 0; u < m_csoaDescendants.GetSize(); u++)
|
|
if (pcbn == m_csoaDescendants[u]) {
|
|
HTREEITEM htiGone = pcbn -> Handle();
|
|
|
|
m_csoaDescendants.RemoveAt(u);
|
|
Changed();
|
|
m_pctcOwner -> DeleteItem(htiGone);
|
|
CString csWork;
|
|
|
|
csWork.Format(_TEXT(" (%d)"), m_csoaDescendants.GetSize());
|
|
m_csName.LoadString(m_uidName);
|
|
m_csName += csWork;
|
|
m_pctcOwner -> SetItemText(m_hti, m_csName);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFixedNode::Import
|
|
|
|
This member function will import one or more files of the given type if there
|
|
is a document template and dynamic constructor available. It uses the
|
|
template to customize the File Open dialog, and the constructor to build the
|
|
elements.
|
|
|
|
******************************************************************************/
|
|
|
|
void CFixedNode::Import() {
|
|
if (!m_pcmdt || !m_pcrc || !m_pcrc -> m_pfnCreateObject)
|
|
return;
|
|
|
|
CString csExtension, csFilter;
|
|
|
|
m_pcmdt -> GetDocString(csExtension, CDocTemplate::filterExt);
|
|
m_pcmdt -> GetDocString(csFilter, CDocTemplate::filterName);
|
|
csFilter += _T("|*") + csExtension + _T("||");
|
|
|
|
CFileDialog cfd(TRUE, csExtension, NULL,
|
|
OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT, csFilter,
|
|
m_pctcOwner);
|
|
|
|
if (cfd.DoModal() != IDOK)
|
|
return;
|
|
|
|
// Import all of the named files...
|
|
|
|
for (POSITION pos = cfd.GetStartPosition(); pos; ) {
|
|
|
|
// Create the underlying object using dynamic creation. Only a
|
|
// CProjectNode has the required function, here.
|
|
|
|
CProjectNode* pcpn = (CProjectNode *) m_pcrc -> CreateObject();
|
|
|
|
if (!pcpn || !pcpn -> IsKindOf(RUNTIME_CLASS(CProjectNode))) {
|
|
TRACE("Imported object not derived from CProjectNode");
|
|
delete pcpn;
|
|
continue;
|
|
}
|
|
|
|
pcpn -> SetFileName(cfd.GetNextPathName(pos));
|
|
pcpn -> Rename(pcpn -> FileTitle());
|
|
pcpn -> NoteOwner(*m_pcdOwner);
|
|
pcpn -> SetWorkspace(m_pcbnWorkspace);
|
|
m_csoaDescendants.Add(pcpn);
|
|
WorkspaceChange();
|
|
pcpn -> EditorInfo(m_pcmdt);
|
|
pcpn -> Fill(m_pctcOwner, m_hti);
|
|
}
|
|
|
|
// Now, update our own appearance (get the count right)
|
|
CString csWork;
|
|
|
|
csWork.Format(_TEXT(" (%d)"), m_csoaDescendants.GetSize());
|
|
m_csName.LoadString(m_uidName);
|
|
m_csName += csWork;
|
|
m_pctcOwner -> SetItemText(m_hti, m_csName);
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFixedNode::Fill
|
|
|
|
This is a generic fill- the node names itself, then fills its node using the
|
|
array of nodes given to it at init time.
|
|
|
|
******************************************************************************/
|
|
|
|
void CFixedNode::Fill(CTreeCtrl *pctc, HTREEITEM hti) {
|
|
CString csWork;
|
|
|
|
csWork.Format(_TEXT(" (%d)"), m_csoaDescendants.GetSize());
|
|
m_csName.LoadString(m_uidName);
|
|
m_csName += csWork;
|
|
CBasicNode::Fill(pctc, hti);
|
|
for (unsigned u = 0; u < m_csoaDescendants.GetSize(); u++)
|
|
((CBasicNode *) m_csoaDescendants[u]) -> Fill(pctc, m_hti);
|
|
if (m_pcmdt && m_pcrc && m_pcrc -> m_pfnCreateObject) {
|
|
// Insert the Import menu item
|
|
m_cwaMenuID.InsertAt(0, ID_Import);
|
|
m_cwaMenuID.InsertAt(1, (WORD) 0);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFileNode implementation
|
|
|
|
******************************************************************************/
|
|
|
|
IMPLEMENT_SERIAL(CFileNode, CBasicNode, 0);
|
|
|
|
CFileNode::CFileNode() {
|
|
m_cwaMenuID.Add(ID_RenameItem);
|
|
m_bEditPath = FALSE;
|
|
m_bCheckForValidity = TRUE;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFileNode::Rename
|
|
|
|
If there is no name currently, then see if the named file can be created.
|
|
The other case, means the file should already be on the disk, so it is a bit
|
|
trickier.
|
|
|
|
First, check to see if the name violates the current naming conventions. If
|
|
so, reject it. Then attempt to move the file. IF the name is OK and the
|
|
file is moved, set the new name in the item label. Always returns FALSE.
|
|
|
|
******************************************************************************/
|
|
|
|
BOOL CFileNode::Rename(LPCTSTR lpstrNew) {
|
|
CString csNew = lpstrNew;
|
|
|
|
if (!lpstrNew) { // This only happens if the label edit was canceled.
|
|
csNew.LoadString(IDS_FileName);
|
|
if (m_pctcOwner)
|
|
m_pctcOwner -> SetItemText(m_hti, csNew + ViewName());
|
|
WorkspaceChange();
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_csExtension.CompareNoCase(csNew.Right(m_csExtension.GetLength())))
|
|
csNew += m_csExtension;
|
|
if (m_csName.IsEmpty()) {
|
|
CFile cfTemp;
|
|
|
|
// This check needs to be optional since in some instances, we know
|
|
// the name is valid because the file is open, and we're just trying
|
|
// to collect the name.
|
|
|
|
if (!cfTemp.Open(csNew, CFile::modeCreate | CFile::modeNoTruncate |
|
|
CFile::modeWrite | CFile::shareDenyNone) && m_bCheckForValidity) {
|
|
CString csWork, csDisplay;
|
|
|
|
csWork.LoadString(IDS_InvalidFilename);
|
|
csDisplay.Format(csWork, (LPCTSTR) csNew);
|
|
AfxMessageBox(csDisplay);
|
|
return FALSE;
|
|
}
|
|
|
|
try {
|
|
m_csPath = cfTemp.GetFilePath();
|
|
m_csPath = m_csPath.Left(1 + m_csPath.ReverseFind(_T('\\')));
|
|
}
|
|
catch (CException *pce) {
|
|
pce -> ReportError();
|
|
pce -> Delete();
|
|
return FALSE;
|
|
}
|
|
|
|
// If the file type isn't registered, then GetFileTitle returns the
|
|
// extension, so strip it!
|
|
|
|
csNew = cfTemp.GetFileTitle();
|
|
if (!m_csExtension.CompareNoCase(csNew.Right(
|
|
m_csExtension.GetLength())))
|
|
csNew = csNew.Left(csNew.GetLength() - m_csExtension.GetLength());
|
|
|
|
return CBasicNode::Rename(csNew); // OK from this path
|
|
}
|
|
|
|
// Strip any path if it cannot be changed, and substitute the real one
|
|
|
|
if (!m_bEditPath)
|
|
csNew = m_csPath + csNew.Mid(1 + csNew.ReverseFind(_T('\\')));
|
|
|
|
try {
|
|
LPSTR lpstr;
|
|
|
|
CFile::Rename(FullName(), csNew);
|
|
|
|
GetFullPathName(csNew, MAX_PATH, csNew.GetBuffer(MAX_PATH), &lpstr);
|
|
csNew.ReleaseBuffer();
|
|
m_csPath = csNew.Left(1 + csNew.ReverseFind(_T('\\')));
|
|
csNew = csNew.Mid(m_csPath.GetLength());
|
|
m_csName = csNew.Left(csNew.GetLength() -
|
|
m_csExtension.GetLength());
|
|
csNew.LoadString(IDS_FileName);
|
|
if (m_pctcOwner)
|
|
m_pctcOwner -> SetItemText(m_hti, csNew + m_csName);
|
|
WorkspaceChange();
|
|
return FALSE; // Force the change (above) to be kept.
|
|
}
|
|
catch (CFileException *pcfe) { // Don't get a file name with statics
|
|
if (pcfe -> m_cause == ERROR_FILE_NOT_FOUND)
|
|
csNew = FullName();
|
|
pcfe -> m_strFileName = csNew;
|
|
pcfe -> ReportError();
|
|
pcfe -> Delete();
|
|
return FALSE;
|
|
}
|
|
catch (CException *pce) {
|
|
pce -> ReportError();
|
|
pce -> Delete();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFileNode::CanEdit
|
|
|
|
This will return TRUE, but it will first have to remove the File Name: stuff
|
|
from the label, so we can get a cleaner edit.
|
|
|
|
******************************************************************************/
|
|
|
|
BOOL CFileNode::CanEdit() const {
|
|
|
|
CEdit* pce = m_pctcOwner -> GetEditControl();
|
|
if (pce)
|
|
pce -> SetWindowText(m_bEditPath ? m_csPath + m_csName : m_csName);
|
|
return !!pce;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFileNode::Fill
|
|
|
|
We play a bit of a game here, changing our name temporarily to use the base
|
|
class implementation.
|
|
|
|
******************************************************************************/
|
|
|
|
void CFileNode::Fill(CTreeCtrl* pctc, HTREEITEM htiParent) {
|
|
CString csTemp = Name();
|
|
|
|
m_csName.LoadString(IDS_FileName);
|
|
m_csName += csTemp;
|
|
CBasicNode::Fill(pctc, htiParent);
|
|
m_csName = csTemp;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CFileNode::Serialize
|
|
|
|
Since the name is civered by the base class, we only need to serialize the
|
|
boolean controlling long/short file names.
|
|
|
|
******************************************************************************/
|
|
|
|
void CFileNode::Serialize(CArchive& car) {
|
|
CBasicNode::Serialize(car);
|
|
if (car.IsLoading())
|
|
car >> m_csPath;
|
|
else
|
|
car << m_csPath;
|
|
}
|
|
|
|
/******************************************************************************
|
|
|
|
CProjectNode implementation
|
|
|
|
******************************************************************************/
|
|
|
|
IMPLEMENT_SERIAL(CProjectNode, CBasicNode, 0)
|
|
|
|
CProjectNode::CProjectNode() {
|
|
m_pcmdt = NULL;
|
|
}
|
|
|
|
void CProjectNode::Fill(CTreeCtrl *pctc, HTREEITEM hti) {
|
|
CBasicNode::Fill(pctc, hti);
|
|
m_cfn.SetWorkspace(m_pcbnWorkspace);
|
|
m_cfn.Fill(pctc, m_hti);
|
|
}
|
|
|
|
void CProjectNode::Serialize(CArchive& car) {
|
|
CBasicNode::Serialize(car);
|
|
m_cfn.Serialize(car);
|
|
} |