Leaked source code of windows server 2003
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.

596 lines
22 KiB

  1. // PropPageExt.cpp : Implementation of CPropPageExt
  2. #include "stdafx.h"
  3. #include "DSAdminExt.h"
  4. #include "PropPageExt.h"
  5. #include "globals.h"
  6. #include <crtdbg.h>
  7. #include "Iads.h"
  8. #include "adsprop.h"
  9. #include "Adshlp.h"
  10. #include "resource.h"
  11. #include "winable.h"
  12. typedef struct
  13. {
  14. DWORD dwFlags; // item flags
  15. DWORD dwProviderFlags; // flags for item provider
  16. DWORD offsetName; // offset to ADS path of the object
  17. DWORD offsetClass; // offset to object class name / == 0 not known
  18. } DSOBJECT, * LPDSOBJECT;
  19. typedef struct
  20. {
  21. CLSID clsidNamespace; // namespace identifier (indicates which namespace selection from)
  22. UINT cItems; // number of objects
  23. DSOBJECT aObjects[1]; // array of objects
  24. } DSOBJECTNAMES, * LPDSOBJECTNAMES;
  25. #define BYTE_OFFSET(base, offset) (((LPBYTE)base)+offset)
  26. /////////////////////////////////////////////////////////////////////////////
  27. // CPropPageExt
  28. ///////////////////////////////
  29. // Interface IExtendPropertySheet
  30. ///////////////////////////////
  31. HRESULT CPropPageExt::CreatePropertyPages(
  32. /* [in] */ LPPROPERTYSHEETCALLBACK lpProvider,
  33. /* [in] */ LONG_PTR handle,
  34. /* [in] */ LPDATAOBJECT lpIDataObject)
  35. {
  36. HRESULT hr = S_FALSE;
  37. LPDSOBJECTNAMES pDsObjectNames;
  38. PWSTR pwzObjName;
  39. PWSTR pwzClass;
  40. // Unpack the data pointer and create the property page.
  41. // Register clipboard format
  42. FORMATETC fmte = { cfDsObjectNames, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  43. STGMEDIUM objMedium = {TYMED_NULL};;
  44. hr = lpIDataObject->GetData(&fmte, &objMedium);
  45. if (SUCCEEDED(hr))
  46. {
  47. pDsObjectNames = (LPDSOBJECTNAMES)objMedium.hGlobal;
  48. if (pDsObjectNames->cItems < 1)
  49. {
  50. hr = E_FAIL;
  51. }
  52. pwzObjName = (PWSTR)BYTE_OFFSET(pDsObjectNames,
  53. pDsObjectNames->aObjects[0].offsetName);
  54. pwzClass = (PWSTR)BYTE_OFFSET(pDsObjectNames,
  55. pDsObjectNames->aObjects[0].offsetClass);
  56. // Save the ADsPath of object
  57. m_ObjPath = new WCHAR [wcslen(pwzObjName )+1];
  58. wcscpy(m_ObjPath,pwzObjName);
  59. }
  60. // Now release the objMedium:
  61. // If punkForRelease is NULL, the receiver of
  62. // the medium is responsible for releasing it; otherwise,
  63. // punkForRelease points to the IUnknown on the appropriate
  64. // object so its Release method can be called.
  65. ReleaseStgMedium(&objMedium);
  66. PROPSHEETPAGE psp;
  67. HPROPSHEETPAGE hPage = NULL;
  68. hr = S_OK;
  69. //
  70. // Create a property sheet page object from a dialog box.
  71. //
  72. // We store a pointer to our class in the psp.lParam, so we
  73. // can access our class members from within the DSExtensionPageDlgProc.
  74. //
  75. // If the page needs more instance data, you can append
  76. // arbitrary size of data at the end of this structure,
  77. // and pass it to the CreatePropSheetPage. In such a case,
  78. // the size of entire data structure (including page specific
  79. // data) must be stored in the dwSize field. Note that in
  80. // general you should NOT need to do this, as you can simply
  81. // store a pointer to data in the lParam member.
  82. psp.dwSize = sizeof(PROPSHEETPAGE);
  83. psp.dwFlags = PSP_DEFAULT | PSP_USETITLE;
  84. psp.hInstance = g_hinst;
  85. psp.pszTemplate = MAKEINTRESOURCE(IDD_DSExtensionPageGen);
  86. psp.pfnDlgProc = DSExtensionPageDlgProc;
  87. psp.lParam = reinterpret_cast<LPARAM>(this);
  88. psp.pszTitle = MAKEINTRESOURCE(IDS_PROPPAGE_TITLE);
  89. hPage = CreatePropertySheetPage(&psp);
  90. _ASSERT(hPage);
  91. hr = lpProvider->AddPage(hPage);
  92. return hr;
  93. }
  94. HRESULT CPropPageExt::QueryPagesFor(
  95. /* [in] */ LPDATAOBJECT lpDataObject)
  96. {
  97. return S_OK;
  98. }
  99. BOOL CALLBACK CPropPageExt::DSExtensionPageDlgProc(HWND hDlg,
  100. UINT uMessage,
  101. WPARAM wParam,
  102. LPARAM lParam)
  103. {
  104. static CPropPageExt *pThis = NULL;
  105. static bool b_IsDirty = FALSE;
  106. switch (uMessage)
  107. {
  108. ////////////////////////////////////////////////////////////////////////////////
  109. //WM_INITDIALOG handler
  110. ////////////////////////////////////////////////////////////////////////////////
  111. case WM_INITDIALOG:
  112. {
  113. pThis = reinterpret_cast<CPropPageExt *>(reinterpret_cast<PROPSHEETPAGE *>(lParam)->lParam);
  114. //Set to value of m_hPropPageWnd to the HWND of the our property page dialog
  115. pThis->m_hPropPageWnd = hDlg;
  116. HRESULT hr;
  117. IDirectoryObject* pDirObject = NULL;
  118. hr = ADsGetObject( pThis->m_ObjPath, IID_IDirectoryObject,(void **)&pDirObject);
  119. if (SUCCEEDED(hr))
  120. {
  121. //Retrieve some general info about the current user object.
  122. //We use our IDirectoryObject pointer here.
  123. ADS_ATTR_INFO *pAttrInfo=NULL;
  124. ADS_ATTR_INFO *pAttrInfo1=NULL;
  125. DWORD dwReturn, dwReturn1;
  126. LPWSTR pAttrNames[]={L"employeeID", L"mail", L"physicalDeliveryOfficeName", L"telephoneNumber"};
  127. LPWSTR pAttrNames1[]={L"allowedAttributesEffective"};
  128. DWORD dwNumAttr=sizeof(pAttrNames)/sizeof(LPWSTR);
  129. DWORD dwNumAttr1=sizeof(pAttrNames1)/sizeof(LPWSTR);
  130. /////////////////////////////////////////
  131. // First get the allowedAttributesEffective
  132. // attribute value. We use it to determine
  133. // whether we have the proper permissions
  134. // to modify the attributes of the current
  135. // object. If we have the needed
  136. // permissions, we enable the edit fields.
  137. // (They're disabled by default.)
  138. ///////////////////////////////////////////
  139. bool b_allowEmployeeChange = FALSE;
  140. bool b_allowMailChange = FALSE;
  141. bool b_allowOfficeChange = FALSE;
  142. bool b_allowTelNumberChange = FALSE;
  143. hr = pDirObject->GetObjectAttributes( pAttrNames1,
  144. dwNumAttr1,
  145. &pAttrInfo1,
  146. &dwReturn1 );
  147. if ( SUCCEEDED(hr) )
  148. {
  149. //The call can succeed with no attributes returned if you lack privilege,
  150. //so check that all attributes are returned.
  151. if (dwReturn1 && pAttrInfo1 && pAttrInfo1->pszAttrName &&
  152. _wcsicmp(pAttrInfo1->pszAttrName,L"allowedAttributesEffective")== 0)
  153. {
  154. if (ADSTYPE_INVALID != pAttrInfo1->dwADsType)
  155. {
  156. //Permissions are per-attribute, so you need to check
  157. //if the attribute name is in the array of names returned
  158. //by the read of allowedAttributesEffective.
  159. //The attributes we are interested in modifying are:
  160. //employeeID, mail, physicalDeliveryOfficeName, telephoneNumber
  161. for (DWORD i = 0; i < pAttrInfo1->dwNumValues; i++)
  162. {
  163. if (_tcscmp(L"employeeID", pAttrInfo1->pADsValues[i].CaseIgnoreString) == 0)
  164. b_allowEmployeeChange = TRUE;
  165. else if (_tcscmp(L"mail", pAttrInfo1->pADsValues[i].CaseIgnoreString) == 0)
  166. b_allowMailChange = TRUE;
  167. else if (_tcscmp(L"physicalDeliveryOfficeName", pAttrInfo1->pADsValues[i].CaseIgnoreString) == 0)
  168. b_allowOfficeChange = TRUE;
  169. else if (_tcscmp(L"telephoneNumber", pAttrInfo1->pADsValues[i].CaseIgnoreString) == 0)
  170. b_allowTelNumberChange = TRUE;
  171. }
  172. }
  173. }
  174. }
  175. //For loop for setting default value of text controls
  176. //This makes use of the fact that the ID values of the
  177. //text controls are sequential. We use the value of
  178. //b_allowChanges to determine whether we enable editing or not
  179. for (int i = IDC_EMPID; i <= IDC_TELNUMBER; i++)
  180. {
  181. SetWindowText(GetDlgItem(hDlg,i),L"<not set>");
  182. if (IDC_EMPID == i && b_allowEmployeeChange)
  183. EnableWindow(GetDlgItem(hDlg, i), TRUE);
  184. else if (IDC_EMAIL == i && b_allowMailChange)
  185. EnableWindow(GetDlgItem(hDlg, i), TRUE);
  186. else if (IDC_OFFICE == i && b_allowOfficeChange)
  187. EnableWindow(GetDlgItem(hDlg, i), TRUE);
  188. else if (IDC_TELNUMBER == i && b_allowTelNumberChange)
  189. EnableWindow(GetDlgItem(hDlg, i), TRUE);
  190. }
  191. /////////////////////////////////////////////////////////////
  192. // Use FreeADsMem for all memory obtained from the ADSI call.
  193. /////////////////////////////////////////////////////////////
  194. if (pAttrInfo1)
  195. FreeADsMem( pAttrInfo1 );
  196. /////////////////////////////////////////
  197. // Now get attribute values requested
  198. // Note: The order is not necessarily the
  199. // same as requested using pAttrNames.
  200. ///////////////////////////////////////////
  201. hr = pDirObject->GetObjectAttributes( pAttrNames,
  202. dwNumAttr,
  203. &pAttrInfo,
  204. &dwReturn );
  205. if ( SUCCEEDED(hr) )
  206. {
  207. //Fill values of text controls with information taken from
  208. //object attributes
  209. for(DWORD idx=0; idx < dwReturn;idx++, pAttrInfo++ )
  210. {
  211. if (_wcsicmp(pAttrInfo->pszAttrName,L"employeeID") == 0 &&
  212. pAttrInfo->pADsValues->CaseIgnoreString != '\0')
  213. {
  214. SetWindowText(GetDlgItem(hDlg,IDC_EMPID),pAttrInfo->pADsValues->CaseIgnoreString);
  215. }
  216. else if (_wcsicmp(pAttrInfo->pszAttrName,L"mail") == 0 &&
  217. pAttrInfo->pADsValues->CaseIgnoreString != '\0')
  218. {
  219. SetWindowText(GetDlgItem(hDlg,IDC_EMAIL),pAttrInfo->pADsValues->CaseIgnoreString);
  220. }
  221. else if (_wcsicmp(pAttrInfo->pszAttrName,L"physicalDeliveryOfficeName") == 0 &&
  222. pAttrInfo->pADsValues->CaseIgnoreString != '\0')
  223. {
  224. SetWindowText(GetDlgItem(hDlg,IDC_OFFICE),pAttrInfo->pADsValues->CaseIgnoreString);
  225. }
  226. else if (_wcsicmp(pAttrInfo->pszAttrName,L"telephoneNumber") == 0 &&
  227. pAttrInfo->pADsValues->CaseIgnoreString != '\0')
  228. {
  229. SetWindowText(GetDlgItem(hDlg,IDC_TELNUMBER),pAttrInfo->pADsValues->CaseIgnoreString);
  230. }
  231. }
  232. }
  233. //Release our IDirectoryObject interface
  234. pDirObject->Release();
  235. /////////////////////////////////////////////////////////////
  236. // Use FreeADsMem for all memory obtained from the ADSI call.
  237. /////////////////////////////////////////////////////////////
  238. if (pAttrInfo)
  239. //First subtract pointer increments in the above for loop to our original pAttrInfo
  240. pAttrInfo = pAttrInfo-(dwReturn);
  241. FreeADsMem(pAttrInfo);
  242. return TRUE;
  243. }
  244. } //WM_INITDIALOG
  245. break;
  246. ////////////////////////////////////////////////////////////////////////////////
  247. ////////////////////////////////////////////////////////////////////////////////
  248. //WM_NOTIFY handler
  249. ////////////////////////////////////////////////////////////////////////////////
  250. case WM_NOTIFY:
  251. switch (((NMHDR FAR *)lParam)->code)
  252. {
  253. //We use this notification to enable the Advanced button on the general page
  254. case PSN_SETACTIVE:
  255. if (!pThis->m_hDlgModeless)
  256. EnableWindow(GetDlgItem(hDlg, IDC_BUTTONADV), TRUE);
  257. return TRUE;
  258. break;
  259. /////////////////////////////////////////////////////////////////
  260. //PSN_APPLY handler
  261. /////////////////////////////////////////////////////////////////
  262. case PSN_APPLY:
  263. {
  264. if(b_IsDirty)
  265. {
  266. IDirectoryObject* pDirObject = NULL;
  267. HRESULT hr = ADsGetObject(pThis->m_ObjPath, IID_IDirectoryObject,(void **)&pDirObject);
  268. _ASSERT(SUCCEEDED(hr));
  269. //Apply changes user made here
  270. WCHAR empID[128];
  271. WCHAR email[128];
  272. WCHAR office[128];
  273. WCHAR telnumber[128];
  274. DWORD dwReturn;
  275. ADSVALUE snEmpID, snEmail, snOffice, snTelnumber;
  276. ADS_ATTR_INFO attrInfo[] = { {L"employeeID",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&snEmpID,1},
  277. {L"mail",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&snEmail,1},
  278. {L"physicalDeliveryOfficeName",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&snOffice,1},
  279. {L"telephoneNumber",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&snTelnumber,1},
  280. };
  281. DWORD dwAttrs = 0;
  282. dwAttrs = sizeof(attrInfo)/sizeof(ADS_ATTR_INFO);
  283. GetWindowText(GetDlgItem(hDlg, IDC_EMPID), empID, sizeof(empID));
  284. GetWindowText(GetDlgItem(hDlg, IDC_EMAIL), email, sizeof(email));
  285. GetWindowText(GetDlgItem(hDlg, IDC_OFFICE), office, sizeof(office));
  286. GetWindowText(GetDlgItem(hDlg, IDC_TELNUMBER), telnumber, sizeof(telnumber));
  287. snEmpID.dwType=ADSTYPE_CASE_IGNORE_STRING;
  288. snEmpID.CaseIgnoreString = empID;
  289. snEmail.dwType=ADSTYPE_CASE_IGNORE_STRING;
  290. snEmail.CaseIgnoreString = email;
  291. snOffice.dwType=ADSTYPE_CASE_IGNORE_STRING;
  292. snOffice.CaseIgnoreString = office;
  293. snTelnumber.dwType=ADSTYPE_CASE_IGNORE_STRING;
  294. snTelnumber.CaseIgnoreString = telnumber;
  295. hr = pDirObject->SetObjectAttributes(attrInfo, dwAttrs, &dwReturn);
  296. if (SUCCEEDED(hr))
  297. MessageBox(hDlg,
  298. L"Changes accepted",
  299. L"Changes to Object Attributes",
  300. MB_OK | MB_ICONEXCLAMATION);
  301. else
  302. MessageBox(hDlg,
  303. L"Some or all changes were rejected\nby the directory service.",
  304. L"Changes to Object Attributes",
  305. MB_OK | MB_ICONWARNING);
  306. //Release our IDirectoryObject interface
  307. pDirObject->Release();
  308. b_IsDirty = FALSE;
  309. }
  310. //No user changes. Property sheet will go down, so
  311. //first check if our property page's child dialog is open
  312. else if (pThis->m_hDlgModeless)
  313. PostMessage(pThis->m_hDlgModeless, WM_CLOSE, wParam, lParam);
  314. return TRUE;
  315. break; //PSN_APPLY
  316. }
  317. /////////////////////////////////////////////////////////////////
  318. /////////////////////////////////////////////////////////////////
  319. //PSN_QUERYCANCEL handler
  320. /////////////////////////////////////////////////////////////////
  321. case PSN_QUERYCANCEL:
  322. if (pThis->m_hDlgModeless)
  323. //The property page's child window is still open, so
  324. //we need to close it first.
  325. PostMessage(pThis->m_hDlgModeless, WM_CLOSE, wParam, lParam);
  326. return TRUE;
  327. break; //PSN_QUERYCANCEL
  328. /////////////////////////////////////////////////////////////////
  329. default:
  330. return FALSE;
  331. break;
  332. } //end switch (((NMHDR FAR *)lParam)->code)
  333. break; //WM_NOTIFY
  334. ////////////////////////////////////////////////////////////////////////////////
  335. ////////////////////////////////////////////////////////////////////////////////
  336. //WM_COMMAND handler
  337. ////////////////////////////////////////////////////////////////////////////////
  338. case WM_COMMAND:
  339. switch (LOWORD(wParam))
  340. {
  341. /////////////////////////////////////////////////////////////////
  342. //IDC_EMPID, IDC_EMPID, IDC_OFFICE, IDC_TELNUMBER handler
  343. /////////////////////////////////////////////////////////////////
  344. case IDC_EMPID:
  345. case IDC_EMAIL:
  346. case IDC_OFFICE:
  347. case IDC_TELNUMBER:
  348. if (EN_CHANGE == HIWORD(wParam) &&
  349. SendMessage(GetDlgItem(hDlg, LOWORD(wParam)),EM_GETMODIFY,0,0))
  350. {
  351. //Activate Apply button
  352. SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
  353. //Set b_IsDirty to TRUE, indicating that user changes have occurred
  354. //Used in handling PSN_APPLY
  355. b_IsDirty = TRUE;
  356. }
  357. return TRUE;
  358. break;
  359. /////////////////////////////////////////////////////////////////
  360. /////////////////////////////////////////////////////////////////
  361. //IDC_BUTTONADV handler
  362. /////////////////////////////////////////////////////////////////
  363. case IDC_BUTTONADV:
  364. {
  365. //Disable Advanced button so that more than one child dialog
  366. //can't be available at a given time
  367. EnableWindow(GetDlgItem(hDlg, IDC_BUTTONADV), FALSE);
  368. //Create a secondary dialog
  369. pThis->m_hDlgModeless = CreateDialogParam(g_hinst, MAKEINTRESOURCE(IDD_DSExtensionPage),
  370. hDlg, AdvDialogProc, reinterpret_cast<LPARAM>(pThis));
  371. return TRUE;
  372. }
  373. break; //IDC_BUTTONADV
  374. /////////////////////////////////////////////////////////////////
  375. } // end switch
  376. break; //WM_COMMAND
  377. ////////////////////////////////////////////////////////////////////////////////
  378. ////////////////////////////////////////////////////////////////////////////////
  379. //WM_MODELESSDLGCLOSED handler (custom window message)
  380. ////////////////////////////////////////////////////////////////////////////////
  381. case WM_MODELESSDLGCLOSED:
  382. //Enable Advanced button again
  383. EnableWindow(GetDlgItem(hDlg, IDC_BUTTONADV), TRUE);
  384. return TRUE;
  385. break;
  386. ////////////////////////////////////////////////////////////////////////////////
  387. ////////////////////////////////////////////////////////////////////////////////
  388. //WM_DESTROY handler
  389. ////////////////////////////////////////////////////////////////////////////////
  390. case WM_DESTROY:
  391. pThis->m_hPropPageWnd = NULL;
  392. RemoveProp(hDlg, L"ID");
  393. return TRUE;
  394. break;
  395. ////////////////////////////////////////////////////////////////////////////////
  396. default:
  397. return FALSE;
  398. break;
  399. } //
  400. return TRUE;
  401. }
  402. BOOL CALLBACK CPropPageExt::AdvDialogProc(HWND hDlg,
  403. UINT uMessage,
  404. WPARAM wParam,
  405. LPARAM lParam)
  406. {
  407. static CPropPageExt *pThis = NULL;
  408. switch (uMessage)
  409. {
  410. ////////////////////////////////////////////////////////////////////////////////
  411. //WM_INITDIALOG handler
  412. ////////////////////////////////////////////////////////////////////////////////
  413. case WM_INITDIALOG:
  414. {
  415. BSTR bsResult;
  416. pThis = reinterpret_cast<CPropPageExt *>(lParam);
  417. HRESULT hr;
  418. IADs* pIADs = NULL;
  419. hr = ADsGetObject( pThis->m_ObjPath, IID_IADs,(void **)&pIADs);
  420. if (SUCCEEDED(hr))
  421. {
  422. // Retrieves the GUID for this object- The guid uniquely identifies
  423. // this directory object. The Guid is globally unique
  424. // Also the guid is rename/move safe. The ADsPath below returns the
  425. // CURRENT location of the object- The guid remains constant regardless of
  426. // name or location of the directory object
  427. pIADs->get_GUID(&bsResult);
  428. SetWindowText(GetDlgItem(hDlg,IDC_GUID),bsResult);
  429. SysFreeString(bsResult);
  430. // Retrieves the RDN
  431. pIADs->get_Name(&bsResult);
  432. SetWindowText(GetDlgItem(hDlg,IDC_NAME),bsResult);
  433. SysFreeString(bsResult);
  434. // Retrieves the value in the class attribute, that is, group
  435. pIADs->get_Class(&bsResult);
  436. SetWindowText(GetDlgItem(hDlg,IDC_CLASS),bsResult);
  437. SysFreeString(bsResult);
  438. // Retrieves the full literal LDAP path for this object.
  439. // This may be used to re-bind to this object- though for persistent
  440. // storage (and to be 'move\rename' safe) it is suggested that the
  441. // guid be used instead of the ADsPath
  442. pIADs->get_ADsPath(&bsResult);
  443. SetWindowText(GetDlgItem(hDlg,IDC_ADSPATH),bsResult);
  444. SysFreeString(bsResult);
  445. // Retrieves the LDAP path for the parent\container for this object
  446. pIADs->get_Parent(&bsResult);
  447. SetWindowText(GetDlgItem(hDlg,IDC_PARENT),bsResult);
  448. SysFreeString(bsResult);
  449. // Retrieves the LDAP path for the Schema definition of the object returned from
  450. /// the IADs::get_Schema() member
  451. pIADs->get_Schema(&bsResult);
  452. SetWindowText(GetDlgItem(hDlg,IDC_SCHEMA),bsResult);
  453. SysFreeString(bsResult);
  454. pIADs->Release();
  455. pIADs = NULL;
  456. }
  457. return TRUE;
  458. }
  459. break;
  460. ////////////////////////////////////////////////////////////////////////////////
  461. ////////////////////////////////////////////////////////////////////////////////
  462. //WM_COMMAND handler
  463. ////////////////////////////////////////////////////////////////////////////////
  464. case WM_COMMAND:
  465. switch (LOWORD (wParam))
  466. {
  467. case IDOK :
  468. PostMessage(hDlg, WM_CLOSE, wParam, lParam);
  469. return TRUE;
  470. break;
  471. }
  472. break;
  473. ////////////////////////////////////////////////////////////////////////////////
  474. ////////////////////////////////////////////////////////////////////////////////
  475. //WM_CLOSE handler
  476. ////////////////////////////////////////////////////////////////////////////////
  477. case WM_CLOSE:
  478. DestroyWindow (hDlg);
  479. SendMessage(pThis->m_hPropPageWnd, WM_MODELESSDLGCLOSED, (WPARAM)hDlg, 0);
  480. pThis->m_hDlgModeless = NULL;
  481. return TRUE;
  482. break;
  483. }//end switch (uMessage)
  484. ////////////////////////////////////////////////////////////////////////////////
  485. return FALSE ;
  486. }