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.

702 lines
19 KiB

17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
18 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2009 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
  5. * Copyright (C) 2011 Wayne Stambaugh <stambaughw@verizon.net>
  6. * Copyright (C) 1992-2011 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 class_module.cpp
  27. * @brief MODULE class implementation.
  28. */
  29. #include "fctsys.h"
  30. #include "gr_basic.h"
  31. #include "wxstruct.h"
  32. #include "plot_common.h"
  33. #include "class_drawpanel.h"
  34. #include "trigo.h"
  35. #include "confirm.h"
  36. #include "kicad_string.h"
  37. #include "pcbcommon.h"
  38. #include "pcbnew.h"
  39. #include "colors_selection.h"
  40. #include "richio.h"
  41. #include "filter_reader.h"
  42. #include "macros.h"
  43. #include "3d_struct.h"
  44. #include "drag.h"
  45. #include "protos.h"
  46. #include "class_board.h"
  47. #include "class_edge_mod.h"
  48. #include "class_module.h"
  49. MODULE::MODULE( BOARD* parent ) :
  50. BOARD_ITEM( (BOARD_ITEM*) parent, PCB_MODULE_T )
  51. {
  52. m_Attributs = MOD_DEFAULT;
  53. m_Layer = LAYER_N_FRONT;
  54. m_Orient = 0;
  55. m_ModuleStatus = 0;
  56. flag = 0;
  57. m_CntRot90 = m_CntRot180 = 0;
  58. m_Surface = 0.0;
  59. m_Link = 0;
  60. m_LastEdit_Time = time( NULL );
  61. m_LocalClearance = 0;
  62. m_LocalSolderMaskMargin = 0;
  63. m_LocalSolderPasteMargin = 0;
  64. m_LocalSolderPasteMarginRatio = 0.0;
  65. m_Reference = new TEXTE_MODULE( this, TEXT_is_REFERENCE );
  66. m_Value = new TEXTE_MODULE( this, TEXT_is_VALUE );
  67. // Reserve one void 3D entry, to avoid problems with void list
  68. m_3D_Drawings.PushBack( new S3D_MASTER( this ) );
  69. }
  70. MODULE::MODULE( const MODULE& aModule ) :
  71. BOARD_ITEM( aModule )
  72. {
  73. BOARD_ITEM* newItem;
  74. m_Pos = aModule.m_Pos;
  75. m_LibRef = aModule.m_LibRef;
  76. m_Attributs = aModule.m_Attributs;
  77. m_Orient = aModule.m_Orient;
  78. m_BoundaryBox = aModule.m_BoundaryBox;
  79. m_PadNum = aModule.m_PadNum;
  80. m_CntRot90 = aModule.m_CntRot90;
  81. m_CntRot180 = aModule.m_CntRot180;
  82. m_LastEdit_Time = aModule.m_LastEdit_Time;
  83. m_Link = aModule.m_Link;
  84. m_Path = aModule.m_Path; //is this correct behavior?
  85. m_LocalClearance = aModule.m_LocalClearance;
  86. m_LocalSolderMaskMargin = aModule.m_LocalSolderMaskMargin;
  87. m_LocalSolderPasteMargin = aModule.m_LocalSolderPasteMargin;
  88. m_LocalSolderPasteMarginRatio = aModule.m_LocalSolderPasteMarginRatio;
  89. /* Copy reference and value. */
  90. m_Reference = new TEXTE_MODULE( *aModule.m_Reference );
  91. m_Reference->SetParent( this );
  92. m_Value = new TEXTE_MODULE( *aModule.m_Value );
  93. m_Value->SetParent( this );
  94. /* Copy auxiliary data: Pads */
  95. m_Pads.DeleteAll();
  96. for( D_PAD* pad = aModule.m_Pads; pad; pad = pad->Next() )
  97. {
  98. D_PAD* newpad = new D_PAD( *pad );
  99. newpad->SetParent( this );
  100. m_Pads.PushBack( newpad );
  101. }
  102. /* Copy auxiliary data: Drawings */
  103. m_Drawings.DeleteAll();
  104. for( BOARD_ITEM* item = aModule.m_Drawings; item; item = item->Next() )
  105. {
  106. switch( item->Type() )
  107. {
  108. case PCB_MODULE_TEXT_T:
  109. case PCB_MODULE_EDGE_T:
  110. newItem = (BOARD_ITEM*)item->Clone();
  111. newItem->SetParent( this );
  112. m_Drawings.PushBack( newItem );
  113. break;
  114. default:
  115. wxMessageBox( wxT( "MODULE::Copy() Internal Err: unknown type" ) );
  116. break;
  117. }
  118. }
  119. /* Copy auxiliary data: 3D_Drawings info */
  120. m_3D_Drawings.DeleteAll();
  121. for( S3D_MASTER* item = aModule.m_3D_Drawings; item; item = item->Next() )
  122. {
  123. if( item->m_Shape3DName.IsEmpty() ) // do not copy empty shapes.
  124. continue;
  125. S3D_MASTER* t3d = m_3D_Drawings;
  126. t3d = new S3D_MASTER( this );
  127. t3d->Copy( item );
  128. m_3D_Drawings.PushBack( t3d );
  129. }
  130. // Ensure there is at least one item in m_3D_Drawings.
  131. if( m_3D_Drawings.GetCount() == 0 )
  132. m_3D_Drawings.PushBack( new S3D_MASTER( this ) ); // push a void item
  133. m_Doc = aModule.m_Doc;
  134. m_KeyWord = aModule.m_KeyWord;
  135. }
  136. MODULE::~MODULE()
  137. {
  138. delete m_Reference;
  139. delete m_Value;
  140. }
  141. /* Draw the anchor cross (vertical)
  142. * Must be done after the pads, because drawing the hole will erase overwrite
  143. * every thing already drawn.
  144. */
  145. void MODULE::DrawAncre( EDA_DRAW_PANEL* panel, wxDC* DC, const wxPoint& offset,
  146. int dim_ancre, int draw_mode )
  147. {
  148. int anchor_size = DC->DeviceToLogicalXRel( dim_ancre );
  149. GRSetDrawMode( DC, draw_mode );
  150. if( GetBoard()->IsElementVisible( ANCHOR_VISIBLE ) )
  151. {
  152. int color = g_ColorsSettings.GetItemColor( ANCHOR_VISIBLE );
  153. GRLine( panel->GetClipBox(), DC,
  154. m_Pos.x - offset.x - anchor_size, m_Pos.y - offset.y,
  155. m_Pos.x - offset.x + anchor_size, m_Pos.y - offset.y,
  156. 0, color );
  157. GRLine( panel->GetClipBox(), DC,
  158. m_Pos.x - offset.x, m_Pos.y - offset.y - anchor_size,
  159. m_Pos.x - offset.x, m_Pos.y - offset.y + anchor_size,
  160. 0, color );
  161. }
  162. }
  163. void MODULE::Copy( MODULE* aModule )
  164. {
  165. m_Pos = aModule->m_Pos;
  166. m_Layer = aModule->m_Layer;
  167. m_LibRef = aModule->m_LibRef;
  168. m_Attributs = aModule->m_Attributs;
  169. m_Orient = aModule->m_Orient;
  170. m_BoundaryBox = aModule->m_BoundaryBox;
  171. m_PadNum = aModule->m_PadNum;
  172. m_CntRot90 = aModule->m_CntRot90;
  173. m_CntRot180 = aModule->m_CntRot180;
  174. m_LastEdit_Time = aModule->m_LastEdit_Time;
  175. m_Link = aModule->m_Link;
  176. m_Path = aModule->m_Path; //is this correct behavior?
  177. SetTimeStamp( GetNewTimeStamp() );
  178. m_LocalClearance = aModule->m_LocalClearance;
  179. m_LocalSolderMaskMargin = aModule->m_LocalSolderMaskMargin;
  180. m_LocalSolderPasteMargin = aModule->m_LocalSolderPasteMargin;
  181. m_LocalSolderPasteMarginRatio = aModule->m_LocalSolderPasteMarginRatio;
  182. /* Copy reference and value. */
  183. m_Reference->Copy( aModule->m_Reference );
  184. m_Value->Copy( aModule->m_Value );
  185. /* Copy auxiliary data: Pads */
  186. m_Pads.DeleteAll();
  187. for( D_PAD* pad = aModule->m_Pads; pad; pad = pad->Next() )
  188. {
  189. D_PAD* newpad = new D_PAD( this );
  190. newpad->Copy( pad );
  191. m_Pads.PushBack( newpad );
  192. }
  193. /* Copy auxiliary data: Drawings */
  194. m_Drawings.DeleteAll();
  195. for( BOARD_ITEM* item = aModule->m_Drawings; item; item = item->Next() )
  196. {
  197. switch( item->Type() )
  198. {
  199. case PCB_MODULE_TEXT_T:
  200. TEXTE_MODULE * textm;
  201. textm = new TEXTE_MODULE( this );
  202. textm->Copy( (TEXTE_MODULE*) item );
  203. m_Drawings.PushBack( textm );
  204. break;
  205. case PCB_MODULE_EDGE_T:
  206. EDGE_MODULE * edge;
  207. edge = new EDGE_MODULE( this );
  208. edge->Copy( (EDGE_MODULE*) item );
  209. m_Drawings.PushBack( edge );
  210. break;
  211. default:
  212. wxMessageBox( wxT( "MODULE::Copy() Internal Err: unknown type" ) );
  213. break;
  214. }
  215. }
  216. /* Copy auxiliary data: 3D_Drawings info */
  217. m_3D_Drawings.DeleteAll();
  218. // Ensure there is one (or more) item in m_3D_Drawings
  219. m_3D_Drawings.PushBack( new S3D_MASTER( this ) ); // push a void item
  220. for( S3D_MASTER* item = aModule->m_3D_Drawings; item; item = item->Next() )
  221. {
  222. if( item->m_Shape3DName.IsEmpty() ) // do not copy empty shapes.
  223. continue;
  224. S3D_MASTER* t3d = m_3D_Drawings;
  225. if( t3d && t3d->m_Shape3DName.IsEmpty() ) // The first entry can
  226. { // exist, but is empty : use it.
  227. t3d->Copy( item );
  228. }
  229. else
  230. {
  231. t3d = new S3D_MASTER( this );
  232. t3d->Copy( item );
  233. m_3D_Drawings.PushBack( t3d );
  234. }
  235. }
  236. m_Doc = aModule->m_Doc;
  237. m_KeyWord = aModule->m_KeyWord;
  238. }
  239. /**
  240. * Function Draw
  241. * Draws the footprint to the current Device Context
  242. * @param aPanel = draw panel, Used to know the clip box
  243. * @param aDC = Current Device Context
  244. * @param aDrawMode = GR_OR, GR_XOR..
  245. * @param aOffset = draw offset (usually wxPoint(0,0)
  246. */
  247. void MODULE::Draw( EDA_DRAW_PANEL* aPanel, wxDC* aDC, int aDrawMode, const wxPoint& aOffset )
  248. {
  249. if( (m_Flags & DO_NOT_DRAW) || (IsMoving()) )
  250. return;
  251. for( D_PAD* pad = m_Pads; pad; pad = pad->Next() )
  252. {
  253. if( pad->IsMoving() )
  254. continue;
  255. pad->Draw( aPanel, aDC, aDrawMode, aOffset );
  256. }
  257. BOARD* brd = GetBoard();
  258. // Draws footprint anchor
  259. DrawAncre( aPanel, aDC, aOffset, DIM_ANCRE_MODULE, aDrawMode );
  260. /* Draw graphic items */
  261. if( brd->IsElementVisible( MOD_REFERENCES_VISIBLE ) )
  262. {
  263. if( !(m_Reference->IsMoving()) )
  264. m_Reference->Draw( aPanel, aDC, aDrawMode, aOffset );
  265. }
  266. if( brd->IsElementVisible( MOD_VALUES_VISIBLE ) )
  267. {
  268. if( !(m_Value->IsMoving()) )
  269. m_Value->Draw( aPanel, aDC, aDrawMode, aOffset );
  270. }
  271. for( BOARD_ITEM* item = m_Drawings; item; item = item->Next() )
  272. {
  273. if( item->IsMoving() )
  274. continue;
  275. switch( item->Type() )
  276. {
  277. case PCB_MODULE_TEXT_T:
  278. case PCB_MODULE_EDGE_T:
  279. item->Draw( aPanel, aDC, aDrawMode, aOffset );
  280. break;
  281. default:
  282. break;
  283. }
  284. }
  285. // Enable these line to draw m_BoundaryBox (debug tests purposes only)
  286. #if 0
  287. GRRect( aPanel->GetClipBox(), aDC, m_BoundaryBox, 0, BROWN );
  288. #endif
  289. }
  290. /**
  291. * Function DrawEdgesOnly
  292. * Draws the footprint edges only to the current Device Context
  293. * @param panel = The active Draw Panel (used to know the clip box)
  294. * @param DC = current Device Context
  295. * @param offset = draw offset (usually wxPoint(0,0)
  296. * @param draw_mode = GR_OR, GR_XOR, GR_AND
  297. */
  298. void MODULE::DrawEdgesOnly( EDA_DRAW_PANEL* panel, wxDC* DC, const wxPoint& offset, int draw_mode )
  299. {
  300. for( BOARD_ITEM* item = m_Drawings; item; item = item->Next() )
  301. {
  302. switch( item->Type() )
  303. {
  304. case PCB_MODULE_EDGE_T:
  305. item->Draw( panel, DC, draw_mode, offset );
  306. break;
  307. default:
  308. break;
  309. }
  310. }
  311. }
  312. void MODULE::CalculateBoundingBox()
  313. {
  314. m_BoundaryBox = GetFootPrintRect();
  315. m_Surface = ABS( (double) m_BoundaryBox.GetWidth() * m_BoundaryBox.GetHeight() );
  316. }
  317. EDA_RECT MODULE::GetFootPrintRect() const
  318. {
  319. EDA_RECT area;
  320. area.SetOrigin( m_Pos );
  321. area.SetEnd( m_Pos );
  322. area.Inflate( 50 ); // Give a min size
  323. for( EDGE_MODULE* edge = (EDGE_MODULE*) m_Drawings.GetFirst(); edge; edge = edge->Next() )
  324. if( edge->Type() == PCB_MODULE_EDGE_T )
  325. area.Merge( edge->GetBoundingBox() );
  326. for( D_PAD* pad = m_Pads; pad; pad = pad->Next() )
  327. area.Merge( pad->GetBoundingBox() );
  328. return area;
  329. }
  330. EDA_RECT MODULE::GetBoundingBox() const
  331. {
  332. EDA_RECT area = GetFootPrintRect();
  333. // Calculate extended area including text fields
  334. area.Merge( m_Reference->GetBoundingBox() );
  335. area.Merge( m_Value->GetBoundingBox() );
  336. // Add the Clearance shape size: (shape around the pads when the
  337. // clearance is shown. Not optimized, but the draw cost is small
  338. // (perhaps smaller than optimization).
  339. int biggest_clearance = GetBoard()->GetBiggestClearanceValue();
  340. area.Inflate( biggest_clearance );
  341. return area;
  342. }
  343. /* Virtual function, from EDA_ITEM.
  344. * display module info on MsgPanel
  345. */
  346. void MODULE::DisplayInfo( EDA_DRAW_FRAME* frame )
  347. {
  348. int nbpad;
  349. char bufcar[512], Line[512];
  350. bool flag = false;
  351. wxString msg;
  352. BOARD* board = GetBoard();
  353. frame->EraseMsgBox();
  354. if( frame->IsType( PCB_FRAME ) )
  355. flag = true;
  356. frame->AppendMsgPanel( m_Reference->m_Text, m_Value->m_Text, DARKCYAN );
  357. if( flag ) // Display last date the component was edited( useful in Module Editor)
  358. {
  359. time_t edit_time = m_LastEdit_Time;
  360. strcpy( Line, ctime( &edit_time ) );
  361. strtok( Line, " \n\r" );
  362. strcpy( bufcar, strtok( NULL, " \n\r" ) ); strcat( bufcar, " " );
  363. strcat( bufcar, strtok( NULL, " \n\r" ) ); strcat( bufcar, ", " );
  364. strtok( NULL, " \n\r" );
  365. strcat( bufcar, strtok( NULL, " \n\r" ) );
  366. msg = FROM_UTF8( bufcar );
  367. frame->AppendMsgPanel( _( "Last Change" ), msg, BROWN );
  368. }
  369. else // display time stamp in schematic
  370. {
  371. msg.Printf( wxT( "%8.8lX" ), m_TimeStamp );
  372. frame->AppendMsgPanel( _( "Netlist path" ), m_Path, BROWN );
  373. }
  374. frame->AppendMsgPanel( _( "Layer" ), board->GetLayerName( m_Layer ), RED );
  375. EDA_ITEM* PtStruct = m_Pads;
  376. nbpad = 0;
  377. while( PtStruct )
  378. {
  379. nbpad++;
  380. PtStruct = PtStruct->Next();
  381. }
  382. msg.Printf( wxT( "%d" ), nbpad );
  383. frame->AppendMsgPanel( _( "Pads" ), msg, BLUE );
  384. msg = wxT( ".." );
  385. if( IsLocked() )
  386. msg[0] = 'L';
  387. if( m_ModuleStatus & MODULE_is_PLACED )
  388. msg[1] = 'P';
  389. frame->AppendMsgPanel( _( "Stat" ), msg, MAGENTA );
  390. msg.Printf( wxT( "%.1f" ), (float) m_Orient / 10 );
  391. frame->AppendMsgPanel( _( "Orient" ), msg, BROWN );
  392. frame->AppendMsgPanel( _( "Module" ), m_LibRef, BLUE );
  393. if( m_3D_Drawings != NULL )
  394. msg = m_3D_Drawings->m_Shape3DName;
  395. else
  396. msg = _( "No 3D shape" );
  397. frame->AppendMsgPanel( _( "3D-Shape" ), msg, RED );
  398. wxString doc = _( "Doc: " ) + m_Doc;
  399. wxString keyword = _( "KeyW: " ) + m_KeyWord;
  400. frame->AppendMsgPanel( doc, keyword, BLACK );
  401. }
  402. bool MODULE::HitTest( const wxPoint& aRefPos )
  403. {
  404. if( m_BoundaryBox.Contains( aRefPos ) )
  405. return true;
  406. return false;
  407. }
  408. bool MODULE::HitTest( EDA_RECT& aRefArea )
  409. {
  410. if( m_BoundaryBox.GetX() < aRefArea.GetX() )
  411. return false;
  412. if( m_BoundaryBox.GetY() < aRefArea.GetY() )
  413. return false;
  414. if( m_BoundaryBox.GetRight() > aRefArea.GetRight() )
  415. return false;
  416. if( m_BoundaryBox.GetBottom() > aRefArea.GetBottom() )
  417. return false;
  418. return true;
  419. }
  420. D_PAD* MODULE::FindPadByName( const wxString& aPadName ) const
  421. {
  422. wxString buf;
  423. for( D_PAD* pad = m_Pads; pad; pad = pad->Next() )
  424. {
  425. pad->ReturnStringPadName( buf );
  426. #if 1
  427. if( buf.CmpNoCase( aPadName ) == 0 ) // why case insensitive?
  428. #else
  429. if( buf == aPadName )
  430. #endif
  431. return pad;
  432. }
  433. return NULL;
  434. }
  435. D_PAD* MODULE::GetPad( const wxPoint& aPosition, int aLayerMask )
  436. {
  437. for( D_PAD* pad = m_Pads; pad; pad = pad->Next() )
  438. {
  439. /* ... and on the correct layer. */
  440. if( ( pad->m_layerMask & aLayerMask ) == 0 )
  441. continue;
  442. if( pad->HitTest( aPosition ) )
  443. return pad;
  444. }
  445. return NULL;
  446. }
  447. // see class_module.h
  448. SEARCH_RESULT MODULE::Visit( INSPECTOR* inspector, const void* testData,
  449. const KICAD_T scanTypes[] )
  450. {
  451. KICAD_T stype;
  452. SEARCH_RESULT result = SEARCH_CONTINUE;
  453. const KICAD_T* p = scanTypes;
  454. bool done = false;
  455. #if 0 && defined(DEBUG)
  456. std::cout << GetClass().mb_str() << ' ';
  457. #endif
  458. while( !done )
  459. {
  460. stype = *p;
  461. switch( stype )
  462. {
  463. case PCB_MODULE_T:
  464. result = inspector->Inspect( this, testData ); // inspect me
  465. ++p;
  466. break;
  467. case PCB_PAD_T:
  468. result = IterateForward( m_Pads, inspector, testData, p );
  469. ++p;
  470. break;
  471. case PCB_MODULE_TEXT_T:
  472. result = inspector->Inspect( m_Reference, testData );
  473. if( result == SEARCH_QUIT )
  474. break;
  475. result = inspector->Inspect( m_Value, testData );
  476. if( result == SEARCH_QUIT )
  477. break;
  478. // m_Drawings can hold TYPETEXTMODULE also, so fall thru
  479. case PCB_MODULE_EDGE_T:
  480. result = IterateForward( m_Drawings, inspector, testData, p );
  481. // skip over any types handled in the above call.
  482. for( ; ; )
  483. {
  484. switch( stype = *++p )
  485. {
  486. case PCB_MODULE_TEXT_T:
  487. case PCB_MODULE_EDGE_T:
  488. continue;
  489. default:
  490. ;
  491. }
  492. break;
  493. }
  494. break;
  495. default:
  496. done = true;
  497. break;
  498. }
  499. if( result == SEARCH_QUIT )
  500. break;
  501. }
  502. return result;
  503. }
  504. wxString MODULE::GetSelectMenuText() const
  505. {
  506. wxString text;
  507. text << _( "Footprint" ) << wxT( " " ) << GetReference();
  508. text << wxT( " (" ) << GetLayerName() << wxT( ")" );
  509. return text;
  510. }
  511. EDA_ITEM* MODULE::doClone() const
  512. {
  513. return new MODULE( *this );
  514. }
  515. #if defined(DEBUG)
  516. void MODULE::Show( int nestLevel, std::ostream& os ) const
  517. {
  518. BOARD* board = GetBoard();
  519. // for now, make it look like XML, expand on this later.
  520. NestedSpace( nestLevel, os ) << '<' << GetClass().Lower().mb_str() <<
  521. " ref=\"" << m_Reference->m_Text.mb_str() << '"' <<
  522. " value=\"" << m_Value->m_Text.mb_str() << '"' <<
  523. " layer=\"" << board->GetLayerName( m_Layer ).mb_str() << '"' <<
  524. ">\n";
  525. NestedSpace( nestLevel + 1, os ) << "<boundingBox" << m_BoundaryBox.GetPosition()
  526. << m_BoundaryBox.GetSize() << "/>\n";
  527. NestedSpace( nestLevel + 1, os ) << "<orientation tenths=\"" << m_Orient
  528. << "\"/>\n";
  529. EDA_ITEM* p;
  530. NestedSpace( nestLevel + 1, os ) << "<mpads>\n";
  531. p = m_Pads;
  532. for( ; p; p = p->Next() )
  533. p->Show( nestLevel + 2, os );
  534. NestedSpace( nestLevel + 1, os ) << "</mpads>\n";
  535. NestedSpace( nestLevel + 1, os ) << "<mdrawings>\n";
  536. p = m_Drawings;
  537. for( ; p; p = p->Next() )
  538. p->Show( nestLevel + 2, os );
  539. NestedSpace( nestLevel + 1, os ) << "</mdrawings>\n";
  540. p = m_Son;
  541. for( ; p; p = p->Next() )
  542. {
  543. p->Show( nestLevel + 1, os );
  544. }
  545. NestedSpace( nestLevel, os ) << "</" << GetClass().Lower().mb_str()
  546. << ">\n";
  547. }
  548. #endif