617 lines
16 KiB
C++
617 lines
16 KiB
C++
// AssimilateView.cpp : implementation of the CAssimilateView class
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include "Includes.h"
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
CAssimilateView* ghAssimilateView = NULL;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView
|
|
|
|
IMPLEMENT_DYNCREATE(CAssimilateView, CTreeView)
|
|
|
|
BEGIN_MESSAGE_MAP(CAssimilateView, CTreeView)
|
|
//{{AFX_MSG_MAP(CAssimilateView)
|
|
ON_NOTIFY_REFLECT(NM_DBLCLK, OnDblclk)
|
|
ON_WM_DESTROY()
|
|
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, OnItemexpanded)
|
|
ON_WM_RBUTTONDBLCLK()
|
|
ON_WM_KEYDOWN()
|
|
ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelchanged)
|
|
//}}AFX_MSG_MAP
|
|
// Standard printing commands
|
|
ON_COMMAND(ID_FILE_PRINT, CTreeView::OnFilePrint)
|
|
ON_COMMAND(ID_FILE_PRINT_DIRECT, CTreeView::OnFilePrint)
|
|
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CTreeView::OnFilePrintPreview)
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView construction/destruction
|
|
|
|
CAssimilateView::CAssimilateView()
|
|
{
|
|
ghAssimilateView = this;
|
|
}
|
|
|
|
CAssimilateView::~CAssimilateView()
|
|
{
|
|
}
|
|
|
|
BOOL CAssimilateView::PreCreateWindow(CREATESTRUCT& cs)
|
|
{
|
|
// TODO: Modify the Window class or styles here by modifying
|
|
// the CREATESTRUCT cs
|
|
|
|
if (!CTreeView::PreCreateWindow(cs))
|
|
{
|
|
return FALSE;
|
|
}
|
|
cs.style |= TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS/* | TVS_EDITLABELS*/;
|
|
return TRUE;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView drawing
|
|
|
|
void CAssimilateView::OnDraw(CDC* pDC)
|
|
{
|
|
CAssimilateDoc* pDoc = GetDocument();
|
|
ASSERT_VALID(pDoc);
|
|
// TODO: add draw code for native data here
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView printing
|
|
|
|
BOOL CAssimilateView::OnPreparePrinting(CPrintInfo* pInfo)
|
|
{
|
|
// default preparation
|
|
return DoPreparePrinting(pInfo);
|
|
}
|
|
|
|
void CAssimilateView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
|
|
{
|
|
// TODO: add extra initialization before printing
|
|
}
|
|
|
|
void CAssimilateView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
|
|
{
|
|
// TODO: add cleanup after printing
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView diagnostics
|
|
|
|
#ifdef _DEBUG
|
|
void CAssimilateView::AssertValid() const
|
|
{
|
|
CTreeView::AssertValid();
|
|
}
|
|
|
|
void CAssimilateView::Dump(CDumpContext& dc) const
|
|
{
|
|
CTreeView::Dump(dc);
|
|
}
|
|
|
|
CAssimilateDoc* CAssimilateView::GetDocument() // non-debug version is inline
|
|
{
|
|
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CAssimilateDoc)));
|
|
return (CAssimilateDoc*)m_pDocument;
|
|
}
|
|
#endif //_DEBUG
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CAssimilateView message handlers
|
|
|
|
void CAssimilateView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
|
|
{
|
|
// TODO: Add your specialized code here and/or call the base class
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
|
|
switch(lHint)
|
|
{
|
|
case AS_DELETECONTENTS:
|
|
tree.DeleteAllItems();
|
|
break;
|
|
case AS_NEWFILE:
|
|
// findme: hmmm, not sure about this. Adding files zaps the tree? Would look odd to user...
|
|
tree.DeleteAllItems();
|
|
BuildTree();
|
|
UpdateTree();
|
|
break;
|
|
case AS_FILESUPDATED:
|
|
UpdateTree(); // sorts tree according to enum names, sorts model according to new tree, re-draws tree
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SetFirstModelTitleAndMode();
|
|
}
|
|
|
|
|
|
|
|
// in practise, the Assimilate app will only ever use one sort type, but wtf...
|
|
//
|
|
typedef enum
|
|
{
|
|
TS_BY_ENUMTYPE = 0,
|
|
} TREESORTTYPE;
|
|
|
|
//
|
|
// tree item compare callback,
|
|
//
|
|
// returns (to windows):- (so do NOT change these defines)
|
|
//
|
|
#define ITEM1_BEFORE_ITEM2 -1
|
|
#define ITEM1_MATCHES_ITEM2 0
|
|
#define ITEM1_AFTER_ITEM2 1
|
|
//
|
|
int CALLBACK TreeCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
|
{
|
|
CSequence* seq1 = (CSequence*) lParam1;
|
|
CSequence* seq2 = (CSequence*) lParam2;
|
|
|
|
switch (lParamSort)
|
|
{
|
|
case TS_BY_ENUMTYPE:
|
|
{
|
|
// enumtypes are enum'd lower-numbers-more-important...
|
|
//
|
|
if (seq1->GetEnumType() < seq2->GetEnumType())
|
|
{
|
|
return ITEM1_BEFORE_ITEM2;
|
|
}
|
|
|
|
if (seq1->GetEnumType() > seq2->GetEnumType())
|
|
{
|
|
return ITEM1_AFTER_ITEM2;
|
|
}
|
|
|
|
// must be same type, so sort alphabetically...
|
|
// (strcmp almost returns correct values, except the results are signed anynums, not explicitly -1/0/1)
|
|
//
|
|
return ITEM1_MATCHES_ITEM2;
|
|
/*
|
|
int i = strcmp(seq1->GetEnum(),seq2->GetEnum());
|
|
|
|
if (i<0)
|
|
{
|
|
return ITEM1_BEFORE_ITEM2;
|
|
}
|
|
|
|
if (i>0)
|
|
{
|
|
return ITEM1_AFTER_ITEM2;
|
|
}
|
|
|
|
return ITEM1_MATCHES_ITEM2;
|
|
*/
|
|
}
|
|
break;
|
|
}
|
|
|
|
ASSERT(0);
|
|
return ITEM1_MATCHES_ITEM2;
|
|
}
|
|
|
|
bool TreeSort(CTreeCtrl& tree, TREESORTTYPE ts)
|
|
{
|
|
HTREEITEM hTreeItem_Model = tree.GetRootItem(); // "klingon"
|
|
|
|
while (hTreeItem_Model)
|
|
{
|
|
TVSORTCB sortParams;
|
|
|
|
sortParams.hParent = hTreeItem_Model;
|
|
sortParams.lpfnCompare = TreeCompareFunc;
|
|
sortParams.lParam = ts;
|
|
|
|
tree.SortChildrenCB(&sortParams);
|
|
|
|
hTreeItem_Model = tree.GetNextSiblingItem(hTreeItem_Model);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// returns true if any delete has taken place (sequence or model)...
|
|
//
|
|
bool CAssimilateView::DeleteCurrentItem(bool bNoQueryForSequenceDelete)
|
|
{
|
|
// OutputDebugString(va("DeleteCurrentItem %s\n",bNoQueryForSequenceDelete?"CTRL":""));
|
|
|
|
// ok, what *is* the current item?
|
|
//
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
HTREEITEM theItem = tree.GetSelectedItem();
|
|
if (theItem == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ok, so it's either a model or a sequence...
|
|
//
|
|
CModel* curModel = NULL;
|
|
if (tree.GetParentItem(theItem) == NULL)
|
|
{
|
|
curModel = (CModel*)tree.GetItemData(theItem);
|
|
if (curModel != NULL)
|
|
{
|
|
if (GetYesNo(va("Delete whole model \"%s\"?",curModel->GetName())))
|
|
{
|
|
tree.DeleteItem(theItem);
|
|
GetDocument()->DeleteModel(curModel);
|
|
GetDocument()->SetModifiedFlag();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curModel = (CModel*)tree.GetItemData(tree.GetParentItem(theItem));
|
|
CSequence* curSequence = (CSequence*)tree.GetItemData(theItem);
|
|
if (curSequence != NULL)
|
|
{
|
|
// ask them if they're sure, unless the ctrl key is also pressed...
|
|
//
|
|
if (bNoQueryForSequenceDelete || GetYesNo(va("Delete sequence \"%s\"?",curSequence->GetName())))
|
|
{
|
|
tree.DeleteItem(theItem);
|
|
curModel->DeleteSequence(curSequence);
|
|
GetDocument()->SetModifiedFlag();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// also re-orders model(s) sequences to match tree order...
|
|
//
|
|
void CAssimilateView::SortTree()
|
|
{
|
|
TreeSort(GetTreeCtrl(),TS_BY_ENUMTYPE);
|
|
|
|
// now update models to reflect their positions within the tree struct...
|
|
//
|
|
HTREEITEM hTreeItem_Model = GetTreeCtrl().GetRootItem(); // "klingon"
|
|
|
|
while (hTreeItem_Model)
|
|
{
|
|
CModel* curModel = (CModel*) GetTreeCtrl().GetItemData(hTreeItem_Model);
|
|
// OutputDebugString(va("item = '%s'\n",(LPCSTR) GetTreeCtrl().GetItemText(hTreeItem_Model)));
|
|
|
|
int iSequenceNum = 0;
|
|
|
|
HTREEITEM hTreeItem_Sequence = GetTreeCtrl().GetChildItem(hTreeItem_Model);
|
|
|
|
while (hTreeItem_Sequence)
|
|
{
|
|
CSequence* curSequence = (CSequence*) GetTreeCtrl().GetItemData(hTreeItem_Sequence);
|
|
|
|
curSequence->m_iSequenceNumber = iSequenceNum++;
|
|
|
|
hTreeItem_Sequence = GetTreeCtrl().GetNextSiblingItem(hTreeItem_Sequence);
|
|
}
|
|
|
|
curModel->ReOrderSequences();
|
|
|
|
hTreeItem_Model = GetTreeCtrl().GetNextSiblingItem(hTreeItem_Model);
|
|
}
|
|
|
|
// sod it, too much work to check if re-ordering actually took place. Let's assume it did... :-)
|
|
//
|
|
GetDocument()->SetModifiedFlag();
|
|
}
|
|
|
|
// sorts tree according to enum names, sorts model according to new tree, re-draws tree
|
|
// (re-draw = changes icons & text according to enum-validity and prefs)
|
|
//
|
|
void CAssimilateView::UpdateTree()
|
|
{
|
|
SortTree(); // sorts tree according to enum names, sorts model according to new tree
|
|
|
|
HTREEITEM hTreeItem_Model = GetTreeCtrl().GetRootItem(); // "klingon"
|
|
|
|
while (hTreeItem_Model)
|
|
{
|
|
CModel* curModel = (CModel*) GetTreeCtrl().GetItemData(hTreeItem_Model);
|
|
// OutputDebugString(va("item = '%s'\n",(LPCSTR) GetTreeCtrl().GetItemText(hTreeItem_Model)));
|
|
|
|
HTREEITEM hTreeItem_Sequence = GetTreeCtrl().GetChildItem(hTreeItem_Model);
|
|
|
|
while (hTreeItem_Sequence)
|
|
{
|
|
CSequence* curSequence = (CSequence*) GetTreeCtrl().GetItemData(hTreeItem_Sequence);
|
|
|
|
// OutputDebugString(va("item = '%s'\n",(LPCSTR) GetTreeCtrl().GetItemText(hTreeItem_Sequence)));
|
|
|
|
// gQuakeHelperTreeViewhandle->GetTreeCtrl().SetItemState(hTreeItem, bMatch?TVIS_SELECTED:0, TVIS_SELECTED);
|
|
|
|
// update this tree item accordingly...
|
|
//
|
|
CDC *pDC = GetDC();
|
|
GetTreeCtrl().SetItemText(hTreeItem_Sequence, curSequence->GetDisplayNameForTree(curModel,gbViewAnimEnums,gbViewFrameDetails,gbViewFrameDetails_Additional,pDC));
|
|
ReleaseDC(pDC);
|
|
int iIcon = curSequence->GetDisplayIconForTree(curModel);
|
|
GetTreeCtrl().SetItemImage(hTreeItem_Sequence, iIcon, iIcon);
|
|
hTreeItem_Sequence = GetTreeCtrl().GetNextSiblingItem(hTreeItem_Sequence);
|
|
}
|
|
|
|
hTreeItem_Model = GetTreeCtrl().GetNextSiblingItem(hTreeItem_Model);
|
|
}
|
|
}
|
|
|
|
|
|
PlayerMode_e CAssimilateView::GetSingleOrMultiPlayerMode()
|
|
{
|
|
PlayerMode_e ePlayerMode = eMODE_BAD;
|
|
|
|
LPCSTR psEnumFilename = ((CAssimilateApp*)AfxGetApp())->GetEnumFilename();
|
|
|
|
if (psEnumFilename && !stricmp(psEnumFilename,sDEFAULT_ENUM_FILENAME))
|
|
ePlayerMode = eMODE_SINGLE;
|
|
else
|
|
if (psEnumFilename && !stricmp(psEnumFilename,sDEFAULT_ENUM_FILENAME_MULTI))
|
|
ePlayerMode = eMODE_MULTI;
|
|
|
|
return ePlayerMode;
|
|
}
|
|
|
|
void CAssimilateView::SetFirstModelTitleAndMode()
|
|
{
|
|
PlayerMode_e ePlayerMode = GetSingleOrMultiPlayerMode();
|
|
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
CModel* curModel = GetDocument()->GetFirstModel();
|
|
if (curModel)
|
|
{
|
|
HTREEITEM hTreeItem = tree.GetRootItem();
|
|
tree.SetItemText(hTreeItem, va("%s: ( %s )",curModel->GetName(), (ePlayerMode == eMODE_SINGLE)?"Single-Player Mode":(ePlayerMode == eMODE_MULTI)?"Multi-Player Mode":"Unknown Mode - Prefs not setup?"));
|
|
}
|
|
}
|
|
|
|
void CAssimilateView::BuildTree()
|
|
{
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
|
|
CModel* curModel = GetDocument()->GetFirstModel();
|
|
while(curModel != NULL)
|
|
{
|
|
HTREEITEM item = tree.InsertItem(curModel->GetName());
|
|
tree.SetItemData(item, DWORD(curModel));
|
|
tree.SetItemImage(item, ObjID_Folder, ObjID_Folder);
|
|
CSequence* curSequence = curModel->GetFirstSequence();
|
|
while(curSequence != NULL)
|
|
{
|
|
CDC *pDC = GetDC();
|
|
HTREEITEM seqItem = tree.InsertItem(curSequence->GetDisplayNameForTree(curModel,gbViewAnimEnums,gbViewFrameDetails,gbViewFrameDetails_Additional,pDC), item);
|
|
ReleaseDC(pDC);
|
|
tree.SetItemData(seqItem, DWORD(curSequence));
|
|
/* if (curSequence->ValidEnum())
|
|
{
|
|
tree.SetItemImage(seqItem, ObjID_Sequence, ObjID_Sequence);
|
|
}
|
|
else
|
|
{
|
|
tree.SetItemImage(seqItem, ObjID_MissingEnum, ObjID_MissingEnum);
|
|
}*/
|
|
int iIcon = curSequence->GetDisplayIconForTree(curModel);
|
|
tree.SetItemImage(seqItem, iIcon, iIcon);
|
|
|
|
curSequence = curSequence->GetNext();
|
|
}
|
|
curModel = curModel->GetNext();
|
|
}
|
|
|
|
SetFirstModelTitleAndMode();
|
|
}
|
|
|
|
void CAssimilateView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
// OutputDebugString("double\n");
|
|
|
|
*pResult = 0;
|
|
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
HTREEITEM theItem = tree.GetSelectedItem();
|
|
if (theItem == NULL)
|
|
{
|
|
return;
|
|
}
|
|
if (tree.GetParentItem(theItem) == NULL)
|
|
{
|
|
CModel* theModel = (CModel*)tree.GetItemData(theItem);
|
|
if (theModel != NULL)
|
|
{
|
|
if (theModel->DoProperties())
|
|
{
|
|
GetDocument()->SetModifiedFlag();
|
|
}
|
|
*pResult = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CSequence* theSequence = (CSequence*)tree.GetItemData(theItem);
|
|
if (theSequence != NULL)
|
|
{
|
|
if (theSequence->DoProperties())
|
|
{
|
|
GetDocument()->SetModifiedFlag();
|
|
}
|
|
*pResult = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAssimilateView::OnInitialUpdate()
|
|
{
|
|
CTreeView::OnInitialUpdate();
|
|
|
|
// TODO: Add your specialized code here and/or call the base class
|
|
CImageList *pimagelist;
|
|
pimagelist = new CImageList();
|
|
pimagelist->Create(IDB_TREEIMAGES, 16, FALSE, 0x00ffffff);
|
|
CTreeCtrl& theTree = GetTreeCtrl();
|
|
CImageList* oldImageList;
|
|
oldImageList = theTree.GetImageList(TVSIL_NORMAL);
|
|
if (oldImageList != NULL)
|
|
delete(oldImageList);
|
|
theTree.SetImageList(pimagelist, TVSIL_NORMAL);
|
|
}
|
|
|
|
void CAssimilateView::OnDestroy()
|
|
{
|
|
CTreeCtrl& theTree = GetTreeCtrl();
|
|
CImageList *pimagelist;
|
|
pimagelist = theTree.GetImageList(TVSIL_NORMAL);
|
|
if (pimagelist != NULL)
|
|
{
|
|
pimagelist->DeleteImageList();
|
|
delete pimagelist;
|
|
}
|
|
CTreeView::OnDestroy();
|
|
|
|
// TODO: Add your message handler code here
|
|
|
|
}
|
|
|
|
void CAssimilateView::OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
|
|
// TODO: Add your control notification handler code here
|
|
CTreeCtrl& theTree = GetTreeCtrl();
|
|
HTREEITEM curSelection = pNMTreeView->itemNew.hItem;
|
|
if (theTree.GetParentItem(curSelection) == NULL)
|
|
{
|
|
int expandState = theTree.GetItemState(curSelection, TVIS_EXPANDED);
|
|
if (theTree.GetItemState(curSelection, TVIS_EXPANDED) & TVIS_EXPANDED)
|
|
{
|
|
theTree.SetItemImage(curSelection, ObjID_OpenFolder, ObjID_OpenFolder);
|
|
}
|
|
else
|
|
{
|
|
theTree.SetItemImage(curSelection, ObjID_Folder, ObjID_Folder);
|
|
}
|
|
}
|
|
*pResult = 0;
|
|
}
|
|
|
|
void CAssimilateView::OnRButtonDblClk(UINT nFlags, CPoint point)
|
|
{
|
|
// TODO: Add your message handler code here and/or call default
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
HTREEITEM theItem = tree.GetSelectedItem();
|
|
if (theItem == NULL)
|
|
{
|
|
CTreeView::OnRButtonDblClk(nFlags, point);
|
|
return; //don't have an item, don't go on
|
|
}
|
|
if (tree.GetParentItem(theItem) == NULL)
|
|
{
|
|
CModel* theModel = (CModel*)tree.GetItemData(theItem);
|
|
if (theModel != NULL)
|
|
{
|
|
if (theModel->DoProperties())
|
|
{
|
|
GetDocument()->SetModifiedFlag();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* // fixme: update this stuff some other time
|
|
CSequence* theSequence = (CSequence*)tree.GetItemData(theItem);
|
|
if (theSequence != NULL)
|
|
{
|
|
if (theSequence->Parse())
|
|
{
|
|
GetDocument()->SetModifiedFlag();
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void CAssimilateView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
|
|
{
|
|
switch (nChar)
|
|
{
|
|
case VK_DELETE:
|
|
{
|
|
if (DeleteCurrentItem(!!(GetAsyncKeyState(VK_CONTROL)))) // NoQueryForSequenceDelete if CTRL pressed
|
|
{
|
|
UpdateTree(); // sorts tree according to enum names, sorts model according to new tree, re-draws tree
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
|
|
}
|
|
|
|
|
|
|
|
// currently tells the document which model is the one the user considers current (ie which one is blue in tree)
|
|
//
|
|
void CAssimilateView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
// OutputDebugString("OnSelchanged TOP\n");
|
|
GetDocument()->ClearModelUserSelectionBools();
|
|
|
|
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
|
|
HTREEITEM theItem = pNMTreeView->itemNew.hItem;
|
|
|
|
if (!theItem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CTreeCtrl& tree = GetTreeCtrl();
|
|
|
|
CModel *theModel = NULL;
|
|
if (tree.GetParentItem(theItem) == NULL)
|
|
{
|
|
theModel = (CModel*)tree.GetItemData(theItem);
|
|
ASSERT(theModel);
|
|
if (theModel)
|
|
{
|
|
theModel->SetUserSelectionBool();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
theModel = (CModel*)tree.GetItemData(tree.GetParentItem(theItem));
|
|
ASSERT(theModel);
|
|
if (theModel)
|
|
{
|
|
theModel->SetUserSelectionBool();
|
|
}
|
|
}
|
|
|
|
// OutputDebugString("OnSelchanged\n");
|
|
// if (theModel)
|
|
// {
|
|
// OutputDebugString(va("Model = %s\n",theModel->GetName()));
|
|
// }
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|