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.

1207 lines
41 KiB

6 years ago
6 years ago
6 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2019 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <convert_to_biu.h>
  25. #include <macros.h> // arrayDim definition
  26. #include <pcb_edit_frame.h>
  27. #include <class_board.h>
  28. #include <widgets/paged_dialog.h>
  29. #include <widgets/layer_box_selector.h>
  30. #include <wx/colordlg.h>
  31. #include <wx/rawbmp.h>
  32. #include "panel_board_stackup.h"
  33. #include <panel_setup_layers.h>
  34. #include "board_stackup_reporter.h"
  35. #include <bitmaps.h>
  36. #include <wx/clipbrd.h>
  37. #include <wx/dataobj.h>
  38. #include "dialog_dielectric_list_manager.h"
  39. // Some wx widget ID to know what widget has fired a event:
  40. #define ID_INCREMENT 128 // space between 2 ID type. Bigger than the layer count max
  41. // The actual widget IDs are the base id + the row index.
  42. // they are used in events to know the row index of the control that fired the event
  43. enum WIDGETS_IDS
  44. {
  45. ID_ITEM_MATERIAL = 10000, // Be sure it is higher than other IDs used in the board setup dialog
  46. ID_ITEM_THICKNESS = ID_ITEM_MATERIAL + ID_INCREMENT,
  47. ID_ITEM_THICKNESS_LOCKED = ID_ITEM_THICKNESS + ID_INCREMENT,
  48. ID_ITEM_COLOR = ID_ITEM_THICKNESS_LOCKED + ID_INCREMENT,
  49. };
  50. // Default colors to draw icons:
  51. static wxColor copperColor( 220, 180, 30 );
  52. static wxColor dielectricColor( 75, 120, 75 );
  53. static wxColor pasteColor( 200, 200, 200 );
  54. static void drawBitmap( wxBitmap& aBitmap, wxColor aColor );
  55. PANEL_SETUP_BOARD_STACKUP::PANEL_SETUP_BOARD_STACKUP( PAGED_DIALOG* aParent, PCB_EDIT_FRAME* aFrame,
  56. PANEL_SETUP_LAYERS* aPanelLayers ):
  57. PANEL_SETUP_BOARD_STACKUP_BASE( aParent->GetTreebook() ),
  58. m_delectricMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_DIELECTRIC ),
  59. m_solderMaskMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SOLDERMASK ),
  60. m_silkscreenMatList( DIELECTRIC_SUBSTRATE_LIST::DL_MATERIAL_SILKSCREEN )
  61. {
  62. m_frame = aFrame;
  63. m_panelLayers = aPanelLayers;
  64. m_board = m_frame->GetBoard();
  65. m_brdSettings = &m_board->GetDesignSettings();
  66. m_units = aFrame->GetUserUnits();
  67. m_enabledLayers = m_board->GetEnabledLayers() & BOARD_STACKUP::StackupAllowedBrdLayers();
  68. // Calculates a good size for color swatches (icons) in this dialog
  69. wxClientDC dc( this );
  70. m_colorSwatchesSize = dc.GetTextExtent( "XX" );
  71. m_colorComboSize = dc.GetTextExtent( "XXX Undefined XXX" );
  72. m_colorIconsSize = dc.GetTextExtent( "XXXX" );
  73. // Calculates a good size for wxTextCtrl to enter Epsilon R and Loss tan
  74. // ("0.000000" + margins)
  75. m_numericFieldsSize = dc.GetTextExtent( "X.XXXXXXX" );
  76. m_numericFieldsSize.y = -1; // Use default for the vertical size
  77. // Calculates a minimal size for wxTextCtrl to enter a dim with units
  78. // ("000.0000000 mils" + margins)
  79. m_numericTextCtrlSize = dc.GetTextExtent( "XXX.XXXXXXX mils" );
  80. m_numericTextCtrlSize.y = -1; // Use default for the vertical size
  81. // The grid column containing the lock checkbox is kept to a minimal
  82. // size. So we use a wxStaticBitmap: set the bitmap itself
  83. m_bitmapLockThickness->SetBitmap( KiScaledBitmap( locked_xpm, aFrame ) );
  84. // Gives a minimal size of wxTextCtrl showing dimensions+units
  85. m_thicknessCtrl->SetMinSize( m_numericTextCtrlSize );
  86. m_tcCTValue->SetMinSize( m_numericTextCtrlSize );
  87. buildLayerStackPanel();
  88. synchronizeWithBoard( true );
  89. }
  90. PANEL_SETUP_BOARD_STACKUP::~PANEL_SETUP_BOARD_STACKUP()
  91. {
  92. disconnectEvents();
  93. }
  94. void PANEL_SETUP_BOARD_STACKUP::disconnectEvents()
  95. {
  96. // Disconnect Events connected to items in m_controlItemsList
  97. for( wxControl* item: m_controlItemsList )
  98. {
  99. wxBitmapComboBox* cb = dynamic_cast<wxBitmapComboBox*>( item );
  100. if( cb )
  101. cb->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED,
  102. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
  103. NULL, this );
  104. wxButton* matButt = dynamic_cast<wxButton*>( item );
  105. if( matButt )
  106. matButt->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
  107. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
  108. NULL, this );
  109. wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( item );
  110. if( textCtrl )
  111. textCtrl->Disconnect( wxEVT_COMMAND_TEXT_UPDATED,
  112. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
  113. NULL, this );
  114. }
  115. }
  116. void PANEL_SETUP_BOARD_STACKUP::onExportToClipboard( wxCommandEvent& event )
  117. {
  118. if( !transferDataFromUIToStackup() )
  119. return;
  120. // Build a ascii representation of stackup and copy it in the clipboard
  121. wxString report = BuildStackupReport( m_stackup, m_units );
  122. if( wxTheClipboard->Open() )
  123. {
  124. // This data objects are held by the clipboard,
  125. // so do not delete them in the app.
  126. wxTheClipboard->SetData( new wxTextDataObject( report ) );
  127. wxTheClipboard->Close();
  128. }
  129. }
  130. wxColor PANEL_SETUP_BOARD_STACKUP::GetSelectedColor( int aRow ) const
  131. {
  132. wxBitmapComboBox* choice = static_cast<wxBitmapComboBox*>( m_rowUiItemsList[aRow].m_ColorCtrl );
  133. wxASSERT( choice );
  134. int idx = choice->GetSelection();
  135. if( idx != GetColorUserDefinedListIdx() ) // a standard color is selected
  136. return GetColorStandardList()[idx].m_Color;
  137. else
  138. return m_UserColors[aRow];
  139. }
  140. void PANEL_SETUP_BOARD_STACKUP::onUpdateThicknessValue( wxUpdateUIEvent& event )
  141. {
  142. int thickness = 0;
  143. for( auto item : m_stackup.GetList() )
  144. {
  145. if( item->IsThicknessEditable() && item->m_Enabled )
  146. thickness += item->m_Thickness;
  147. }
  148. m_tcCTValue->SetValue( StringFromValue( m_units, thickness, true, true ) );
  149. }
  150. int PANEL_SETUP_BOARD_STACKUP::GetPcbTickness()
  151. {
  152. return ValueFromString( m_units, m_thicknessCtrl->GetValue(), true );
  153. }
  154. void PANEL_SETUP_BOARD_STACKUP::synchronizeWithBoard( bool aFullSync )
  155. {
  156. BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
  157. // Calculate copper layer count from m_enabledLayers, and *do not use* brd_stackup
  158. // for that, because it is not necessary up to date
  159. // (for instance after modifying the layer count from the panel layers in dialog)
  160. LSET copperMask = m_enabledLayers & ( LSET::ExternalCuMask() | LSET::InternalCuMask() );
  161. int copperLayersCount = copperMask.count();
  162. if( aFullSync )
  163. {
  164. int thickness = m_brdSettings->GetBoardThickness();
  165. m_thicknessCtrl->SetValue( StringFromValue( m_units, thickness, true, true ) );
  166. m_rbDielectricConstraint->SetSelection( brd_stackup.m_HasDielectricConstrains ? 1 : 0 );
  167. m_choiceEdgeConn->SetSelection( brd_stackup.m_EdgeConnectorConstraints );
  168. m_cbCastellatedPads->SetValue( brd_stackup.m_CastellatedPads );
  169. m_cbEgdesPlated->SetValue( brd_stackup.m_EdgePlating );
  170. // find the choice depending on the initial finish setting
  171. wxArrayString initial_finish_list = GetCopperFinishStandardList( false );
  172. unsigned idx;
  173. for( idx = 0; idx < initial_finish_list.GetCount(); idx++ )
  174. {
  175. if( initial_finish_list[idx] == brd_stackup.m_FinishType )
  176. break;
  177. }
  178. // Now init the choice (use last choice: "User defined" if not found )
  179. if( idx >= initial_finish_list.GetCount() )
  180. idx = initial_finish_list.GetCount()-1;
  181. m_choiceFinish->SetSelection( idx );
  182. }
  183. int row = 0;
  184. for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
  185. {
  186. BOARD_STACKUP_ROW_UI_ITEM& ui_row_item = m_rowUiItemsList[row];
  187. BOARD_STACKUP_ITEM* brd_stack_item = nullptr;
  188. // test for existing stackup items in board:
  189. for( BOARD_STACKUP_ITEM* brd_item : brd_stackup.GetList() )
  190. {
  191. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  192. {
  193. // Compare only BS_ITEM_TYPE_DIELECTRIC items
  194. if( brd_item->m_Type != BS_ITEM_TYPE_DIELECTRIC )
  195. continue;
  196. if( item->m_DielectricLayerId == brd_item->m_DielectricLayerId )
  197. brd_stack_item = brd_item;
  198. }
  199. else if( item->m_LayerId == brd_item->m_LayerId )
  200. brd_stack_item = brd_item;
  201. if( brd_stack_item )
  202. break;
  203. }
  204. // Update panel stackup info if needed. If the board stackup item is not found
  205. // just refresh the default values
  206. if( brd_stack_item != nullptr && aFullSync )
  207. {
  208. *item = *brd_stack_item;
  209. if( item->IsMaterialEditable() )
  210. {
  211. wxTextCtrl* matName = dynamic_cast<wxTextCtrl*>( ui_row_item.m_MaterialCtrl );
  212. if( matName )
  213. matName->SetValue( item->m_Material );
  214. }
  215. if( item->IsThicknessEditable() )
  216. {
  217. wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_ThicknessCtrl );
  218. if( textCtrl )
  219. textCtrl->SetValue( StringFromValue( m_units, item->m_Thickness, true, true ) );
  220. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  221. {
  222. wxCheckBox* cb_box = dynamic_cast<wxCheckBox*> ( ui_row_item.m_ThicknessLockCtrl );
  223. if( cb_box )
  224. cb_box->SetValue( item->m_ThicknessLocked );
  225. }
  226. }
  227. if( item->IsColorEditable() )
  228. {
  229. auto bm_combo = dynamic_cast<wxBitmapComboBox*>( ui_row_item.m_ColorCtrl );
  230. int color_idx = 0;
  231. if( item->m_Color.StartsWith( "#" ) ) // User defined color
  232. {
  233. wxColour color( item->m_Color );
  234. m_UserColors[row] = color;
  235. color_idx = GetColorUserDefinedListIdx();
  236. if( bm_combo ) // Update user color shown in the wxBitmapComboBox
  237. {
  238. bm_combo->SetString( color_idx, color.GetAsString( wxC2S_HTML_SYNTAX ) );
  239. wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
  240. LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D(), COLOR4D( color ) );
  241. bm_combo->SetItemBitmap( color_idx, layerbmp );
  242. }
  243. }
  244. else
  245. {
  246. const FAB_LAYER_COLOR* color_list = GetColorStandardList();
  247. for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
  248. {
  249. if( color_list[ii].m_ColorName == item->m_Color )
  250. {
  251. color_idx = ii;
  252. break;
  253. }
  254. }
  255. }
  256. if( bm_combo )
  257. bm_combo->SetSelection( color_idx );
  258. }
  259. if( item->HasEpsilonRValue() )
  260. {
  261. wxString txt;
  262. txt.Printf( "%.1f", item->m_EpsilonR );
  263. wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_EpsilonCtrl );
  264. if( textCtrl )
  265. textCtrl->SetValue( txt );
  266. }
  267. if( item->HasLossTangentValue() )
  268. {
  269. wxString txt;
  270. txt.Printf( "%g", item->m_LossTangent );
  271. wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ui_row_item.m_LossTgCtrl );
  272. if( textCtrl )
  273. textCtrl->SetValue( txt );
  274. }
  275. }
  276. // Now enable/disable stackup items, according to the m_enabledLayers config
  277. bool show_item;
  278. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  279. // the m_DielectricLayerId is not a copper layer id, it is a dielectric idx from 1
  280. show_item = item->m_DielectricLayerId < copperLayersCount;
  281. else
  282. show_item = m_enabledLayers[item->m_LayerId];
  283. item->m_Enabled = show_item;
  284. ui_row_item.m_isEnabled = show_item;
  285. // Show or not items of this row:
  286. ui_row_item.m_Icon->Show( show_item );
  287. ui_row_item.m_LayerName->Show( show_item );
  288. ui_row_item.m_LayerTypeCtrl->Show( show_item );
  289. ui_row_item.m_MaterialCtrl->Show( show_item );
  290. if( ui_row_item.m_MaterialButt )
  291. ui_row_item.m_MaterialButt->Show( show_item );
  292. ui_row_item.m_ThicknessCtrl->Show( show_item );
  293. ui_row_item.m_ThicknessLockCtrl->Show( show_item );
  294. ui_row_item.m_ColorCtrl->Show( show_item );
  295. ui_row_item.m_EpsilonCtrl->Show( show_item );
  296. ui_row_item.m_LossTgCtrl->Show( show_item );
  297. row++;
  298. }
  299. updateIconColor();
  300. }
  301. void PANEL_SETUP_BOARD_STACKUP::addMaterialChooser( wxWindowID aId,
  302. const wxString * aMaterialName,
  303. BOARD_STACKUP_ROW_UI_ITEM& aUiRowItem )
  304. {
  305. wxBoxSizer* bSizerMat = new wxBoxSizer( wxHORIZONTAL );
  306. m_fgGridSizer->Add( bSizerMat, 1, wxEXPAND, 2 );
  307. wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY );
  308. if( aMaterialName )
  309. textCtrl->SetValue( *aMaterialName );
  310. textCtrl->SetMinSize( m_numericTextCtrlSize );
  311. bSizerMat->Add( textCtrl, 0, wxTOP|wxBOTTOM|wxLEFT, 5 );
  312. wxButton* m_buttonMat = new wxButton( m_scGridWin, aId, _("..."),
  313. wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT );
  314. bSizerMat->Add( m_buttonMat, 0, wxALIGN_CENTER_VERTICAL, 2 );
  315. m_buttonMat->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
  316. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onMaterialChange ),
  317. NULL, this );
  318. m_controlItemsList.push_back( m_buttonMat );
  319. aUiRowItem.m_MaterialCtrl = textCtrl;
  320. aUiRowItem.m_MaterialButt = m_buttonMat;
  321. }
  322. wxControl* PANEL_SETUP_BOARD_STACKUP::addSpacer()
  323. {
  324. wxStaticText* emptyText = new wxStaticText( m_scGridWin, wxID_ANY, wxEmptyString );
  325. m_fgGridSizer->Add( emptyText, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND );
  326. return emptyText;
  327. }
  328. void PANEL_SETUP_BOARD_STACKUP::buildLayerStackPanel()
  329. {
  330. // for dielectric: layer type keyword is "core"
  331. m_core_prepreg_choice.Add( _( "Core" ) );
  332. // for dielectric: layer type keyword is "prepreg"
  333. m_core_prepreg_choice.Add( _( "PrePreg" ) );
  334. // Build a full stackup for the dialog, with a active copper layer count
  335. // = current board layer count to calculate a reasonable default
  336. // dielectric thickness, for board having no stackup initalized:
  337. m_stackup.BuildDefaultStackupList( nullptr, m_brdSettings->GetCopperLayerCount() );
  338. int row = 0;
  339. const FAB_LAYER_COLOR* color_list = GetColorStandardList();
  340. for( auto item : m_stackup.GetList() )
  341. {
  342. BOARD_STACKUP_ROW_UI_ITEM ui_row_item;
  343. bool show_item = true;//false;
  344. ui_row_item.m_isEnabled = true;
  345. // Reserve room in m_UserColors to store usercolor definition
  346. m_UserColors.push_back( color_list[GetColorUserDefinedListIdx()].m_Color );
  347. // Add color swatch icon. The color will be updated later,
  348. // when all widgets are initialized
  349. wxStaticBitmap* bitmap = new wxStaticBitmap( m_scGridWin, wxID_ANY, wxNullBitmap );
  350. m_fgGridSizer->Add( bitmap, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT );
  351. ui_row_item.m_Icon = bitmap;
  352. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  353. {
  354. wxString lname;
  355. lname.Printf( _( "Dielectric %d" ), item->m_DielectricLayerId );
  356. wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
  357. m_fgGridSizer->Add( st_text, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  358. ui_row_item.m_LayerName = st_text;
  359. wxChoice* choice = new wxChoice( m_scGridWin, wxID_ANY, wxDefaultPosition,
  360. wxDefaultSize, m_core_prepreg_choice );
  361. choice->SetSelection( item->m_TypeName == KEY_CORE ? 0 : 1 );
  362. m_fgGridSizer->Add( choice, 0, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  363. ui_row_item.m_LayerTypeCtrl = choice;
  364. }
  365. else
  366. {
  367. item->m_LayerName = m_board->GetLayerName( item->m_LayerId );
  368. wxStaticText* st_text = new wxStaticText( m_scGridWin, wxID_ANY, item->m_LayerName );
  369. m_fgGridSizer->Add( st_text, 0, wxALL|wxALIGN_CENTER_VERTICAL, 1 );
  370. st_text->Show( show_item );
  371. ui_row_item.m_LayerName = st_text;
  372. wxString lname;
  373. if( item->m_TypeName == KEY_COPPER )
  374. lname = _( "Copper" );
  375. else
  376. lname = wxGetTranslation( item->m_TypeName );
  377. st_text = new wxStaticText( m_scGridWin, wxID_ANY, lname );
  378. m_fgGridSizer->Add( st_text, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  379. ui_row_item.m_LayerTypeCtrl = st_text;
  380. }
  381. if( item->IsMaterialEditable() )
  382. {
  383. addMaterialChooser( ID_ITEM_MATERIAL+row, &item->m_Material, ui_row_item );
  384. }
  385. else
  386. {
  387. ui_row_item.m_MaterialCtrl = addSpacer();
  388. }
  389. if( item->IsThicknessEditable() )
  390. {
  391. wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, ID_ITEM_THICKNESS+row );
  392. textCtrl->SetMinSize( m_numericTextCtrlSize );
  393. textCtrl->SetValue( StringFromValue( m_units, item->m_Thickness, true, true ) );
  394. m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  395. m_controlItemsList.push_back( textCtrl );
  396. textCtrl->Connect( wxEVT_COMMAND_TEXT_UPDATED,
  397. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onThicknessChange ),
  398. NULL, this );
  399. ui_row_item.m_ThicknessCtrl = textCtrl;
  400. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  401. {
  402. wxCheckBox* cb_box = new wxCheckBox( m_scGridWin, ID_ITEM_THICKNESS_LOCKED+row,
  403. wxEmptyString );
  404. cb_box->SetValue( item->m_ThicknessLocked );
  405. m_fgGridSizer->Add( cb_box, 0, wxALIGN_CENTER_VERTICAL, 2 );
  406. ui_row_item.m_ThicknessLockCtrl = cb_box;
  407. }
  408. else
  409. {
  410. ui_row_item.m_ThicknessLockCtrl = addSpacer();
  411. }
  412. }
  413. else
  414. {
  415. ui_row_item.m_ThicknessCtrl = addSpacer();
  416. ui_row_item.m_ThicknessLockCtrl = addSpacer();
  417. }
  418. if( item->IsColorEditable() )
  419. {
  420. int color_idx = 0;
  421. if( item->m_Color.StartsWith( "#" ) ) // User defined color
  422. {
  423. wxColour color( item->m_Color );
  424. m_UserColors[row] = color;
  425. color_idx = GetColorUserDefinedListIdx();
  426. }
  427. else
  428. {
  429. for( int ii = 0; ii < GetColorStandardListCount(); ii++ )
  430. {
  431. if( color_list[ii].m_ColorName == item->m_Color )
  432. {
  433. color_idx = ii;
  434. break;
  435. }
  436. }
  437. }
  438. wxBitmapComboBox* bm_combo = createBmComboBox( item, row );
  439. m_colorComboSize.y = bm_combo->GetSize().y;
  440. bm_combo->SetMinSize( m_colorComboSize );
  441. m_fgGridSizer->Add( bm_combo, 0, wxEXPAND|wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  442. bm_combo->SetSelection( color_idx );
  443. ui_row_item.m_ColorCtrl = bm_combo;
  444. }
  445. else
  446. {
  447. ui_row_item.m_ColorCtrl = addSpacer();
  448. }
  449. if( item->HasEpsilonRValue() )
  450. {
  451. wxString txt;
  452. txt.Printf( "%.1f", item->m_EpsilonR );
  453. wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
  454. wxDefaultPosition, m_numericFieldsSize );
  455. textCtrl->SetValue( txt );
  456. m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  457. ui_row_item.m_EpsilonCtrl = textCtrl;
  458. }
  459. else
  460. {
  461. ui_row_item.m_EpsilonCtrl = addSpacer();
  462. }
  463. if( item->HasLossTangentValue() )
  464. {
  465. wxString txt;
  466. txt.Printf( "%g", item->m_LossTangent );
  467. wxTextCtrl* textCtrl = new wxTextCtrl( m_scGridWin, wxID_ANY, wxEmptyString,
  468. wxDefaultPosition, m_numericFieldsSize );
  469. textCtrl->SetValue( txt );
  470. m_fgGridSizer->Add( textCtrl, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 2 );
  471. ui_row_item.m_LossTgCtrl = textCtrl;
  472. }
  473. else
  474. {
  475. ui_row_item.m_LossTgCtrl = addSpacer();
  476. }
  477. m_rowUiItemsList.push_back( ui_row_item );
  478. row++;
  479. }
  480. // Get the translated list of choices and init m_choiceFinish
  481. wxArrayString finish_list = GetCopperFinishStandardList( true );
  482. m_choiceFinish->Append( finish_list );
  483. m_choiceFinish->SetSelection( 0 ); // Will be correctly set later
  484. updateIconColor();
  485. m_scGridWin->Layout();
  486. }
  487. // Transfer current UI settings to m_stackup but not to the board
  488. bool PANEL_SETUP_BOARD_STACKUP::transferDataFromUIToStackup()
  489. {
  490. // First, verify the list of layers currently in stackup:
  491. // if it does not mach the list of layers set in PANEL_SETUP_LAYERS
  492. // prompt the user to update the stackup
  493. // the current enabled layers in PANEL_SETUP_LAYERS
  494. LSET layersList = m_panelLayers->GetUILayerMask() & BOARD_STACKUP::StackupAllowedBrdLayers();
  495. if( m_enabledLayers != layersList )
  496. {
  497. wxMessageBox( _( "Stackup not up to date. Verify it" ) );
  498. return false;
  499. }
  500. // The board thickness and the thickness from stackup settings should be compatible
  501. // so verify that compatibility
  502. int pcbTickness = GetPcbTickness() ;
  503. int stackup_thickness = 0;
  504. bool thickness_error = false; // Set to true if a negative thickness value is found
  505. for( auto item : m_stackup.GetList() )
  506. {
  507. if( item->IsThicknessEditable() && item->m_Enabled )
  508. {
  509. stackup_thickness += item->m_Thickness;
  510. if( item->m_Thickness < 0 )
  511. thickness_error = true;
  512. }
  513. }
  514. if( thickness_error )
  515. {
  516. wxMessageBox( _( "A layer thickness is < 0. Fix it" ) );
  517. return false;
  518. }
  519. int delta = std::abs( stackup_thickness - pcbTickness );
  520. double relative_error = (double)delta/pcbTickness;
  521. // warn user if relative_error > 0.01
  522. const double relative_error_max = 0.01;
  523. if( relative_error > relative_error_max )
  524. {
  525. wxString msg;
  526. msg.Printf( _( "Board thickness %s differs from stackup thickness %s\n"
  527. "Allowed max error %s" ),
  528. StringFromValue( m_units, pcbTickness, true, true ),
  529. StringFromValue( m_units, stackup_thickness, true, true ),
  530. StringFromValue( m_units, KiROUND( relative_error_max*pcbTickness),
  531. true, true ) );
  532. wxMessageBox( msg );
  533. return false;
  534. }
  535. int row = 0;
  536. wxString txt;
  537. wxString error_msg;
  538. bool success = true;
  539. double value;
  540. for( auto item : m_stackup.GetList() )
  541. {
  542. // Skip stackup items useless for the current board
  543. if( !item->m_Enabled )
  544. {
  545. row++;
  546. continue;
  547. }
  548. item->m_LayerName = m_rowUiItemsList[row].m_LayerName->GetLabel();
  549. if( item->HasEpsilonRValue() )
  550. {
  551. wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
  552. txt = textCtrl->GetValue();
  553. if( txt.ToDouble( &value ) && value >= 0.0 )
  554. item->m_EpsilonR = value;
  555. else if( txt.ToCDouble( &value ) && value >= 0.0 )
  556. item->m_EpsilonR = value;
  557. else
  558. {
  559. success = false;
  560. error_msg << _( "Incorrect value for Epsilon R (Epsilon R must be positive or null if not used)" );
  561. }
  562. }
  563. if( item->HasLossTangentValue() )
  564. {
  565. wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
  566. txt = textCtrl->GetValue();
  567. if( txt.ToDouble( &value ) && value >= 0.0 )
  568. item->m_LossTangent = value;
  569. else if( txt.ToCDouble( &value ) && value >= 0.0 )
  570. item->m_LossTangent = value;
  571. else
  572. {
  573. success = false;
  574. if( !error_msg.IsEmpty() )
  575. error_msg << "\n";
  576. error_msg << _( "Incorrect value for Loss tg (Loss tg must be positive or null if not used)" );
  577. }
  578. }
  579. if( item->IsMaterialEditable() )
  580. {
  581. wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
  582. item->m_Material = textCtrl->GetValue();
  583. }
  584. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  585. {
  586. // Choice is Core or Prepreg:
  587. wxChoice* choice = static_cast<wxChoice*>( m_rowUiItemsList[row].m_LayerTypeCtrl );
  588. int idx = choice->GetSelection();
  589. if( idx == 0 )
  590. item->m_TypeName = KEY_CORE;
  591. else
  592. item->m_TypeName = KEY_PREPREG;
  593. }
  594. if( item->IsThicknessEditable() )
  595. {
  596. wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_ThicknessCtrl );
  597. txt = textCtrl->GetValue();
  598. item->m_Thickness = ValueFromString( m_frame->GetUserUnits(), txt, true );
  599. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  600. {
  601. // Dielectric thickness layer can have a locked thickness:
  602. wxCheckBox* cb_box = static_cast<wxCheckBox*>
  603. ( m_rowUiItemsList[row].m_ThicknessLockCtrl );
  604. item->m_ThicknessLocked = cb_box && cb_box->GetValue();
  605. }
  606. }
  607. if( item->IsColorEditable() )
  608. {
  609. const FAB_LAYER_COLOR* color_list = GetColorStandardList();
  610. wxBitmapComboBox* choice = static_cast<wxBitmapComboBox*>( m_rowUiItemsList[row].m_ColorCtrl );
  611. int idx = choice->GetSelection();
  612. if( idx == GetColorUserDefinedListIdx() )
  613. item->m_Color = m_UserColors[row].GetAsString( wxC2S_HTML_SYNTAX );
  614. else
  615. item->m_Color = color_list[idx].m_ColorName;
  616. }
  617. row++;
  618. }
  619. if( !success )
  620. {
  621. wxMessageBox( error_msg, _( "Errors" ) );
  622. return false;
  623. }
  624. wxArrayString finish_list = GetCopperFinishStandardList( false );
  625. int finish = m_choiceFinish->GetSelection() >= 0 ? m_choiceFinish->GetSelection() : 0;
  626. m_stackup.m_FinishType = finish_list[finish];
  627. m_stackup.m_HasDielectricConstrains = m_rbDielectricConstraint->GetSelection() == 1;
  628. m_stackup.m_EdgeConnectorConstraints = (BS_EDGE_CONNECTOR_CONSTRAINTS)m_choiceEdgeConn->GetSelection();
  629. m_stackup.m_CastellatedPads = m_cbCastellatedPads->GetValue();
  630. m_stackup.m_EdgePlating = m_cbEgdesPlated->GetValue();
  631. return true;
  632. }
  633. bool PANEL_SETUP_BOARD_STACKUP::TransferDataFromWindow()
  634. {
  635. if( !transferDataFromUIToStackup() )
  636. return false;
  637. BOARD_STACKUP& brd_stackup = m_brdSettings->GetStackupDescriptor();
  638. brd_stackup.m_FinishType = m_stackup.m_FinishType;
  639. brd_stackup.m_HasDielectricConstrains = m_stackup.m_HasDielectricConstrains;
  640. brd_stackup.m_EdgeConnectorConstraints = m_stackup.m_EdgeConnectorConstraints;
  641. brd_stackup.m_CastellatedPads = m_stackup.m_CastellatedPads;
  642. brd_stackup.m_EdgePlating = m_stackup.m_EdgePlating;
  643. // copy enabled items to the new board stackup
  644. brd_stackup.RemoveAll();
  645. for( auto item : m_stackup.GetList() )
  646. {
  647. if( item->m_Enabled )
  648. brd_stackup.Add( new BOARD_STACKUP_ITEM( *item ) );
  649. }
  650. m_brdSettings->SetBoardThickness( GetPcbTickness() );
  651. m_brdSettings->m_HasStackup = true;
  652. return true;
  653. }
  654. void PANEL_SETUP_BOARD_STACKUP::ImportSettingsFrom( BOARD* aBoard )
  655. {
  656. BOARD* savedBrd = m_board;
  657. BOARD_DESIGN_SETTINGS* savedSettings = m_brdSettings;
  658. m_brdSettings = &aBoard->GetDesignSettings();
  659. m_enabledLayers = m_panelLayers->GetUILayerMask() & BOARD_STACKUP::StackupAllowedBrdLayers();
  660. synchronizeWithBoard( true );
  661. m_brdSettings = savedSettings;
  662. m_board = savedBrd;
  663. Layout();
  664. Refresh();
  665. }
  666. void PANEL_SETUP_BOARD_STACKUP::OnLayersOptionsChanged( LSET aNewLayerSet )
  667. {
  668. // First, verify the list of layers currently in stackup:
  669. // if it does not mach the list of layers set in PANEL_SETUP_LAYERS
  670. // rebuild the panel
  671. // the current enabled layers in PANEL_SETUP_LAYERS
  672. LSET layersList = m_panelLayers->GetUILayerMask() & BOARD_STACKUP::StackupAllowedBrdLayers();
  673. if( m_enabledLayers != layersList )
  674. {
  675. m_enabledLayers = layersList;
  676. synchronizeWithBoard( false );
  677. Layout();
  678. Refresh();
  679. }
  680. }
  681. void PANEL_SETUP_BOARD_STACKUP::onCalculateDielectricThickness( wxCommandEvent& event )
  682. {
  683. // Collect thickness of all layers but dielectric
  684. int thickness = 0;
  685. int fixed_thickness_cnt = 0;
  686. bool thickness_error = false; // True if a locked thickness value in list is < 0
  687. int row = 0;
  688. for( auto item : m_stackup.GetList() )
  689. {
  690. if( !item->IsThicknessEditable() || !item->m_Enabled )
  691. {
  692. row++;
  693. continue;
  694. }
  695. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  696. {
  697. wxCheckBox* checkBox = static_cast<wxCheckBox*>( m_rowUiItemsList[row].m_ThicknessLockCtrl );
  698. if( !checkBox->GetValue() ) // Only not locked dielectric thickness can be modified
  699. {
  700. row++;
  701. continue;
  702. }
  703. else
  704. {
  705. fixed_thickness_cnt++;
  706. if( item->m_Thickness < 0 )
  707. thickness_error = true;
  708. }
  709. }
  710. thickness += item->m_Thickness;
  711. row++;
  712. }
  713. if( thickness_error )
  714. {
  715. wxMessageBox( _( "A locked dielectric thickness is < 0\n"
  716. "Unlock it or change its thickness") );
  717. return;
  718. }
  719. LSET copperMask = m_enabledLayers & ( LSET::ExternalCuMask() | LSET::InternalCuMask() );
  720. int copperLayersCount = copperMask.count();
  721. // the number of adjustable dielectric layers must obvioulsly be > 0
  722. // So verify the user has at least one dielectric layer free
  723. int adjustableDielectricCount = copperLayersCount - 1 - fixed_thickness_cnt;
  724. if( adjustableDielectricCount <= 0 )
  725. {
  726. wxMessageBox( _( "Cannot calculate dielectric thickness\n"
  727. "At least one dielectric layer must be not locked") );
  728. return;
  729. }
  730. int dielectric_thickness = GetPcbTickness() - thickness;
  731. if( dielectric_thickness <= 0 ) // fixed thickness is too big: cannot calculate free thickness
  732. {
  733. wxMessageBox( _( "Cannot calculate dielectric thickness\n"
  734. "Fixed thickness too big or board thickness too small") );
  735. return;
  736. }
  737. dielectric_thickness /= adjustableDielectricCount;
  738. // Update items thickness, and the values displayed on screen
  739. row = 0;
  740. for( auto item : m_stackup.GetList() )
  741. {
  742. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC && item->m_Enabled )
  743. {
  744. wxCheckBox* checkBox = static_cast<wxCheckBox*>( m_rowUiItemsList[row].m_ThicknessLockCtrl );
  745. if( !checkBox->GetValue() ) // Not locked thickness: can be modified
  746. {
  747. item->m_Thickness = dielectric_thickness;
  748. wxTextCtrl* textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_ThicknessCtrl );
  749. textCtrl->SetValue( StringFromValue( m_units, item->m_Thickness, true, true ) );
  750. }
  751. }
  752. row++;
  753. }
  754. }
  755. void PANEL_SETUP_BOARD_STACKUP::onColorSelected( wxCommandEvent& event )
  756. {
  757. int idx = event.GetSelection();
  758. int item_id = event.GetId();
  759. int row = item_id - ID_ITEM_COLOR;
  760. wxASSERT( (int)m_UserColors.size() > row );
  761. if( GetColorStandardListCount()-1 == idx ) // Set user color is the last option in list
  762. {
  763. wxColourDialog dlg( this );
  764. if( dlg.ShowModal() == wxID_OK )
  765. {
  766. wxBitmapComboBox* combo = static_cast<wxBitmapComboBox*>( FindWindowById( item_id ) );
  767. wxColour color = dlg.GetColourData().GetColour();
  768. m_UserColors[row] = color;
  769. combo->SetString( idx, color.GetAsString( wxC2S_HTML_SYNTAX ) );
  770. wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
  771. LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
  772. COLOR4D( color ) );
  773. combo->SetItemBitmap( combo->GetCount()-1, layerbmp );
  774. }
  775. }
  776. updateIconColor( row );
  777. }
  778. void PANEL_SETUP_BOARD_STACKUP::onMaterialChange( wxCommandEvent& event )
  779. {
  780. // Ensure m_materialList contains all materials already in use in stackup list
  781. // and add it is missing
  782. if( !transferDataFromUIToStackup() )
  783. return;
  784. for( BOARD_STACKUP_ITEM* item : m_stackup.GetList() )
  785. {
  786. DIELECTRIC_SUBSTRATE_LIST* mat_list = nullptr;
  787. if( item->m_Type == BS_ITEM_TYPE_DIELECTRIC )
  788. mat_list = &m_delectricMatList;
  789. else if( item->m_Type == BS_ITEM_TYPE_SOLDERMASK )
  790. mat_list = &m_solderMaskMatList;
  791. else if( item->m_Type == BS_ITEM_TYPE_SILKSCREEN )
  792. mat_list = &m_silkscreenMatList;
  793. else
  794. continue;
  795. int idx = mat_list->FindSubstrate( item->m_Material,
  796. item->m_EpsilonR,
  797. item->m_LossTangent );
  798. if( idx < 0 && !item->m_Material.IsEmpty() )
  799. {
  800. // This material is not in list: add it
  801. DIELECTRIC_SUBSTRATE new_mat;
  802. new_mat.m_Name = item->m_Material;
  803. new_mat.m_EpsilonR = item->m_EpsilonR;
  804. new_mat.m_LossTangent = item->m_LossTangent;
  805. mat_list->AppendSubstrate( new_mat );
  806. }
  807. }
  808. int row = event.GetId() - ID_ITEM_MATERIAL;
  809. BOARD_STACKUP_ITEM* item = GetStackupItem( row );
  810. DIELECTRIC_SUBSTRATE_LIST* item_mat_list = nullptr;
  811. switch( item->m_Type )
  812. {
  813. case BS_ITEM_TYPE_DIELECTRIC:
  814. item_mat_list = &m_delectricMatList;
  815. break;
  816. case BS_ITEM_TYPE_SOLDERMASK:
  817. item_mat_list = &m_solderMaskMatList;
  818. break;
  819. case BS_ITEM_TYPE_SILKSCREEN:
  820. item_mat_list = &m_silkscreenMatList;
  821. break;
  822. default:
  823. item_mat_list = nullptr;
  824. break;
  825. }
  826. DIALOG_DIELECTRIC_MATERIAL dlg( this, *item_mat_list );
  827. if( dlg.ShowModal() != wxID_OK )
  828. return;
  829. DIELECTRIC_SUBSTRATE substrate = dlg.GetSelectedSubstrate();
  830. if( substrate.m_Name.IsEmpty() ) // No substrate specified
  831. return;
  832. // Update Name, Epsilon R and Loss tg
  833. item->m_Material = substrate.m_Name;
  834. item->m_EpsilonR = substrate.m_EpsilonR;
  835. item->m_LossTangent = substrate.m_LossTangent;
  836. wxTextCtrl* textCtrl;
  837. textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_MaterialCtrl );
  838. textCtrl->SetValue( item->m_Material );
  839. textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_EpsilonCtrl );
  840. textCtrl->SetValue( item->FormatEpsilonR() );
  841. textCtrl = static_cast<wxTextCtrl*>( m_rowUiItemsList[row].m_LossTgCtrl );
  842. textCtrl->SetValue( item->FormatLossTangent() );
  843. }
  844. void PANEL_SETUP_BOARD_STACKUP::onThicknessChange( wxCommandEvent& event )
  845. {
  846. int row = event.GetId() - ID_ITEM_THICKNESS;
  847. wxString value = event.GetString();
  848. BOARD_STACKUP_ITEM* item = GetStackupItem( row );
  849. item->m_Thickness = ValueFromString( m_frame->GetUserUnits(), value, true );
  850. }
  851. BOARD_STACKUP_ITEM* PANEL_SETUP_BOARD_STACKUP::GetStackupItem( int aIndex )
  852. {
  853. return m_stackup.GetStackupLayer( aIndex );
  854. }
  855. wxColor PANEL_SETUP_BOARD_STACKUP::getColorIconItem( int aRow )
  856. {
  857. BOARD_STACKUP_ITEM* layer = GetStackupItem( aRow );
  858. wxColor color;
  859. switch( layer->m_Type )
  860. {
  861. case BS_ITEM_TYPE_COPPER:
  862. color = copperColor;
  863. break;
  864. case BS_ITEM_TYPE_DIELECTRIC:
  865. color = dielectricColor;
  866. break;
  867. case BS_ITEM_TYPE_SOLDERMASK:
  868. color = GetSelectedColor( aRow );
  869. break;
  870. case BS_ITEM_TYPE_SILKSCREEN:
  871. color = GetSelectedColor( aRow );
  872. break;
  873. case BS_ITEM_TYPE_SOLDERPASTE:
  874. color = pasteColor;
  875. break;
  876. case BS_ITEM_TYPE_UNDEFINED: // Should not happen
  877. wxASSERT( 0 );
  878. break;
  879. }
  880. return color;
  881. }
  882. void PANEL_SETUP_BOARD_STACKUP::updateIconColor( int aRow )
  883. {
  884. if( aRow >= 0 )
  885. {
  886. wxStaticBitmap* st_bitmap = m_rowUiItemsList[aRow].m_Icon;
  887. // explicit depth important under MSW
  888. wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
  889. drawBitmap( bmp, getColorIconItem( aRow ) );
  890. st_bitmap->SetBitmap( bmp );
  891. return;
  892. }
  893. for( unsigned row = 0; row < m_rowUiItemsList.size(); row++ )
  894. {
  895. // explicit depth important under MSW
  896. wxBitmap bmp(m_colorIconsSize.x, m_colorIconsSize.y / 2, 24);
  897. drawBitmap( bmp, getColorIconItem( row ) );
  898. m_rowUiItemsList[row].m_Icon->SetBitmap( bmp );
  899. }
  900. }
  901. wxBitmapComboBox* PANEL_SETUP_BOARD_STACKUP::createBmComboBox( BOARD_STACKUP_ITEM* aStackupItem, int aRow )
  902. {
  903. wxBitmapComboBox* combo = new wxBitmapComboBox( m_scGridWin, ID_ITEM_COLOR+aRow,
  904. wxEmptyString, wxDefaultPosition,
  905. wxDefaultSize, 0, nullptr, wxCB_READONLY );
  906. // Fills the combo box with choice list + bitmaps
  907. for( const FAB_LAYER_COLOR* item = GetColorStandardList(); ; ++item )
  908. {
  909. if( item->m_ColorName.IsEmpty() )
  910. break;
  911. wxColor curr_color = item->m_Color;
  912. wxString label;
  913. // Defined colors have a name, the user color uses the HTML notation ( i.e. #FF0000)
  914. if( GetColorStandardListCount()-1 > (int)combo->GetCount() )
  915. label = wxGetTranslation( item->m_ColorName );
  916. else // Append the user color, if specified, else add a default user color
  917. {
  918. if( aStackupItem && aStackupItem->m_Color.StartsWith( "#" ) )
  919. {
  920. curr_color = wxColour( aStackupItem->m_Color );
  921. label = aStackupItem->m_Color;
  922. }
  923. else
  924. label = curr_color.GetAsString( wxC2S_HTML_SYNTAX );
  925. }
  926. wxBitmap layerbmp( m_colorSwatchesSize.x, m_colorSwatchesSize.y );
  927. LAYER_SELECTOR::DrawColorSwatch( layerbmp, COLOR4D( 0, 0, 0, 0 ),
  928. COLOR4D( curr_color ) );
  929. combo->Append( label, layerbmp );
  930. }
  931. #ifdef __WXGTK__
  932. // Adjust the minimal width. On GTK, the size calculated by wxWidgets is not good
  933. // bitmaps are ignored
  934. combo->Fit();
  935. int min_width = combo->GetSize().x;
  936. min_width += m_colorSwatchesSize.x;
  937. combo->SetMinSize( wxSize( min_width, -1 ) );
  938. #endif
  939. // add the wxBitmapComboBox to wxControl list, to be able to disconnect the event
  940. // on exit
  941. m_controlItemsList.push_back( combo );
  942. combo->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED,
  943. wxCommandEventHandler( PANEL_SETUP_BOARD_STACKUP::onColorSelected ),
  944. NULL, this );
  945. return combo;
  946. }
  947. void drawBitmap( wxBitmap& aBitmap, wxColor aColor )
  948. {
  949. wxNativePixelData data( aBitmap );
  950. wxNativePixelData::Iterator p(data);
  951. for( int yy = 0; yy < data.GetHeight(); yy++ )
  952. {
  953. wxNativePixelData::Iterator rowStart = p;
  954. for( int xx = 0; xx < data.GetWidth(); xx++ )
  955. {
  956. p.Red() = aColor.Red();
  957. p.Green() = aColor.Green();
  958. p.Blue() = aColor.Blue();
  959. ++p;
  960. }
  961. p = rowStart;
  962. p.OffsetY(data, 1);
  963. }
  964. }