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.

799 lines
20 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
  6. * Copyright (C) 1992-2017 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. /**
  26. * @file sch_sheet_path.cpp
  27. * @brief SCH_SHEET_PATH class implementation.
  28. */
  29. #include <fctsys.h>
  30. #include <dlist.h>
  31. #include <sch_screen.h>
  32. #include <sch_item_struct.h>
  33. #include <sch_reference_list.h>
  34. #include <class_library.h>
  35. #include <sch_sheet_path.h>
  36. #include <sch_component.h>
  37. #include <sch_sheet.h>
  38. #include <template_fieldnames.h>
  39. #include <dialogs/dialog_schematic_find.h>
  40. #include <boost/functional/hash.hpp>
  41. #include <wx/filename.h>
  42. namespace std
  43. {
  44. size_t hash<SCH_SHEET_PATH>::operator()( const SCH_SHEET_PATH& path ) const
  45. {
  46. return path.GetCurrentHash();
  47. }
  48. }
  49. SCH_SHEET_PATH::SCH_SHEET_PATH()
  50. {
  51. m_pageNumber = 0;
  52. m_current_hash = 0;
  53. }
  54. void SCH_SHEET_PATH::Rehash()
  55. {
  56. m_current_hash = 0;
  57. for( auto sheet : m_sheets )
  58. boost::hash_combine( m_current_hash, sheet->GetTimeStamp() );
  59. }
  60. int SCH_SHEET_PATH::Cmp( const SCH_SHEET_PATH& aSheetPathToTest ) const
  61. {
  62. if( size() > aSheetPathToTest.size() )
  63. return 1;
  64. if( size() < aSheetPathToTest.size() )
  65. return -1;
  66. //otherwise, same number of sheets.
  67. for( unsigned i = 0; i < size(); i++ )
  68. {
  69. if( at( i )->GetTimeStamp() > aSheetPathToTest.at( i )->GetTimeStamp() )
  70. return 1;
  71. if( at( i )->GetTimeStamp() < aSheetPathToTest.at( i )->GetTimeStamp() )
  72. return -1;
  73. }
  74. return 0;
  75. }
  76. SCH_SHEET* SCH_SHEET_PATH::Last() const
  77. {
  78. if( !empty() )
  79. return at( size() - 1 );
  80. return NULL;
  81. }
  82. SCH_SCREEN* SCH_SHEET_PATH::LastScreen() const
  83. {
  84. SCH_SHEET* lastSheet = Last();
  85. if( lastSheet )
  86. return lastSheet->GetScreen();
  87. return NULL;
  88. }
  89. SCH_ITEM* SCH_SHEET_PATH::LastDrawList() const
  90. {
  91. SCH_SHEET* lastSheet = Last();
  92. if( lastSheet && lastSheet->GetScreen() )
  93. return lastSheet->GetScreen()->GetDrawItems();
  94. return NULL;
  95. }
  96. SCH_ITEM* SCH_SHEET_PATH::FirstDrawList() const
  97. {
  98. SCH_ITEM* item = NULL;
  99. if( !empty() && at( 0 )->GetScreen() )
  100. item = at( 0 )->GetScreen()->GetDrawItems();
  101. /* @fixme - These lists really should be one of the boost pointer containers. This
  102. * is a brain dead hack to allow reverse iteration of EDA_ITEM linked
  103. * list.
  104. */
  105. SCH_ITEM* lastItem = NULL;
  106. while( item )
  107. {
  108. lastItem = item;
  109. item = item->Next();
  110. }
  111. return lastItem;
  112. }
  113. wxString SCH_SHEET_PATH::Path() const
  114. {
  115. wxString s, t;
  116. s = wxT( "/" ); // This is the root path
  117. // start at 1 to avoid the root sheet,
  118. // which does not need to be added to the path
  119. // it's timestamp changes anyway.
  120. for( unsigned i = 1; i < size(); i++ )
  121. {
  122. t.Printf( _( "%8.8lX/" ), (long unsigned) at( i )->GetTimeStamp() );
  123. s = s + t;
  124. }
  125. return s;
  126. }
  127. wxString SCH_SHEET_PATH::PathHumanReadable() const
  128. {
  129. wxString s;
  130. s = wxT( "/" );
  131. // start at 1 to avoid the root sheet, as above.
  132. for( unsigned i = 1; i < size(); i++ )
  133. {
  134. s = s + at( i )->GetName() + wxT( "/" );
  135. }
  136. return s;
  137. }
  138. void SCH_SHEET_PATH::UpdateAllScreenReferences()
  139. {
  140. EDA_ITEM* t = LastDrawList();
  141. while( t )
  142. {
  143. if( t->Type() == SCH_COMPONENT_T )
  144. {
  145. SCH_COMPONENT* component = (SCH_COMPONENT*) t;
  146. component->GetField( REFERENCE )->SetText( component->GetRef( this ) );
  147. component->UpdateUnit( component->GetUnitSelection( this ) );
  148. }
  149. t = t->Next();
  150. }
  151. }
  152. void SCH_SHEET_PATH::GetComponents( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols,
  153. bool aForceIncludeOrphanComponents )
  154. {
  155. for( SCH_ITEM* item = LastDrawList(); item; item = item->Next() )
  156. {
  157. if( item->Type() == SCH_COMPONENT_T )
  158. {
  159. SCH_COMPONENT* component = (SCH_COMPONENT*) item;
  160. // Skip pseudo components, which have a reference starting with #. This mainly
  161. // affects power symbols.
  162. if( !aIncludePowerSymbols && component->GetRef( this )[0] == wxT( '#' ) )
  163. continue;
  164. LIB_PART* part = component->GetPartRef().lock().get();
  165. if( part || aForceIncludeOrphanComponents )
  166. {
  167. SCH_REFERENCE schReference( component, part, *this );
  168. schReference.SetSheetNumber( m_pageNumber );
  169. aReferences.AddItem( schReference );
  170. }
  171. }
  172. }
  173. }
  174. void SCH_SHEET_PATH::GetMultiUnitComponents( SCH_MULTI_UNIT_REFERENCE_MAP& aRefList,
  175. bool aIncludePowerSymbols )
  176. {
  177. for( SCH_ITEM* item = LastDrawList(); item; item = item->Next() )
  178. {
  179. if( item->Type() != SCH_COMPONENT_T )
  180. continue;
  181. SCH_COMPONENT* component = (SCH_COMPONENT*) item;
  182. // Skip pseudo components, which have a reference starting with #. This mainly
  183. // affects power symbols.
  184. if( !aIncludePowerSymbols && component->GetRef( this )[0] == wxT( '#' ) )
  185. continue;
  186. LIB_PART* part = component->GetPartRef().lock().get();
  187. if( part && part->GetUnitCount() > 1 )
  188. {
  189. SCH_REFERENCE schReference = SCH_REFERENCE( component, part, *this );
  190. schReference.SetSheetNumber( m_pageNumber );
  191. wxString reference_str = schReference.GetRef();
  192. // Never lock unassigned references
  193. if( reference_str[reference_str.Len() - 1] == '?' )
  194. continue;
  195. aRefList[reference_str].AddItem( schReference );
  196. }
  197. }
  198. }
  199. SCH_ITEM* SCH_SHEET_PATH::FindNextItem( KICAD_T aType, SCH_ITEM* aLastItem, bool aWrap ) const
  200. {
  201. bool hasWrapped = false;
  202. bool firstItemFound = false;
  203. SCH_ITEM* drawItem = LastDrawList();
  204. while( drawItem )
  205. {
  206. if( drawItem->Type() == aType )
  207. {
  208. if( !aLastItem || firstItemFound )
  209. {
  210. return drawItem;
  211. }
  212. else if( !firstItemFound && drawItem == aLastItem )
  213. {
  214. firstItemFound = true;
  215. }
  216. }
  217. drawItem = drawItem->Next();
  218. if( !drawItem && aLastItem && aWrap && !hasWrapped )
  219. {
  220. hasWrapped = true;
  221. drawItem = LastDrawList();
  222. }
  223. }
  224. return NULL;
  225. }
  226. SCH_ITEM* SCH_SHEET_PATH::FindPreviousItem( KICAD_T aType, SCH_ITEM* aLastItem, bool aWrap ) const
  227. {
  228. bool hasWrapped = false;
  229. bool firstItemFound = false;
  230. SCH_ITEM* drawItem = FirstDrawList();
  231. while( drawItem )
  232. {
  233. if( drawItem->Type() == aType )
  234. {
  235. if( aLastItem == NULL || firstItemFound )
  236. {
  237. return drawItem;
  238. }
  239. else if( !firstItemFound && drawItem == aLastItem )
  240. {
  241. firstItemFound = true;
  242. }
  243. }
  244. drawItem = drawItem->Back();
  245. if( drawItem == NULL && aLastItem && aWrap && !hasWrapped )
  246. {
  247. hasWrapped = true;
  248. drawItem = FirstDrawList();
  249. }
  250. }
  251. return NULL;
  252. }
  253. bool SCH_SHEET_PATH::SetComponentFootprint( const wxString& aReference, const wxString& aFootPrint,
  254. bool aSetVisible )
  255. {
  256. SCH_SCREEN* screen = LastScreen();
  257. if( screen == NULL )
  258. return false;
  259. return screen->SetComponentFootprint( this, aReference, aFootPrint, aSetVisible );
  260. }
  261. bool SCH_SHEET_PATH::operator==( const SCH_SHEET_PATH& d1 ) const
  262. {
  263. return m_current_hash == d1.GetCurrentHash();
  264. }
  265. bool SCH_SHEET_PATH::TestForRecursion( const wxString& aSrcFileName,
  266. const wxString& aDestFileName ) const
  267. {
  268. wxFileName rootFn = g_RootSheet->GetFileName();
  269. wxFileName srcFn = aSrcFileName;
  270. wxFileName destFn = aDestFileName;
  271. if( srcFn.IsRelative() )
  272. srcFn.MakeAbsolute( rootFn.GetPath() );
  273. if( destFn.IsRelative() )
  274. destFn.MakeAbsolute( rootFn.GetPath() );
  275. // The source and destination sheet file names cannot be the same.
  276. if( srcFn == destFn )
  277. return true;
  278. /// @todo Store sheet file names with full path, either relative to project path
  279. /// or absolute path. The current design always assumes subsheet files are
  280. /// located in the project folder which may or may not be desirable.
  281. unsigned i = 0;
  282. while( i < size() )
  283. {
  284. wxFileName cmpFn = at( i )->GetFileName();
  285. if( cmpFn.IsRelative() )
  286. cmpFn.MakeAbsolute( rootFn.GetPath() );
  287. // Test if the file name of the destination sheet is in anywhere in this sheet path.
  288. if( cmpFn == destFn )
  289. break;
  290. i++;
  291. }
  292. // The destination sheet file name was not found in the sheet path or the destination
  293. // sheet file name is the root sheet so no recursion is possible.
  294. if( i >= size() || i == 0 )
  295. return false;
  296. // Walk back up to the root sheet to see if the source file name is already a parent in
  297. // the sheet path. If so, recursion will occur.
  298. do
  299. {
  300. i -= 1;
  301. wxFileName cmpFn = at( i )->GetFileName();
  302. if( cmpFn.IsRelative() )
  303. cmpFn.MakeAbsolute( rootFn.GetPath() );
  304. if( cmpFn == srcFn )
  305. return true;
  306. } while( i != 0 );
  307. // The source sheet file name is not a parent of the destination sheet file name.
  308. return false;
  309. }
  310. int SCH_SHEET_PATH::FindSheet( const wxString& aFileName ) const
  311. {
  312. for( unsigned i = 0; i < size(); i++ )
  313. {
  314. if( at( i )->GetFileName().CmpNoCase( aFileName ) == 0 )
  315. return (int)i;
  316. }
  317. return SHEET_NOT_FOUND;
  318. }
  319. SCH_SHEET* SCH_SHEET_PATH::FindSheetByName( const wxString& aSheetName )
  320. {
  321. for( unsigned i = 0; i < size(); i++ )
  322. {
  323. if( at( i )->GetName().CmpNoCase( aSheetName ) == 0 )
  324. return at( i );
  325. }
  326. return NULL;
  327. }
  328. /********************************************************************/
  329. /* Class SCH_SHEET_LIST to handle the list of Sheets in a hierarchy */
  330. /********************************************************************/
  331. SCH_SHEET_LIST::SCH_SHEET_LIST( SCH_SHEET* aSheet )
  332. {
  333. m_isRootSheet = false;
  334. if( aSheet != NULL )
  335. BuildSheetList( aSheet );
  336. }
  337. SCH_SHEET_PATH* SCH_SHEET_LIST::GetSheetByPath( const wxString& aPath, bool aHumanReadable )
  338. {
  339. wxString sheetPath;
  340. for( unsigned i = 0; i < size(); i++ )
  341. {
  342. sheetPath = ( aHumanReadable ) ? at( i ).PathHumanReadable() : at( i ).Path();
  343. if( sheetPath == aPath )
  344. return &at( i );
  345. }
  346. return NULL;
  347. }
  348. void SCH_SHEET_LIST::BuildSheetList( SCH_SHEET* aSheet )
  349. {
  350. wxCHECK_RET( aSheet != NULL, wxT( "Cannot build sheet list from undefined sheet." ) );
  351. if( aSheet == g_RootSheet )
  352. m_isRootSheet = true;
  353. m_currentSheetPath.push_back( aSheet );
  354. /**
  355. * @todo: Schematic page number is currently a left over relic and is generated as
  356. * SCH_SHEET_PATH object is pushed to the list. This only has meaning when
  357. * entire hierarchy is created from the root sheet down.
  358. */
  359. m_currentSheetPath.SetPageNumber( size() + 1 );
  360. push_back( m_currentSheetPath );
  361. if( aSheet->GetScreen() )
  362. {
  363. EDA_ITEM* item = m_currentSheetPath.LastDrawList();
  364. while( item )
  365. {
  366. if( item->Type() == SCH_SHEET_T )
  367. {
  368. SCH_SHEET* sheet = (SCH_SHEET*) item;
  369. BuildSheetList( sheet );
  370. }
  371. item = item->Next();
  372. }
  373. }
  374. m_currentSheetPath.pop_back();
  375. }
  376. bool SCH_SHEET_LIST::IsModified()
  377. {
  378. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  379. {
  380. if( (*it).LastScreen() && (*it).LastScreen()->IsModify() )
  381. return true;
  382. }
  383. return false;
  384. }
  385. void SCH_SHEET_LIST::ClearModifyStatus()
  386. {
  387. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  388. {
  389. if( (*it).LastScreen() )
  390. (*it).LastScreen()->ClrModify();
  391. }
  392. }
  393. void SCH_SHEET_LIST::AnnotatePowerSymbols()
  394. {
  395. // List of reference for power symbols
  396. SCH_REFERENCE_LIST references;
  397. // Map of locked components (not used, but needed by Annotate()
  398. SCH_MULTI_UNIT_REFERENCE_MAP lockedComponents;
  399. // Build the list of power components:
  400. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  401. {
  402. SCH_SHEET_PATH& spath = *it;
  403. for( EDA_ITEM* item = spath.LastDrawList(); item; item = item->Next() )
  404. {
  405. if( item->Type() != SCH_COMPONENT_T )
  406. continue;
  407. SCH_COMPONENT* component = (SCH_COMPONENT*) item;
  408. LIB_PART* part = component->GetPartRef().lock().get();
  409. if( !part || !part->IsPower() )
  410. continue;
  411. if( part )
  412. {
  413. SCH_REFERENCE schReference( component, part, spath );
  414. references.AddItem( schReference );
  415. }
  416. }
  417. }
  418. // Find duplicate, and silently clear annotation of duplicate
  419. std::map<wxString, int> ref_list; // stores the existing references
  420. for( unsigned ii = 0; ii< references.GetCount(); ++ii )
  421. {
  422. wxString curr_ref = references[ii].GetRef();
  423. if( ref_list.find( curr_ref ) == ref_list.end() )
  424. {
  425. ref_list[curr_ref] = ii;
  426. continue;
  427. }
  428. // Possible duplicate, if the ref ends by a number:
  429. if( curr_ref.Last() < '0' && curr_ref.Last() > '9' )
  430. continue; // not annotated
  431. // Duplicate: clear annotation by removing the number ending the ref
  432. while( curr_ref.Last() >= '0' && curr_ref.Last() <= '9' )
  433. curr_ref.RemoveLast();
  434. references[ii].SetRef( curr_ref );
  435. }
  436. // Break full components reference in name (prefix) and number:
  437. // example: IC1 become IC, and 1
  438. references.SplitReferences();
  439. // Ensure all power symbols have the reference starting by '#'
  440. // (No sure this is really useful)
  441. for( unsigned ii = 0; ii< references.GetCount(); ++ii )
  442. {
  443. if( references[ii].GetRef()[0] != '#' )
  444. {
  445. wxString new_ref = "#" + references[ii].GetRef();
  446. references[ii].SetRef( new_ref );
  447. }
  448. }
  449. // Recalculate and update reference numbers in schematic
  450. references.Annotate( false, 0, 100, lockedComponents );
  451. references.UpdateAnnotation();
  452. }
  453. void SCH_SHEET_LIST::GetComponents( SCH_REFERENCE_LIST& aReferences, bool aIncludePowerSymbols,
  454. bool aForceIncludeOrphanComponents )
  455. {
  456. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  457. (*it).GetComponents( aReferences, aIncludePowerSymbols, aForceIncludeOrphanComponents );
  458. }
  459. void SCH_SHEET_LIST::GetMultiUnitComponents( SCH_MULTI_UNIT_REFERENCE_MAP &aRefList,
  460. bool aIncludePowerSymbols )
  461. {
  462. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  463. {
  464. SCH_MULTI_UNIT_REFERENCE_MAP tempMap;
  465. (*it).GetMultiUnitComponents( tempMap );
  466. for( SCH_MULTI_UNIT_REFERENCE_MAP::value_type& pair : tempMap )
  467. {
  468. // Merge this list into the main one
  469. unsigned n_refs = pair.second.GetCount();
  470. for( unsigned thisRef = 0; thisRef < n_refs; ++thisRef )
  471. {
  472. aRefList[pair.first].AddItem( pair.second[thisRef] );
  473. }
  474. }
  475. }
  476. }
  477. SCH_ITEM* SCH_SHEET_LIST::FindNextItem( KICAD_T aType, SCH_SHEET_PATH** aSheetFoundIn,
  478. SCH_ITEM* aLastItem, bool aWrap )
  479. {
  480. bool hasWrapped = false;
  481. bool firstItemFound = false;
  482. SCH_ITEM* drawItem = NULL;
  483. SCH_SHEET_PATHS_ITER it = begin();
  484. while( it != end() )
  485. {
  486. drawItem = (*it).LastDrawList();
  487. while( drawItem )
  488. {
  489. if( drawItem->Type() == aType )
  490. {
  491. if( aLastItem == NULL || firstItemFound )
  492. {
  493. if( aSheetFoundIn )
  494. *aSheetFoundIn = &(*it);
  495. return drawItem;
  496. }
  497. else if( !firstItemFound && drawItem == aLastItem )
  498. {
  499. firstItemFound = true;
  500. }
  501. }
  502. drawItem = drawItem->Next();
  503. }
  504. ++it;
  505. if( it == end() && aLastItem && aWrap && !hasWrapped )
  506. {
  507. hasWrapped = true;
  508. it = begin();
  509. }
  510. }
  511. return NULL;
  512. }
  513. SCH_ITEM* SCH_SHEET_LIST::FindPreviousItem( KICAD_T aType, SCH_SHEET_PATH** aSheetFoundIn,
  514. SCH_ITEM* aLastItem, bool aWrap )
  515. {
  516. bool hasWrapped = false;
  517. bool firstItemFound = false;
  518. SCH_ITEM* drawItem = NULL;
  519. SCH_SHEET_PATHS_RITER it = rbegin();
  520. while( it != rend() )
  521. {
  522. drawItem = (*it).FirstDrawList();
  523. while( drawItem )
  524. {
  525. if( drawItem->Type() == aType )
  526. {
  527. if( aLastItem == NULL || firstItemFound )
  528. {
  529. if( aSheetFoundIn )
  530. *aSheetFoundIn = &(*it);
  531. return drawItem;
  532. }
  533. else if( !firstItemFound && drawItem == aLastItem )
  534. {
  535. firstItemFound = true;
  536. }
  537. }
  538. drawItem = drawItem->Back();
  539. }
  540. ++it;
  541. if( it == rend() && aLastItem && aWrap && !hasWrapped )
  542. {
  543. hasWrapped = true;
  544. it = rbegin();
  545. }
  546. }
  547. return NULL;
  548. }
  549. bool SCH_SHEET_LIST::SetComponentFootprint( const wxString& aReference,
  550. const wxString& aFootPrint, bool aSetVisible )
  551. {
  552. bool found = false;
  553. for( SCH_SHEET_PATHS_ITER it = begin(); it != end(); ++it )
  554. found = (*it).SetComponentFootprint( aReference, aFootPrint, aSetVisible );
  555. return found;
  556. }
  557. bool SCH_SHEET_LIST::IsComplexHierarchy() const
  558. {
  559. wxString fileName;
  560. for( unsigned i = 0; i < size(); i++ )
  561. {
  562. fileName = at( i ).Last()->GetFileName();
  563. for( unsigned j = 0; j < size(); j++ )
  564. {
  565. if( i == j )
  566. continue;
  567. if( fileName == at( j ).Last()->GetFileName() )
  568. return true;
  569. }
  570. }
  571. return false;
  572. }
  573. bool SCH_SHEET_LIST::TestForRecursion( const SCH_SHEET_LIST& aSrcSheetHierarchy,
  574. const wxString& aDestFileName ) const
  575. {
  576. wxFileName rootFn = g_RootSheet->GetFileName();
  577. wxFileName destFn = aDestFileName;
  578. if( destFn.IsRelative() )
  579. destFn.MakeAbsolute( rootFn.GetPath() );
  580. // Test each SCH_SHEET_PATH in this SCH_SHEET_LIST for potential recursion.
  581. for( unsigned i = 0; i < size(); i++ )
  582. {
  583. // Test each SCH_SHEET_PATH in the source sheet.
  584. for( unsigned j = 0; j < aSrcSheetHierarchy.size(); j++ )
  585. {
  586. const SCH_SHEET_PATH* sheetPath = &aSrcSheetHierarchy[j];
  587. for( unsigned k = 0; k < sheetPath->size(); k++ )
  588. {
  589. if( at( i ).TestForRecursion( sheetPath->GetSheet( k )->GetFileName(),
  590. aDestFileName ) )
  591. return true;
  592. }
  593. }
  594. }
  595. // The source sheet file can safely be added to the destination sheet file.
  596. return false;
  597. }
  598. SCH_SHEET* SCH_SHEET_LIST::FindSheetByName( const wxString& aSheetName )
  599. {
  600. for( unsigned i = 0; i < size(); i++ )
  601. {
  602. SCH_SHEET* sheet = at( i ).FindSheetByName( aSheetName );
  603. if( sheet )
  604. return sheet;
  605. }
  606. return NULL;
  607. }