You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1193 lines
34 KiB
1193 lines
34 KiB
// rootprop.cpp - Root Property Page Implementation
|
|
|
|
#include "stdafx.h"
|
|
#include "resource.h"
|
|
#include "rootprop.h"
|
|
#include "compdata.h"
|
|
#include "scopenode.h"
|
|
#include "wizards.h"
|
|
#include "query.h"
|
|
#include "cmndlgs.h"
|
|
#include "util.h"
|
|
#include "namemap.h"
|
|
|
|
#include <windowsx.h>
|
|
#include <algorithm>
|
|
|
|
int GetDateTimeString(FILETIME* pftime, LPWSTR pszBuf, int cBuf);
|
|
void LoadObjectCB(CComboBox& ComboBox, CEditObjList& ObjList);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// CRootGeneralPage
|
|
|
|
LRESULT CRootGeneralPage::OnInitDialog(UINT mMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
CRootNode* pRootNode = m_ObjList.RootNode();
|
|
ASSERT(pRootNode != NULL);
|
|
if( !pRootNode ) return 0;
|
|
|
|
Edit_LimitText(GetDlgItem(IDC_COMMENTS),255);
|
|
|
|
SetDlgItemText( IDC_NAME, pRootNode->GetName() );
|
|
|
|
tstring strComment;
|
|
pRootNode->GetComment(strComment);
|
|
SetDlgItemText( IDC_COMMENTS, strComment.c_str() );
|
|
|
|
FILETIME ftime;
|
|
WCHAR szDateTime[32];
|
|
|
|
pRootNode->GetCreateTime(&ftime);
|
|
if( GetDateTimeString(&ftime, szDateTime, lengthof(szDateTime)) )
|
|
{
|
|
SetDlgItemText( IDC_CREATED, szDateTime );
|
|
}
|
|
|
|
pRootNode->GetModifyTime(&ftime);
|
|
if( GetDateTimeString(&ftime, szDateTime, lengthof(szDateTime)) )
|
|
{
|
|
SetDlgItemText( IDC_MODIFIED, szDateTime );
|
|
}
|
|
|
|
m_bChgComment = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CRootGeneralPage::OnChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
|
|
{
|
|
SetModified(TRUE);
|
|
m_bChgComment = TRUE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// CRootGeneralPage::OnClose
|
|
//
|
|
// This method is invoked when the edit box receives an Esc char. The method converts
|
|
// the WM_CLOSE message into a command to close the property sheet. Otherwise the
|
|
// WM_CLOSE message has no effect.
|
|
//------------------------------------------------------------------------------------
|
|
LRESULT CRootGeneralPage::OnClose( UINT mMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
|
|
{
|
|
// Simulate press of Cancel button
|
|
::PropSheet_PressButton(GetParent(), PSBTN_CANCEL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
BOOL CRootGeneralPage::OnSetActive()
|
|
{
|
|
m_ObjList.PageActive(m_hWnd);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CRootGeneralPage::OnApply()
|
|
{
|
|
if (m_bChgComment)
|
|
{
|
|
CRootNode* pRootNode = m_ObjList.RootNode();
|
|
ASSERT(pRootNode);
|
|
if( !pRootNode ) return FALSE;
|
|
|
|
int cLen = ::GetWindowTextLength(GetDlgItem(IDC_COMMENTS));
|
|
LPWSTR szTemp = new WCHAR[(cLen+1)];
|
|
if( !szTemp ) return FALSE;
|
|
|
|
int cLen1 = ::GetWindowText(GetDlgItem(IDC_COMMENTS), szTemp, cLen+1);
|
|
ASSERT(cLen == cLen1);
|
|
|
|
pRootNode->SetComment(szTemp);
|
|
pRootNode->UpdateModifyTime();
|
|
|
|
m_bChgComment = FALSE;
|
|
|
|
delete [] szTemp;
|
|
}
|
|
|
|
return m_ObjList.ApplyChanges(m_hWnd);
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// CRootMenuPage
|
|
|
|
CRootMenuPage::~CRootMenuPage()
|
|
{
|
|
m_ObjectCB.Detach();
|
|
m_MenuLV.Detach();
|
|
|
|
m_ObjList.Release();
|
|
}
|
|
|
|
LRESULT CRootMenuPage::OnInitDialog( UINT mMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
|
|
{
|
|
m_ObjectCB.Attach(GetDlgItem(IDC_OBJECTLIST));
|
|
m_MenuLV.Attach(GetDlgItem(IDC_MENULIST));
|
|
|
|
m_itObjSelect = NULL;
|
|
|
|
::ConfigSingleColumnListView(GetDlgItem(IDC_MENULIST));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CRootMenuPage::OnSetActive()
|
|
{
|
|
m_ObjList.PageActive(m_hWnd);
|
|
|
|
LoadObjectCB(m_ObjectCB, m_ObjList);
|
|
|
|
// if object was previously selected
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
// find the edit object by name because it may have been moved or deleted
|
|
// while another page was active
|
|
m_itObjSelect = m_ObjList.FindObject(m_strObjSelect.c_str());
|
|
|
|
if (m_itObjSelect != NULL && m_itObjSelect->IsDeleted())
|
|
m_itObjSelect = NULL;
|
|
}
|
|
|
|
// if object still around, reselect it in the combo box
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
|
|
ASSERT( pNameMap );
|
|
if( !pNameMap ) return FALSE;
|
|
|
|
int iSel = m_ObjectCB.FindStringExact(-1, pNameMap->GetAttributeDisplayName(m_strObjSelect.c_str()));
|
|
ASSERT(iSel != CB_ERR);
|
|
|
|
m_ObjectCB.SetCurSel(iSel);
|
|
}
|
|
else if (m_ObjectCB.GetCount() > 0)
|
|
{
|
|
// default to the first object and update the columns
|
|
m_ObjectCB.SetCurSel(0);
|
|
|
|
void* pv = m_ObjectCB.GetItemDataPtr(0);
|
|
m_itObjSelect = *(EditObjIter*)&pv;
|
|
|
|
m_strObjSelect = m_itObjSelect->Name();
|
|
|
|
DisplayMenus();
|
|
}
|
|
else
|
|
{
|
|
DisplayMenus();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CRootMenuPage::OnObjectSelect( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
int iItem = m_ObjectCB.GetCurSel();
|
|
|
|
// Double-clicking an empty combo box will call this with no selection
|
|
if (iItem >= 0)
|
|
{
|
|
void* pv = m_ObjectCB.GetItemDataPtr(iItem);
|
|
m_itObjSelect = *(EditObjIter*)&pv;
|
|
m_strObjSelect = m_itObjSelect->Name();
|
|
|
|
DisplayMenus();
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
void CRootMenuPage::DisplayMenus()
|
|
{
|
|
HWND hwndLV = GetDlgItem(IDC_MENULIST);
|
|
if( !hwndLV || !::IsWindow(hwndLV) ) return;
|
|
|
|
ListView_DeleteAllItems(hwndLV);
|
|
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
CClassInfo& classInfo = m_itObjSelect->GetObject();
|
|
|
|
// make sure menu names have been loaded
|
|
IStringTable* pStringTable = m_ObjList.RootNode()->GetCompData()->GetStringTable();
|
|
ASSERT(pStringTable != NULL);
|
|
classInfo.LoadStrings(pStringTable);
|
|
|
|
LV_ITEM lvi;
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.iItem = 0;
|
|
lvi.iSubItem = 0;
|
|
|
|
menucmd_vector::iterator itMenu;
|
|
for (itMenu = classInfo.Menus().begin(); itMenu != classInfo.Menus().end(); ++itMenu)
|
|
{
|
|
lvi.pszText = const_cast<LPWSTR>((*itMenu)->Name());
|
|
lvi.lParam = (*itMenu)->ID();
|
|
|
|
int iPos = ListView_InsertItem(hwndLV, &lvi);
|
|
ASSERT(iPos >= 0);
|
|
|
|
lvi.iItem++;
|
|
}
|
|
|
|
// if items are added, select the first
|
|
if (ListView_GetItemCount(hwndLV) > 0)
|
|
{
|
|
ListView_SetItemState(hwndLV, 0, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
|
|
}
|
|
}
|
|
|
|
|
|
EnableDlgItem( m_hWnd, IDC_ADDMENU, (m_itObjSelect != NULL) );
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEMENU, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_EDITMENU, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEUP, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEDOWN, FALSE );
|
|
}
|
|
|
|
|
|
LRESULT CRootMenuPage::OnAddMenu( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
// if object is selected
|
|
if (m_itObjSelect == NULL)
|
|
return 0;
|
|
|
|
CAddMenuDlg dlg(m_itObjSelect->GetObject());
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
CClassInfo* pClassInfo = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfo ) return 0;
|
|
|
|
CMenuCmd* pMenuNew = dlg.GetMenu();
|
|
ASSERT(pMenuNew != NULL);
|
|
|
|
if( pMenuNew )
|
|
{
|
|
// Add new menu to list
|
|
HWND hwndList = GetDlgItem(IDC_MENULIST);
|
|
|
|
// Set name to add it to string table and generate the menu ID
|
|
IStringTable* pStringTable = m_ObjList.RootNode()->GetCompData()->GetStringTable();
|
|
ASSERT( pStringTable );
|
|
if( !pStringTable ) return 0;
|
|
|
|
// Use temp string because string fails an assignement like: strX = strX.c_str()
|
|
// (it relases the private buffer first and then assigns the string)
|
|
tstring strName = pMenuNew->Name();
|
|
pMenuNew->SetName(pStringTable, strName.c_str());
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM | LVIF_TEXT;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = ListView_GetItemCount(hwndList);
|
|
lvi.lParam = pMenuNew->ID();
|
|
lvi.pszText = const_cast<LPWSTR>(pMenuNew->Name());
|
|
ListView_InsertItem(hwndList,&lvi);
|
|
|
|
// if first item is added, select it
|
|
if (ListView_GetItemCount(hwndList) == 1)
|
|
{
|
|
ListView_SetItemState(hwndList, 0, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
|
|
}
|
|
|
|
// Add to menu vector (note that temp CMenuCmdPtr will delete pMenuNew)
|
|
pClassInfo->Menus().push_back(CMenuCmdPtr(pMenuNew));
|
|
|
|
SetModified(TRUE);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT CRootMenuPage::OnEditMenu( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_MENULIST);
|
|
if( !hwndList || !::IsWindow(hwndList) ) return 0;
|
|
|
|
int iIndex = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
|
|
ASSERT(iIndex != -1);
|
|
if( iIndex == -1 ) return 0;
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = iIndex;
|
|
|
|
ListView_GetItem(hwndList, &lvi);
|
|
|
|
// Locate selected menu by it's ID (lparam)
|
|
CClassInfo& classInfo = m_itObjSelect->GetObject();
|
|
menucmd_vector& vMenus = classInfo.Menus();
|
|
|
|
menucmd_vector::iterator itMenu;
|
|
itMenu = std::find(vMenus.begin(), vMenus.end(), lvi.lParam);
|
|
ASSERT(itMenu != vMenus.end());
|
|
if( itMenu == vMenus.end() ) return 0;
|
|
|
|
CMenuCmd* pMenu = *itMenu;
|
|
if( !pMenu ) return 0;
|
|
|
|
CAddMenuDlg dlg(m_itObjSelect->GetObject(), pMenu);
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
CMenuCmd* pMenuNew = dlg.GetMenu();
|
|
ASSERT( pMenuNew );
|
|
if( !pMenuNew ) return 0;
|
|
|
|
// Set the name again in case it was changed
|
|
IStringTable* pStringTable = m_ObjList.RootNode()->GetCompData()->GetStringTable();
|
|
ASSERT( pStringTable );
|
|
if( !pStringTable ) return 0;
|
|
|
|
// Use temp string because string fails an assignement like: strX = strX.c_str()
|
|
// (it relases the private buffer first and then assigns the string)
|
|
tstring strName = pMenuNew->Name();
|
|
pMenuNew->SetName(pStringTable, strName.c_str());
|
|
|
|
// locate object again because the vector may have been reallocated
|
|
CClassInfo* pClassInfoNew = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfoNew ) return 0;
|
|
|
|
menucmd_vector& vMenusNew = pClassInfoNew->Menus();
|
|
|
|
// locate with the old ID because it will be different if the name was changed
|
|
itMenu = std::find(vMenusNew.begin(), vMenusNew.end(), pMenu->ID());
|
|
ASSERT(itMenu != vMenusNew.end());
|
|
if( itMenu == vMenusNew.end() ) return 0;
|
|
|
|
// Replace menu with new one
|
|
*itMenu = pMenuNew;
|
|
|
|
// Update the list
|
|
lvi.mask = LVIF_PARAM | LVIF_TEXT;
|
|
lvi.lParam = pMenuNew->ID();
|
|
lvi.pszText = const_cast<LPWSTR>(pMenuNew->Name());
|
|
ListView_SetItem(hwndList,&lvi);
|
|
|
|
SetModified(TRUE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CRootMenuPage::OnRemoveMenu( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
if (m_itObjSelect == NULL)
|
|
return 0;
|
|
|
|
HWND hwndList = GetDlgItem(IDC_MENULIST);
|
|
|
|
UINT uiMsg = (ListView_GetSelectedCount(hwndList) == 1) ? IDS_MENU_REMOVE_ONE : IDS_MENU_REMOVE;
|
|
int iRet = DisplayMessageBox(m_hWnd, IDS_MENU_REMOVE_TITLE, uiMsg, MB_YESNO|MB_ICONWARNING);
|
|
if (iRet != IDYES)
|
|
return 0;
|
|
|
|
CClassInfo* pClassInfo = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfo ) return 0;
|
|
menucmd_vector& vMenus = pClassInfo->Menus();
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
|
|
int iIndex = -1;
|
|
while ((iIndex = ListView_GetNextItem(hwndList, iIndex, LVNI_SELECTED)) >= 0)
|
|
{
|
|
lvi.iItem = iIndex;
|
|
ListView_GetItem(hwndList, &lvi);
|
|
|
|
// Locate menu by its ID
|
|
menucmd_vector::iterator itMenu = std::find(vMenus.begin(), vMenus.end(), lvi.lParam);
|
|
ASSERT(itMenu != vMenus.end());
|
|
|
|
vMenus.erase(itMenu);
|
|
|
|
ListView_DeleteItem(hwndList, iIndex);
|
|
iIndex--;
|
|
}
|
|
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEMENU, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_EDITMENU, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEUP, FALSE );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEDOWN, FALSE );
|
|
|
|
SetModified(TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CRootMenuPage::OnMoveUpDown( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_MENULIST);
|
|
int iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
|
|
ASSERT(iItem >= 0);
|
|
|
|
// Determine new position for selected item
|
|
if (wID == IDC_MOVEUP)
|
|
iItem--;
|
|
else
|
|
iItem++;
|
|
|
|
// Now swap the selected item with the item at its new position
|
|
// Do it by moving the unselected item to avoid state change notifications
|
|
// because they will cause unwanted butten enables/disables.
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
lvi.iItem = iItem;
|
|
ListView_GetItem(hwndList, &lvi);
|
|
|
|
|
|
// Move the menu item in the menu vector
|
|
CClassInfo* pClassInfo = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfo ) return 0;
|
|
menucmd_vector& vMenus = pClassInfo->Menus();
|
|
|
|
menucmd_vector::iterator itMenu = std::find(vMenus.begin(), vMenus.end(), lvi.lParam);
|
|
ASSERT(itMenu != vMenus.end());
|
|
|
|
menucmd_vector::iterator itMenuOld = itMenu;
|
|
if (wID == IDC_MOVEUP)
|
|
itMenu++;
|
|
else
|
|
itMenu--;
|
|
|
|
// swap the items
|
|
std::iter_swap (itMenuOld, itMenu);
|
|
|
|
//Now delete and reinsert it in the list view
|
|
ListView_DeleteItem(hwndList, lvi.iItem);
|
|
|
|
if (wID == IDC_MOVEUP)
|
|
lvi.iItem++;
|
|
else
|
|
lvi.iItem--;
|
|
lvi.mask = LVIF_PARAM | LVIF_TEXT;
|
|
lvi.pszText = const_cast<LPWSTR>((*itMenu)->Name());
|
|
ListView_InsertItem(hwndList, &lvi);
|
|
|
|
|
|
// Update Up/Down buttons
|
|
EnableDlgItem( m_hWnd, IDC_MOVEUP, (iItem > 0) );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEDOWN, (iItem < (ListView_GetItemCount(hwndList) - 1)) );
|
|
|
|
SetModified(TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CRootMenuPage::OnMenuListChanged(int idCtrl, LPNMHDR pNMHDR, BOOL& bHandled)
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_MENULIST);
|
|
|
|
int nItemSel = ListView_GetSelectedCount(hwndList);
|
|
int iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED);
|
|
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEMENU, (nItemSel > 0) );
|
|
EnableDlgItem( m_hWnd, IDC_EDITMENU, (nItemSel == 1) );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEUP, ((nItemSel == 1) && (iItem > 0)) );
|
|
EnableDlgItem( m_hWnd, IDC_MOVEDOWN, ((nItemSel == 1) && (iItem < (ListView_GetItemCount(hwndList) - 1))) );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
LRESULT CRootMenuPage::OnMenuListDblClk(int idCtrl, LPNMHDR pNMHDR, BOOL& bHandled)
|
|
{
|
|
if (ListView_GetSelectedCount(GetDlgItem(IDC_MENULIST)))
|
|
::SendMessage(GetDlgItem(IDC_EDITMENU), BM_CLICK, (WPARAM)0, (LPARAM)0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL CRootMenuPage::OnApply()
|
|
{
|
|
return m_ObjList.ApplyChanges(m_hWnd);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// CRootObjectPage
|
|
|
|
|
|
LRESULT CRootObjectPage::OnInitDialog(UINT mMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_OBJECTLIST);
|
|
|
|
ConfigSingleColumnListView(hwndList);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
LRESULT CRootObjectPage::OnAddObject(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
|
|
{
|
|
HRESULT hr;
|
|
do
|
|
{
|
|
// Pass list of current classes, so wizard won't add one twice
|
|
string_vector vstrClasses;
|
|
for (EditObjIter itObj = m_ObjList.begin(); itObj != m_ObjList.end(); ++itObj)
|
|
{
|
|
if (!itObj->IsDeleted())
|
|
vstrClasses.push_back(itObj->Name());
|
|
}
|
|
|
|
CAddObjectWizard objWiz;
|
|
objWiz.Initialize(&vstrClasses, m_ObjList.RootNode()->GetCompData()->GetStringTable());
|
|
|
|
// Run the wizard
|
|
IPropertySheetProviderPtr spProvider = m_ObjList.RootNode()->GetCompData()->GetConsole();
|
|
if( spProvider == NULL ) return E_NOINTERFACE;
|
|
|
|
hr = objWiz.Run(spProvider, m_hWnd);
|
|
if (hr == S_OK)
|
|
{
|
|
ASSERT(objWiz.GetNewObject() != NULL);
|
|
if( !(objWiz.GetNewObject()) ) return E_FAIL;
|
|
|
|
EditObjIter itObj = m_ObjList.AddObject(objWiz.GetNewObject());
|
|
ASSERT(itObj != NULL);
|
|
if( itObj == NULL ) return E_FAIL;
|
|
|
|
LV_ITEM lvi;
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.iItem = 0;
|
|
lvi.iSubItem = 0;
|
|
|
|
lvi.pszText = const_cast<LPWSTR>(DisplayNames::GetClassMap()->GetAttributeDisplayName(itObj->Name()));
|
|
lvi.lParam = *(LPARAM*)&itObj; // NEED BETTER CONVERSION
|
|
|
|
int iPos = ListView_InsertItem(GetDlgItem(IDC_OBJECTLIST), &lvi);
|
|
ASSERT(iPos >= 0);
|
|
|
|
// if first item is added, select it
|
|
if (ListView_GetItemCount(GetDlgItem(IDC_OBJECTLIST)) == 1)
|
|
{
|
|
ListView_SetItemState(GetDlgItem(IDC_OBJECTLIST), 0, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
|
|
}
|
|
|
|
SetModified(TRUE);
|
|
}
|
|
}
|
|
while (FALSE);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
LRESULT CRootObjectPage::OnRemoveObject( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_OBJECTLIST);
|
|
|
|
BOOL bFirst = TRUE;
|
|
int iIndex = -1;
|
|
while ((iIndex = ListView_GetNextItem(hwndList, iIndex, LVNI_SELECTED)) != -1)
|
|
{
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iItem = iIndex;
|
|
lvi.iSubItem = 0;
|
|
|
|
BOOL bStat = ListView_GetItem(hwndList, &lvi);
|
|
ASSERT(bStat);
|
|
|
|
EditObjIter itObj = *(EditObjIter*)&lvi.lParam;
|
|
|
|
// get confirmation before deleting first object
|
|
if (bFirst)
|
|
{
|
|
bFirst = FALSE;
|
|
|
|
CString strTitle;
|
|
strTitle.LoadString(IDS_DELETEOBJ_TITLE);
|
|
|
|
CString strMsgFmt;
|
|
if (ListView_GetSelectedCount(hwndList) == 1)
|
|
strMsgFmt.LoadString(IDS_DELETEOBJ);
|
|
else
|
|
strMsgFmt.LoadString(IDS_DELETEOBJS);
|
|
|
|
WCHAR szName[MAX_PATH];
|
|
ListView_GetItemText(hwndList, iIndex, 0, szName, sizeof(szName));
|
|
|
|
CString strMsg;
|
|
strMsg.Format(strMsgFmt, szName);
|
|
|
|
if (::MessageBox(m_hWnd, strMsg, strTitle, MB_YESNO|MB_ICONWARNING) != IDYES)
|
|
return 0;
|
|
}
|
|
|
|
m_ObjList.DeleteObject(itObj);
|
|
|
|
ListView_DeleteItem(hwndList, iIndex);
|
|
|
|
// backup index because it now points to the next item
|
|
iIndex--;
|
|
|
|
SetModified(TRUE);
|
|
}
|
|
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEOBJECT, FALSE );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
LRESULT CRootObjectPage::OnObjListChanged(int idCtrl, LPNMHDR pNMHDR, BOOL& bHandled)
|
|
{
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEOBJECT, ListView_GetSelectedCount(GetDlgItem(IDC_OBJECTLIST)) );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL CRootObjectPage::OnSetActive()
|
|
{
|
|
HWND hwndList = GetDlgItem(IDC_OBJECTLIST);
|
|
|
|
ListView_DeleteAllItems(hwndList);
|
|
|
|
LV_ITEM lvi;
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.iItem = 0;
|
|
lvi.iSubItem = 0;
|
|
|
|
DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
|
|
ASSERT(pNameMap != NULL);
|
|
if (pNameMap == NULL)
|
|
return TRUE;
|
|
|
|
for (EditObjIter itObj = m_ObjList.begin(); itObj != m_ObjList.end(); ++itObj)
|
|
{
|
|
if (!itObj->IsDeleted())
|
|
{
|
|
lvi.pszText = const_cast<LPWSTR>(pNameMap->GetAttributeDisplayName(itObj->Name()));
|
|
lvi.lParam = *(LPARAM*)&itObj; // NEED BETTER CONVERSION
|
|
|
|
int iPos = ListView_InsertItem(hwndList, &lvi);
|
|
ASSERT(iPos >= 0);
|
|
}
|
|
}
|
|
|
|
// if items are added, select the first
|
|
if (ListView_GetItemCount(hwndList) > 0)
|
|
{
|
|
ListView_SetItemState(hwndList, 0, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
|
|
}
|
|
|
|
EnableDlgItem( m_hWnd, IDC_REMOVEOBJECT, FALSE );
|
|
|
|
m_ObjList.PageActive(m_hWnd);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL CRootObjectPage::OnApply()
|
|
{
|
|
return m_ObjList.ApplyChanges(m_hWnd);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// CRootViewPage
|
|
|
|
CRootViewPage::~CRootViewPage()
|
|
{
|
|
m_ObjectCB.Detach();
|
|
m_ColumnLV.Detach();
|
|
|
|
m_ObjList.Release();
|
|
}
|
|
|
|
|
|
LRESULT CRootViewPage::OnInitDialog( UINT mMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled )
|
|
{
|
|
m_ObjectCB.Attach(GetDlgItem(IDC_OBJECTLIST));
|
|
m_ColumnLV.Attach(GetDlgItem(IDC_COLUMNLIST));
|
|
|
|
m_itObjSelect = NULL;
|
|
|
|
::ConfigSingleColumnListView(GetDlgItem(IDC_COLUMNLIST));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
LRESULT CRootViewPage::OnObjectSelect( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
int iItem = m_ObjectCB.GetCurSel();
|
|
|
|
// Double-clicking an empty combo box can call this with no selection
|
|
if (iItem >= 0)
|
|
{
|
|
void* pv = m_ObjectCB.GetItemDataPtr(iItem);
|
|
m_itObjSelect = *(EditObjIter*)&pv;
|
|
m_strObjSelect = m_itObjSelect->Name();
|
|
|
|
DisplayColumns();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CRootViewPage::OnColumnListChanged(int idCtrl, LPNMHDR pNMHDR, BOOL& bHandled)
|
|
{
|
|
EnableDlgItem( m_hWnd, IDC_REMOVECOLUMN, ListView_GetSelectedCount(GetDlgItem(IDC_COLUMNLIST)) );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void CRootViewPage::DisplayColumns()
|
|
{
|
|
HWND hwndLV = GetDlgItem(IDC_COLUMNLIST);
|
|
ASSERT(::IsWindow(hwndLV));
|
|
|
|
ListView_DeleteAllItems(hwndLV);
|
|
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
CClassInfo& classInfo = m_itObjSelect->GetObject();
|
|
|
|
DisplayNameMap* pNameMap = DisplayNames::GetMap(classInfo.Name());
|
|
if( !pNameMap ) return;
|
|
|
|
LV_ITEM lvi;
|
|
lvi.mask = LVIF_TEXT | LVIF_PARAM;
|
|
lvi.iItem = 0;
|
|
lvi.iSubItem = 0;
|
|
|
|
string_vector::iterator itStr;
|
|
for (itStr = classInfo.Columns().begin(); itStr != classInfo.Columns().end(); ++itStr)
|
|
{
|
|
lvi.pszText = const_cast<LPWSTR>(pNameMap->GetAttributeDisplayName(itStr->c_str()));
|
|
lvi.lParam = reinterpret_cast<LPARAM>(itStr->c_str());
|
|
|
|
int iPos = ListView_InsertItem(hwndLV, &lvi);
|
|
ASSERT(iPos >= 0);
|
|
}
|
|
|
|
// if items are added, select the first
|
|
if (ListView_GetItemCount(hwndLV) > 0)
|
|
{
|
|
ListView_SetItemState(hwndLV, 0, LVIS_FOCUSED|LVIS_SELECTED, LVIS_FOCUSED|LVIS_SELECTED);
|
|
}
|
|
}
|
|
|
|
EnableDlgItem( m_hWnd, IDC_ADDCOLUMN, (m_itObjSelect != NULL) );
|
|
EnableDlgItem( m_hWnd, IDC_REMOVECOLUMN, FALSE );
|
|
}
|
|
|
|
|
|
LRESULT CRootViewPage::OnAddColumn( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
// if object is selected
|
|
if (m_itObjSelect == NULL)
|
|
return 0;
|
|
|
|
CAddColumnDlg dlg(m_itObjSelect->Name());
|
|
|
|
if (dlg.DoModal() == IDOK)
|
|
{
|
|
BOOL bModified = FALSE;
|
|
|
|
CClassInfo* pClassInfo = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfo ) return 0;
|
|
|
|
HWND hwndList = GetDlgItem(IDC_COLUMNLIST);
|
|
ASSERT(hwndList != NULL);
|
|
|
|
string_vector::iterator itStr = dlg.GetColumns().begin();
|
|
while (itStr != dlg.GetColumns().end())
|
|
{
|
|
if (std::find(pClassInfo->Columns().begin(), pClassInfo->Columns().end(), *itStr) == pClassInfo->Columns().end())
|
|
{
|
|
pClassInfo->Columns().push_back(*itStr);
|
|
bModified = TRUE;
|
|
}
|
|
++itStr;
|
|
}
|
|
|
|
if (bModified)
|
|
{
|
|
SetModified(TRUE);
|
|
DisplayColumns();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT CRootViewPage::OnRemoveColumn( WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled )
|
|
{
|
|
if (m_itObjSelect == NULL)
|
|
return 0;
|
|
|
|
HWND hwndList = GetDlgItem(IDC_COLUMNLIST);
|
|
ASSERT(hwndList != NULL);
|
|
|
|
UINT uiMsg = (ListView_GetSelectedCount(hwndList) == 1) ? IDS_PROP_REMOVE_ONE : IDS_PROP_REMOVE;
|
|
int iRet = DisplayMessageBox(m_hWnd, IDS_PROP_REMOVE_TITLE, uiMsg, MB_YESNO|MB_ICONWARNING);
|
|
if (iRet != IDYES)
|
|
return 0;
|
|
|
|
CClassInfo* pClassInfo = m_itObjSelect->GetModifiedObject();
|
|
if( !pClassInfo ) return 0;
|
|
|
|
string_vector vstrTmp = pClassInfo->Columns();
|
|
|
|
LVITEM lvi;
|
|
lvi.mask = LVIF_PARAM;
|
|
lvi.iSubItem = 0;
|
|
|
|
int iIndex = -1;
|
|
while ((iIndex = ListView_GetNextItem(hwndList, iIndex, LVNI_SELECTED)) >= 0)
|
|
{
|
|
lvi.iItem = iIndex;
|
|
ListView_GetItem(hwndList, &lvi);
|
|
LPCWSTR pszName = reinterpret_cast<LPCWSTR>(lvi.lParam);
|
|
|
|
string_vector::iterator itStr;
|
|
itStr = std::find(vstrTmp.begin(), vstrTmp.end(), pszName);
|
|
ASSERT(itStr != vstrTmp.end());
|
|
|
|
if( itStr != vstrTmp.end() )
|
|
{
|
|
vstrTmp.erase(itStr);
|
|
}
|
|
}
|
|
|
|
pClassInfo->Columns() = vstrTmp;
|
|
|
|
DisplayColumns();
|
|
|
|
SetModified(TRUE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL CRootViewPage::OnSetActive()
|
|
{
|
|
m_ObjList.PageActive(m_hWnd);
|
|
|
|
LoadObjectCB(m_ObjectCB, m_ObjList);
|
|
|
|
// if object was previously selected
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
// find the edit object by name because it may have been moved or deleted
|
|
// while another page was active
|
|
m_itObjSelect = m_ObjList.FindObject(m_strObjSelect.c_str());
|
|
|
|
if (m_itObjSelect != NULL && m_itObjSelect->IsDeleted())
|
|
m_itObjSelect = NULL;
|
|
}
|
|
|
|
// if object still around, reselect it in the combo box
|
|
if (m_itObjSelect != NULL)
|
|
{
|
|
DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
|
|
ASSERT(pNameMap != NULL);
|
|
|
|
int iSel = m_ObjectCB.FindStringExact(-1, pNameMap->GetAttributeDisplayName(m_strObjSelect.c_str()));
|
|
ASSERT(iSel != CB_ERR);
|
|
|
|
m_ObjectCB.SetCurSel(iSel);
|
|
}
|
|
else if (m_ObjectCB.GetCount() > 0)
|
|
{
|
|
// default to the first object and update the columns
|
|
m_ObjectCB.SetCurSel(0);
|
|
|
|
void* pv = m_ObjectCB.GetItemDataPtr(0);
|
|
m_itObjSelect = *(EditObjIter*)&pv;
|
|
|
|
m_strObjSelect = m_itObjSelect->Name();
|
|
|
|
DisplayColumns();
|
|
}
|
|
else
|
|
{
|
|
DisplayColumns();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL CRootViewPage::OnApply()
|
|
{
|
|
return m_ObjList.ApplyChanges(m_hWnd);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// CEditObjList
|
|
|
|
HRESULT CEditObjList::Initialize(CRootNode* pRootNode, classInfo_vector& vClasses, LONG_PTR lNotifyHandle)
|
|
{
|
|
ASSERT(pRootNode != NULL);
|
|
ASSERT(lNotifyHandle != NULL);
|
|
|
|
m_spRootNode = pRootNode;
|
|
|
|
classInfo_vector::iterator itClass = vClasses.begin();
|
|
while(itClass != vClasses.end())
|
|
{
|
|
CEditObject* pObj = new CEditObject();
|
|
if( !pObj )
|
|
{
|
|
break;
|
|
}
|
|
EditObjIter iter = m_ObjectList.insert(end(), *pObj);
|
|
|
|
iter->m_strName = itClass->Name();
|
|
iter->m_itObjOriginal = itClass;
|
|
|
|
++itClass;
|
|
}
|
|
|
|
m_pvClasses = &vClasses;
|
|
|
|
m_iPageMax = -1;
|
|
|
|
m_lNotifyHandle = lNotifyHandle;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
BOOL CEditObjList::ApplyChanges(HWND hwndPage)
|
|
{
|
|
ASSERT(::IsWindow(hwndPage));
|
|
|
|
// Don't apply changes until called from highest activated page
|
|
if (PropSheet_HwndToIndex(GetParent(hwndPage), hwndPage) < m_iPageMax)
|
|
return TRUE;
|
|
|
|
// Build a vector of the modified classes
|
|
string_vector* pvstrModified = new string_vector;
|
|
if( !pvstrModified ) return FALSE;
|
|
|
|
// Apply changes in reverse order so deletions won't invalidate stored iterators
|
|
std::list<CEditObject>::reverse_iterator itObj = m_ObjectList.rbegin();
|
|
while (itObj != m_ObjectList.rend())
|
|
{
|
|
// if object is modified, replace the original
|
|
if (itObj->m_itObjOriginal != NULL)
|
|
{
|
|
if (itObj->m_pObjModified != NULL)
|
|
{
|
|
*(itObj->m_itObjOriginal) = *(itObj->m_pObjModified);
|
|
pvstrModified->push_back(itObj->m_pObjModified->Name());
|
|
}
|
|
else if (itObj->IsDeleted())
|
|
{
|
|
m_pvClasses->erase(itObj->m_itObjOriginal);
|
|
pvstrModified->push_back(itObj->m_itObjOriginal->Name());
|
|
}
|
|
}
|
|
++itObj;
|
|
}
|
|
|
|
// Now go through list again to add any new objects
|
|
// This must be done separately because it can invalidate all stored iterators
|
|
itObj = m_ObjectList.rbegin();
|
|
while(itObj != m_ObjectList.rend())
|
|
{
|
|
if (itObj->m_itObjOriginal == NULL && itObj->m_pObjModified != NULL)
|
|
{
|
|
m_pvClasses->push_back(*(itObj->m_pObjModified));
|
|
pvstrModified->push_back(itObj->m_pObjModified->Name());
|
|
}
|
|
|
|
++itObj;
|
|
}
|
|
|
|
// clear the edit list and re-initialize it
|
|
m_ObjectList.clear();
|
|
|
|
Initialize(m_spRootNode, *m_pvClasses, m_lNotifyHandle);
|
|
|
|
// Send change notification to root node, so it can update affected child nodes
|
|
// Use MMC method to send from prop page thread to main thread
|
|
if (pvstrModified->size() != 0)
|
|
{
|
|
// create prop change info struct with root node's data interface
|
|
// and list of changed classes
|
|
PropChangeInfo* pChg = new PropChangeInfo;
|
|
if( !pChg )
|
|
{
|
|
delete pvstrModified;
|
|
return FALSE;
|
|
}
|
|
|
|
pChg->pDataObject = static_cast<IDataObject*>(m_spRootNode.p);
|
|
pChg->lNotifyParam = (LPARAM)pvstrModified;
|
|
|
|
MMCPropertyChangeNotify(m_lNotifyHandle, (LPARAM)pChg);
|
|
}
|
|
else
|
|
delete pvstrModified;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void CEditObjList::PageActive(HWND hwndPage)
|
|
{
|
|
ASSERT(::IsWindow(hwndPage));
|
|
|
|
// track the highest created page number for ApplyChanges method
|
|
int iPage = PropSheet_HwndToIndex(GetParent(hwndPage), hwndPage);
|
|
if (iPage > m_iPageMax)
|
|
m_iPageMax = iPage;
|
|
}
|
|
|
|
EditObjIter CEditObjList::FindObject(LPCWSTR pszName)
|
|
{
|
|
if( !pszName ) return NULL;
|
|
|
|
EditObjIter iter = begin();
|
|
while (iter != end())
|
|
{
|
|
// look for class object with matching name
|
|
if (wcscmp(iter->Name(), pszName) == 0)
|
|
{
|
|
// If the object hasn't been modified (copied) yet, then load any
|
|
// string table strings before returning the object
|
|
if (iter->m_itObjOriginal != NULL && iter->m_pObjModified == NULL)
|
|
{
|
|
ASSERT(m_spRootNode != NULL);
|
|
if( !m_spRootNode ) return NULL;
|
|
|
|
IStringTable* pStringTable = m_spRootNode->GetCompData()->GetStringTable();
|
|
ASSERT(pStringTable != NULL);
|
|
if( !pStringTable ) return NULL;
|
|
|
|
HRESULT hr = iter->m_itObjOriginal->LoadStrings(pStringTable);
|
|
}
|
|
return iter;
|
|
}
|
|
++iter;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
EditObjIter CEditObjList::AddObject(CClassInfo* pClassInfo)
|
|
{
|
|
if( !pClassInfo ) return NULL;
|
|
|
|
EditObjIter iter = begin();
|
|
while (iter != end())
|
|
{
|
|
// Check for exiting edit object (can be there if object was deleted)
|
|
if (wcscmp(iter->Name(), pClassInfo->Name()) == 0)
|
|
{
|
|
ASSERT(iter->m_bDeleted && iter->m_pObjModified == NULL);
|
|
iter->m_bDeleted = FALSE;
|
|
iter->m_pObjModified = pClassInfo;
|
|
|
|
return iter;
|
|
}
|
|
++iter;
|
|
}
|
|
|
|
// if not found, create new edit object and store new class info as the modified object
|
|
CEditObject* pObj = new CEditObject();
|
|
if( !pObj ) return iter;
|
|
|
|
iter = m_ObjectList.insert(end(), *pObj);
|
|
|
|
iter->m_strName = pClassInfo->Name();
|
|
iter->m_pObjModified = pClassInfo;
|
|
|
|
return iter;
|
|
}
|
|
|
|
void CEditObjList::DeleteObject(EditObjIter itObj)
|
|
{
|
|
ASSERT(itObj != NULL);
|
|
|
|
if (itObj->m_itObjOriginal == NULL)
|
|
{
|
|
m_ObjectList.erase(itObj);
|
|
}
|
|
else
|
|
{
|
|
if (itObj->m_pObjModified != NULL)
|
|
{
|
|
delete itObj->m_pObjModified;
|
|
itObj->m_pObjModified = NULL;
|
|
}
|
|
|
|
itObj->m_bDeleted = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
void LoadObjectCB(CComboBox& ComboBox, CEditObjList& ObjList)
|
|
{
|
|
|
|
ComboBox.ResetContent();
|
|
|
|
DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
|
|
ASSERT(pNameMap != NULL);
|
|
if (pNameMap == NULL)
|
|
return;
|
|
|
|
for (EditObjIter itObj = ObjList.begin(); itObj != ObjList.end(); ++itObj)
|
|
{
|
|
if (!itObj->IsDeleted())
|
|
{
|
|
int iIndex = ComboBox.AddString(pNameMap->GetAttributeDisplayName(itObj->Name()));
|
|
ASSERT(iIndex >= 0);
|
|
ComboBox.SetItemDataPtr(iIndex, *(LPVOID*)&itObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
int GetDateTimeString(FILETIME* pftime, LPWSTR pszBuf, int cBuf)
|
|
{
|
|
if( !pftime || !pszBuf || !cBuf ) return 0;
|
|
|
|
FILETIME ftimeLocal;
|
|
BOOL bStat = FileTimeToLocalFileTime(pftime, &ftimeLocal);
|
|
ASSERT(bStat);
|
|
|
|
SYSTEMTIME systime;
|
|
bStat = FileTimeToSystemTime(&ftimeLocal, &systime);
|
|
ASSERT(bStat);
|
|
|
|
// get date string
|
|
int cDate = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, NULL, pszBuf, cBuf);
|
|
if (cDate == 0 || cDate > cBuf - 2)
|
|
return 0;
|
|
|
|
// replace teminating null with ", "
|
|
pszBuf[cDate-1] = ',';
|
|
pszBuf[cDate] = ' ';
|
|
|
|
// append time string
|
|
int cTime = GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime, NULL,
|
|
pszBuf + (cDate + 1), cBuf - (cDate + 1));
|
|
|
|
if (cTime == 0)
|
|
return 0;
|
|
|
|
// return total string length excluding terminating null
|
|
return (cDate + cTime - 2);
|
|
}
|
|
|