khanat-opennel-code/code/nel/tools/3d/object_viewer/particle_dlg.cpp

756 lines
24 KiB
C++
Raw Normal View History

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// particle_dlg.cpp : implementation file
//
#include "std_afx.h"
#include "object_viewer.h"
#include "particle_dlg.h"
#include "editable_range.h"
#include "located_properties.h"
#include "particle_system_edit.h"
#include "skippable_message_box.h"
#include "main_frame.h"
//
// TODO : remove these include when the test system will be removed
#include "nel/3d/particle_system.h"
#include "nel/3d/ps_force.h"
#include "nel/3d/ps_emitter.h"
#include "nel/3d/ps_particle.h"
#include "nel/3d/ps_util.h"
#include "nel/3d/ps_zone.h"
#include "nel/3d/ps_color.h"
#include "nel/3d/ps_float.h"
#include "nel/3d/ps_int.h"
#include "nel/3d/ps_plane_basis_maker.h"
#include "nel/3d/particle_system_model.h"
#include "nel/3d/particle_system_shape.h"
#include "nel/3d/texture_file.h"
#include "nel/3d/texture_grouped.h"
#include "nel/3d/nelu.h"
#include "nel/3d/font_manager.h"
//
#include "nel/misc/file.h"
#include "start_stop_particle_system.h"
//
#include "save_options_dlg.h"
#include "create_file_dlg.h"
using namespace NL3D;
//**************************************************************************************************************************
CParticleDlg::CParticleDlg(class CObjectViewer* main, CWnd *pParent, CMainFrame* mainFrame, CAnimationDlg *animDLG)
: CDialog(CParticleDlg::IDD, pParent),
MainFrame(mainFrame),
CurrentRightPane(NULL),
_ActiveNode(NULL),
_ObjView(main),
_EmptyBBox(true),
_AutoUpdateBBox(false),
_PW(NULL)
{
//{{AFX_DATA_INIT(CParticleDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
nlverify (FontManager = main->getFontManager());
nlverify (FontGenerator = main->getFontGenerator());
NL3D::CParticleSystem::setSerializeIdentifierFlag(true); // serialize identifiers for edition
ParticleTreeCtrl = new CParticleTreeCtrl(this);
StartStopDlg = new CStartStopParticleSystem(this, animDLG);
/** register us, so that our 'go' method will be called
* this gives us a chance to display a bbox when needed
*/
_ObjView->registerMainLoopCallBack(this);
}
//**************************************************************************************************************************
BOOL CParticleDlg::Create( UINT nIDTemplate, CWnd* pParentWnd /*= NULL*/ )
{
if (!CDialog::Create(nIDTemplate, pParentWnd)) return FALSE;
return TRUE;
}
//**************************************************************************************************************************
void CParticleDlg::moveElement(const NLMISC::CMatrix &mat)
{
ParticleTreeCtrl->moveElement(mat);
}
//**************************************************************************************************************************
NLMISC::CMatrix CParticleDlg::getElementMatrix(void) const
{
return ParticleTreeCtrl->getElementMatrix();
}
//**************************************************************************************************************************
CParticleDlg::~CParticleDlg()
{
_ObjView->removeMainLoopCallBack(this);
delete ParticleTreeCtrl;
delete CurrentRightPane;
delete StartStopDlg;
if (_PW) _PW->setModificationCallback(NULL);
delete _PW;
}
//**************************************************************************************************************************
void CParticleDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CParticleDlg)
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CParticleDlg, CDialog)
//{{AFX_MSG_MAP(CParticleDlg)
ON_WM_DESTROY()
ON_WM_SIZE()
ON_WM_SHOWWINDOW()
ON_WM_CHAR()
ON_COMMAND(IDM_CREATE_NEW_PS_WORKSPACE, OnCreateNewPsWorkspace)
ON_COMMAND(IDM_LOAD_PS_WORKSPACE, OnLoadPSWorkspace)
ON_COMMAND(IDM_SAVE_ALL_PS_WORKSPACE, OnSaveAllPsWorkspace)
ON_COMMAND(IDM_SAVE_PS_WORKSPACE, OnSavePsWorkspace)
ON_COMMAND(IDM_VIEW_PS_FILENAME, OnViewPsFilename)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//**************************************************************************************************************************
void CParticleDlg::OnDestroy()
{
checkModifiedWorkSpace();
if (CurrentRightPane)
{
CurrentRightPane->DestroyWindow();
delete CurrentRightPane;
CurrentRightPane = NULL;
}
setRegisterWindowState (this, REGKEY_OBJ_PARTICLE_DLG);
CDialog::OnDestroy();
}
//**************************************************************************************************************************
BOOL CParticleDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect r;
GetWindowRect(&r);
ParticleTreeCtrl->Create(WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_BORDER
| TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES | TVS_SHOWSELALWAYS | TVS_EDITLABELS
| TVS_DISABLEDRAGDROP , r, this, 0x1005);
ParticleTreeCtrl->init();
ParticleTreeCtrl->ShowWindow(SW_SHOW);
StartStopDlg->Create(IDD_PARTICLE_SYSTEM_START_STOP, this);
// create menu bar that allow to create / load a particle workspace
CMenu menu;
menu.LoadMenu(MAKEINTRESOURCE(IDR_PARTICLE_DLG_MENU));
this->SetMenu(&menu);
menu.Detach();
updateMenu();
//
_StatusBar.Create(this);
UINT indicators = ID_PS_EDITOR_STATUS;
_StatusBar.SetIndicators(&indicators, 1);
_StatusBar.SetPaneInfo(0, ID_PS_EDITOR_STATUS, SBPS_NORMAL, computeStatusBarWidth());
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, ID_PS_EDITOR_STATUS);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
//**************************************************************************************************************************
void CParticleDlg::setStatusBarText(CString &str)
{
_StatusBar.SetPaneText(0, str, TRUE);
}
//**************************************************************************************************************************
void CParticleDlg::OnSize(UINT nType, int cx, int cy)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
bool blocked = false;
if (ParticleTreeCtrl->m_hWnd && this->m_hWnd)
{
CRect r = getTreeRect(cx, cy);
ParticleTreeCtrl->MoveWindow(&r);
if (CurrentRightPane)
{
CurrentRightPane->MoveWindow(r.right + 10, r.top, r.right + CurrRightPaneWidth + 10, r.top + CurrRightPaneHeight);
}
CDialog::OnSize(nType, cx, cy);
if (IsWindow(_StatusBar))
{
_StatusBar.SetPaneInfo(0, ID_PS_EDITOR_STATUS, SBPS_NORMAL, computeStatusBarWidth());
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, ID_PS_EDITOR_STATUS);
}
}
}
//**************************************************************************************************************************
CRect CParticleDlg::getTreeRect(int cx, int cy) const
{
const uint ox = 0, oy = 10;
if (CurrentRightPane)
{
CRect res(ox, oy, cx - CurrRightPaneWidth - 10, cy - 20);
return res;
}
else
{
CRect res(ox, oy, cx - 10, cy - 20);
return res;
}
}
//**************************************************************************************************************************
void CParticleDlg::setRightPane(CWnd *pane)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
if (CurrentRightPane)
{
CurrentRightPane->DestroyWindow();
}
delete CurrentRightPane;
CurrentRightPane = pane;
RECT r;
if (pane)
{
pane->ShowWindow(SW_SHOW);
CurrentRightPane->GetClientRect(&r);
CurrRightPaneWidth = r.right;
CurrRightPaneHeight = r.bottom;
}
GetClientRect(&r);
this->SendMessage(WM_SIZE, SIZE_RESTORED, r.right + (r.bottom << 16));
GetWindowRect(&r);
this->MoveWindow(&r);
if (CurrentRightPane)
{
CurrentRightPane->Invalidate();
}
this->Invalidate();
ParticleTreeCtrl->Invalidate();
}
//**************************************************************************************************************************
LRESULT CParticleDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_GETMINMAXINFO)
{
sint cx = 150, cy = 150;
if (CurrentRightPane)
{
RECT r;
CurrentRightPane->GetClientRect(&r);
cx += CurrRightPaneWidth;
if (cy < (CurrRightPaneHeight + 20) ) cy = CurrRightPaneHeight + 20;
}
MINMAXINFO *inf = (MINMAXINFO *) lParam;
inf->ptMinTrackSize.x = cx;
inf->ptMinTrackSize.y = cy;
}
return CDialog::WindowProc(message, wParam, lParam);
}
//**************************************************************************************************************************
void CParticleDlg::OnShowWindow(BOOL bShow, UINT nStatus)
{
CDialog::OnShowWindow(bShow, nStatus);
StartStopDlg->ShowWindow(bShow);
}
//**************************************************************************************************************************
void CParticleDlg::goPostRender()
{
NL3D::CParticleSystem *currPS = _ActiveNode ? _ActiveNode->getPSPointer() : NULL;
if (!currPS) return;
if (StartStopDlg->isBBoxDisplayEnabled() && currPS)
{
NL3D::CNELU::Driver->setupModelMatrix(currPS->getSysMat());
if (_AutoUpdateBBox)
{
NLMISC::CAABBox currBBox;
currPS->forceComputeBBox(currBBox);
if (_EmptyBBox)
{
_EmptyBBox = false;
_CurrBBox = currBBox;
}
else
{
NL3D::CPSUtil::displayBBox(NL3D::CNELU::Driver, _CurrBBox, CRGBA::Blue);
_CurrBBox = NLMISC::CAABBox::computeAABBoxUnion(currBBox, _CurrBBox);
}
currPS->setPrecomputedBBox(_CurrBBox);
}
else
{
currPS->getLastComputedBBox(_CurrBBox);
}
NL3D::CPSUtil::displayBBox(NL3D::CNELU::Driver, _CurrBBox, currPS->getAutoComputeBBox() ? CRGBA::White : CRGBA::Red);
}
// copy user matrix into current fx
nlassert(_ObjView);
_ActiveNode->getPSModel()->setUserMatrix(_ObjView->getFXUserMatrix());
}
//**************************************************************************************************************************
void CParticleDlg::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == (UINT) 'p' || nChar == (UINT) 'P' || nChar == (UINT) ' ')
{
// simulate a start / stop on the system
StartStopDlg->toggle();
}
CDialog::OnChar(nChar, nRepCnt, nFlags);
}
//**************************************************************************************************************************
const NLMISC::CMatrix &CParticleDlg::getPSMatrix() const
{
if (!getActivePSM()) return NLMISC::CMatrix::Identity;
return getActivePSM()->getMatrix();
}
//**************************************************************************************************************************
const NLMISC::CMatrix &CParticleDlg::getPSWorldMatrix() const
{
if (!getActivePSM()) return NLMISC::CMatrix::Identity;
return getActivePSM()->getWorldMatrix();
}
//**************************************************************************************************************************
void CParticleDlg::setPSMatrix(const NLMISC::CMatrix &mat)
{
if (!getActivePSM()) return;
getActivePSM()->setMatrix(mat);
}
//**************************************************************************************************************************
void CParticleDlg::setPSWorldMatrix(const NLMISC::CMatrix &mat)
{
if (!getActivePSM()) return;
CMatrix invParentMat = getActivePSM()->getMatrix() * getActivePSM()->getWorldMatrix().inverted();
invParentMat.normalize(CMatrix::XYZ);
CMatrix newMat = invParentMat * mat;
newMat.normalize(CMatrix::XYZ);
getActivePSM()->setMatrix(newMat);
}
//**************************************************************************************************************************
void CParticleDlg::stickPSToSkeleton(CParticleWorkspace::CNode *node,
NL3D::CSkeletonModel *skel,
uint bone,
const std::string &parentSkelName,
const std::string &parentBoneName)
{
if (!node) return;
node->stickPSToSkeleton(skel, bone, parentSkelName, parentBoneName);
if (skel)
{
if (_ObjView->getMainFrame()->MouseMoveType == CMainFrame::MoveFX)
{
_ObjView->getMainFrame()->OnEditMovecamera();
}
_ObjView->getMainFrame()->ToolBar.Invalidate();
}
}
//**************************************************************************************************************************
void CParticleDlg::unstickPSFromSkeleton(CParticleWorkspace::CNode *node)
{
if (!node) return;
node->unstickPSFromSkeleton();
_ObjView->getMainFrame()->ToolBar.Invalidate();
}
//**************************************************************************************************************************
bool CParticleDlg::savePS(HWND parent, CParticleWorkspace::CNode &psNode, bool askToContinueWhenError)
{
return savePSAs(parent, psNode, psNode.getFullPath(), askToContinueWhenError);
}
//**************************************************************************************************************************
bool CParticleDlg::savePSAs(HWND parent, CParticleWorkspace::CNode &psNode ,const std::string &fullPath, bool askToContinueWhenError)
{
nlassert(psNode.getPSPointer());
if (psNode.getResetAutoCountFlag() && psNode.getPSPointer()->getAutoCountFlag())
{
MessageBox(psNode.getFilename().c_str() + getStrRsc(IDS_AUTO_COUNT_ERROR), getStrRsc(IDS_WARNING), MB_ICONEXCLAMATION);
return false;
}
StartStopDlg->stop();
try
{
psNode.savePSAs(fullPath);
psNode.setModified(false);
setStatusBarText(CString(fullPath.c_str()) + " " + getStrRsc(IDS_SAVED));
}
catch (NLMISC::Exception &e)
{
if (askToContinueWhenError)
{
int result = ::MessageBox(parent, CString(e.what()) + getStrRsc(IDS_CONTINUE_SAVING) , getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION);
return result == MB_OK;
}
else
{
::MessageBox(parent, e.what(), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION);
return false;
}
}
return true;
}
//**************************************************************************************************************************
bool CParticleDlg::loadPS(HWND parent, CParticleWorkspace::CNode &psNode, TLoadPSBehav behav)
{
bool loadingOK = true;
try
{
if (!psNode.loadPS())
{
loadingOK = false;
switch(behav)
{
case Silent: return false; // no op
case ReportError:
::MessageBox(parent, (LPCTSTR) (CString(psNode.getFilename().c_str()) + " : " + getStrRsc(IDS_COULDNT_INSTANCIATE_PS)), getStrRsc(IDS_ERROR), MB_OK|MB_ICONEXCLAMATION);
return true;
break;
case ReportErrorSkippable:
{
CSkippableMessageBox mb(getStrRsc(IDS_ERROR), CString(psNode.getFilename().c_str()) + " : " + getStrRsc(IDS_COULDNT_INSTANCIATE_PS), CWnd::FromHandle(parent));
mb.DoModal();
return !mb.getBypassFlag();
}
break;
default:
nlassert(0);
break;
}
}
else
{
setStatusBarText(CString(psNode.getFullPath().c_str()) + " " + getStrRsc(IDS_LOADED));
}
}
catch (NLMISC::Exception &e)
{
switch(behav)
{
case Silent: return false; // no op
case ReportError:
::MessageBox(parent, e.what(), getStrRsc(IDS_ERROR), MB_OK|MB_ICONEXCLAMATION);
return true;
break;
case ReportErrorSkippable:
{
CSkippableMessageBox mb(getStrRsc(IDS_ERROR), CString(e.what()), CWnd::FromHandle(parent));
mb.DoModal();
return !mb.getBypassFlag();
}
break;
default:
nlassert(0);
break;
}
}
if (psNode.getPSPointer()->hasLoop())
{
localizedMessageBox(parent, IDS_FX_HAS_LOOP, IDS_WARNING, MB_OK|MB_ICONEXCLAMATION);
}
return behav != Silent;
}
//**************************************************************************************************************************
void CParticleDlg::checkModifiedWorkSpace()
{
if (_PW)
{
// see if current tree has been changed
if (_PW->isModified())
{
int result = localizedMessageBox(*this, IDS_PS_WORKSPACE_MODIFIED, IDS_PARTICLE_EDITOR, MB_YESNO|MB_ICONQUESTION);
if (result == IDYES)
{
saveWorkspaceStructure();
}
}
if (_PW->isContentModified())
{
saveWorkspaceContent(true);
}
}
}
//**************************************************************************************************************************
void CParticleDlg::closeWorkspace()
{
setActiveNode(NULL);
ParticleTreeCtrl->setActiveNode(NULL);
ParticleTreeCtrl->reset();
delete _PW;
_PW = NULL;
}
//**************************************************************************************************************************
void CParticleDlg::OnCreateNewPsWorkspace()
{
checkModifiedWorkSpace();
// ask name of the new workspace to create
CCreateFileDlg cf(getStrRsc(IDS_CHOOSE_WORKSPACE_NAME), "", "pws");
INT_PTR result = cf.DoModal();
if (result = IDOK)
{
if (cf.touchFile())
{
CParticleWorkspace *newPW = new CParticleWorkspace;
newPW->setModificationCallback(ParticleTreeCtrl);
newPW->init(_ObjView, cf.getFullPath(), _ObjView->getFontManager(), _ObjView->getFontGenerator());
// save empty workspace
try
{
newPW->save();
}
catch(NLMISC::EStream &e)
{
MessageBox(e.what(), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION);
}
closeWorkspace();
_PW = newPW;
ParticleTreeCtrl->buildTreeFromWorkSpace(*_PW);
}
}
}
//**************************************************************************************************************************
void CParticleDlg::OnLoadPSWorkspace()
{
checkModifiedWorkSpace();
static const TCHAR BASED_CODE szFilter[] = _T("particle workspaces(*.pws)|*.pws||");
CFileDialog fd( TRUE, _T(".pws"), _T("*.pws"), 0, szFilter);
INT_PTR result = fd.DoModal();
if (result != IDOK) return;
loadWorkspace((LPCTSTR) fd.GetPathName());
}
//**************************************************************************************************************************
void CParticleDlg::loadWorkspace(const std::string &fullPath)
{
// Add to the path
std::auto_ptr<CParticleWorkspace> newPW(new CParticleWorkspace);
newPW->init(_ObjView, fullPath, _ObjView->getFontManager(), _ObjView->getFontGenerator());
newPW->setModificationCallback(ParticleTreeCtrl);
// save empty workspace
try
{
newPW->load();
setStatusBarText(CString(newPW->getFilename().c_str()) + " " + getStrRsc(IDS_LOADED));
}
catch(NLMISC::EStream &e)
{
MessageBox(e.what(), getStrRsc(IDS_ERROR), MB_ICONEXCLAMATION);
setStatusBarText(CString(e.what()));
return;
}
// try to load each ps
CParticleWorkspace::CNode *firstLoadedNode = NULL;
bool displayErrorMsg = true;
for(uint k = 0; k < newPW->getNumNode(); ++k)
{
displayErrorMsg = loadPS(*this, *newPW->getNode(k), displayErrorMsg ? ReportErrorSkippable : Silent);
if (newPW->getNode(k)->isLoaded() && !firstLoadedNode)
{
firstLoadedNode = newPW->getNode(k);
}
}
closeWorkspace();
_PW = newPW.release();
ParticleTreeCtrl->buildTreeFromWorkSpace(*_PW);
setActiveNode(firstLoadedNode);
ParticleTreeCtrl->setActiveNode(firstLoadedNode);
ParticleTreeCtrl->expandRoot();
setStatusBarText(getStrRsc(IDS_READY));
}
//**************************************************************************************************************************
void CParticleDlg::OnSaveAllPsWorkspace()
{
saveWorkspaceStructure();
saveWorkspaceContent(false);
}
//**************************************************************************************************************************
void CParticleDlg::OnSavePsWorkspace()
{
saveWorkspaceStructure();
saveWorkspaceContent(true);
}
//**************************************************************************************************************************
void CParticleDlg::saveWorkspaceStructure()
{
nlassert(_PW);
try
{
_PW->save();
setStatusBarText(CString(_PW->getFilename().c_str()) + " " + getStrRsc(IDS_SAVED));
}
catch(NLMISC::EStream &e)
{
localizedMessageBox(*this, e.what(), IDS_ERROR, MB_ICONEXCLAMATION);
setStatusBarText(CString(e.what()));
}
}
//**************************************************************************************************************************
void CParticleDlg::saveWorkspaceContent(bool askToSaveModifiedPS)
{
StartStopDlg->stop();
bool saveAll = !askToSaveModifiedPS;
// check each component of the tree
for(uint k = 0; k < _PW->getNumNode(); ++k)
{
if (_PW->getNode(k)->isModified())
{
if (saveAll)
{
bool keepSaving = savePS(*this, *_PW->getNode(k), k != _PW->getNumNode());
if (!keepSaving) break;
}
else
{
// ask if the user wants to save the ps, or save all ps
CString mess;
mess = CString(_PW->getNode(k)->getFilename().c_str()) + getStrRsc(IDS_SAVE_MODIFIED_PS);
CSaveOptionsDlg sop(getStrRsc(IDS_SAVE_FILE), mess, this);
sop.DoModal();
bool saveThisFile = false;
bool stop = false;
switch(sop.getChoice())
{
case CSaveOptionsDlg::Yes: saveThisFile = true; break;
case CSaveOptionsDlg::No: saveThisFile = false; break;
case CSaveOptionsDlg::SaveAll: saveAll = true; break;
case CSaveOptionsDlg::Stop: stop = true; break;
default: nlassert(0);
}
if (stop) break;
if (saveAll || saveThisFile)
{
bool keepSaving = savePS(*this, *_PW->getNode(k), k != _PW->getNumNode());
if (!keepSaving) break;
}
}
}
}
setStatusBarText(getStrRsc(IDS_READY));
}
//**************************************************************************************************************************
void CParticleDlg::setActiveNode(CParticleWorkspace::CNode *node)
{
if (node == _ActiveNode) return;
_ActiveNode = node;
StartStopDlg->setActiveNode(node);
if(MainFrame->isMoveFX())
{
if (node)
{
_ObjView->getMouseListener().setModelMatrix(node->getPSModel()->getMatrix());
}
else
{
_ObjView->getMouseListener().setModelMatrix(NLMISC::CMatrix::Identity);
}
}
else
if(MainFrame->isMoveFXUserMatrix())
{
if (node)
{
_ObjView->getMouseListener().setModelMatrix(node->getPSModel()->getUserMatrix());
}
else
{
_ObjView->getMouseListener().setModelMatrix(NLMISC::CMatrix::Identity);
}
}
}
//**************************************************************************************************************************
NL3D::CParticleSystemModel *CParticleDlg::getModelFromPS(NL3D::CParticleSystem *ps) const
{
if (!ps) return NULL;
if (!_PW) return NULL;
CParticleWorkspace::CNode *node = _PW->getNodeFromPS(ps);
if (!node) return NULL;
return node->getPSModel();
}
//**************************************************************************************************************************
uint CParticleDlg::computeStatusBarWidth() const
{
nlassert(ParticleTreeCtrl);
CRect tcRect;
ParticleTreeCtrl->GetClientRect(&tcRect);
if (!CurrentRightPane) return (uint) std::max((sint) tcRect.Width() - 16, (sint) 0);
return (uint) std::max((sint) tcRect.Width() - 4, (sint) 0);
}
//**************************************************************************************************************************
void CParticleDlg::OnViewPsFilename()
{
ParticleTreeCtrl->setViewFilenameFlag(!ParticleTreeCtrl->getViewFilenameFlag());
updateMenu();
}
//**************************************************************************************************************************
void CParticleDlg::updateMenu()
{
if (!ParticleTreeCtrl) return;
CMenu *menu = GetMenu();
if (!menu) return;
// update the view menu
menu->CheckMenuItem(IDM_VIEW_PS_FILENAME, MF_BYCOMMAND|(ParticleTreeCtrl->getViewFilenameFlag() ? MF_CHECKED : 0));
}