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.

526 lines
19 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include "dialogs/dialog_create_array.h"
  24. #include <base_units.h>
  25. #include <widgets/text_ctrl_eval.h>
  26. #include <board.h>
  27. #include <footprint.h>
  28. #include <pcb_edit_frame.h>
  29. #include <wx/msgdlg.h>
  30. #include <boost/algorithm/string/join.hpp>
  31. /**
  32. * Struct containing the last-entered values for the dialog.
  33. */
  34. struct CREATE_ARRAY_DIALOG_ENTRIES
  35. {
  36. /**
  37. * Construct with some sensible defaults.
  38. * In future, this could be loaded from config?
  39. */
  40. CREATE_ARRAY_DIALOG_ENTRIES() :
  41. m_OptionsSet( true ),
  42. m_GridNx( 5 ),
  43. m_GridNy( 5 ),
  44. m_GridDx( Millimeter2iu( 2.54 ) ),
  45. m_GridDy( Millimeter2iu( 2.54 ) ),
  46. m_GridOffsetX( 0 ),
  47. m_GridOffsetY( 0 ),
  48. m_GridStagger( 1 ),
  49. m_GridStaggerType( 0 ), // rows
  50. m_GridNumberingAxis( 0 ), // h then v
  51. m_GridNumReverseAlt( false ),
  52. m_GridNumStartSet( 1 ), // use specified start
  53. m_Grid2dArrayNumbering( 0 ), // linear numbering
  54. m_GridPrimaryAxisScheme( 0 ), // numeric
  55. m_GridSecondaryAxisScheme( 0 ), // numeric
  56. m_GridPrimaryNumOffset( "1" ), // numeric
  57. m_GridSecondaryNumOffset( "1" ), // numeric
  58. m_GridPrimaryAxisStep( 1 ),
  59. m_GridSecondaryAxisStep( 1 ),
  60. m_CircCentreX( 0 ),
  61. m_CircCentreY( 0 ),
  62. m_CircAngle( 0.0 ),
  63. m_CircCount( 4 ),
  64. m_CircNumStartSet( 1 ), // use specified start
  65. m_GridCircNumScheme( 0 ),
  66. m_CircNumberingOffset( "1" ),
  67. m_CircNumberingStep( 1 ),
  68. m_CircRotatationStep( false ),
  69. m_ArrayTypeTab( 0 ), // start on grid view
  70. m_FootprintKeepAnnotations( false ),
  71. m_FootprintReannotate( true ) // Assign unique by default
  72. {
  73. }
  74. bool m_OptionsSet;
  75. long m_GridNx;
  76. long m_GridNy;
  77. long m_GridDx;
  78. long m_GridDy;
  79. long m_GridOffsetX;
  80. long m_GridOffsetY;
  81. long m_GridStagger;
  82. long m_GridStaggerType;
  83. long m_GridNumberingAxis;
  84. bool m_GridNumReverseAlt;
  85. long m_GridNumStartSet;
  86. long m_Grid2dArrayNumbering;
  87. long m_GridPrimaryAxisScheme;
  88. long m_GridSecondaryAxisScheme;
  89. wxString m_GridPrimaryNumOffset;
  90. wxString m_GridSecondaryNumOffset;
  91. long m_GridPrimaryAxisStep;
  92. long m_GridSecondaryAxisStep;
  93. long m_CircCentreX;
  94. long m_CircCentreY;
  95. long m_CircAngle;
  96. long m_CircCount;
  97. long m_CircNumStartSet;
  98. long m_GridCircNumScheme;
  99. wxString m_CircNumberingOffset;
  100. long m_CircNumberingStep;
  101. bool m_CircRotatationStep;
  102. long m_ArrayTypeTab;
  103. bool m_FootprintKeepAnnotations;
  104. bool m_FootprintReannotate;
  105. };
  106. // Persistent options settings
  107. static CREATE_ARRAY_DIALOG_ENTRIES s_arrayOptions;
  108. /**
  109. * Local mapping for list-box <-> numbering type
  110. */
  111. struct NUMBERING_LIST_DATA
  112. {
  113. ARRAY_AXIS::NUMBERING_TYPE m_numbering_type;
  114. wxString m_label;
  115. };
  116. /**
  117. * List of type <--> name mappings (in order) for the numbering type
  118. * list boxes
  119. */
  120. static const std::vector<NUMBERING_LIST_DATA> numberingTypeData {
  121. {
  122. ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC,
  123. _( "Numerals (0,1,2,...,9,10)" ),
  124. },
  125. {
  126. ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_HEX,
  127. _( "Hexadecimal (0,1,...,F,10,...)" ),
  128. },
  129. {
  130. ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_NO_IOSQXZ,
  131. _( "Alphabet, minus IOSQXZ" ),
  132. },
  133. {
  134. ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_FULL,
  135. _( "Alphabet, full 26 characters" ),
  136. },
  137. };
  138. DIALOG_CREATE_ARRAY::DIALOG_CREATE_ARRAY( PCB_BASE_FRAME* aParent,
  139. std::unique_ptr<ARRAY_OPTIONS>& aSettings,
  140. bool aIsFootprintEditor, const wxPoint& aOrigPos ) :
  141. DIALOG_CREATE_ARRAY_BASE( aParent ),
  142. m_settings( aSettings ),
  143. m_originalItemPosition( aOrigPos ),
  144. m_isFootprintEditor( aIsFootprintEditor ),
  145. m_hSpacing( aParent, m_labelDx, m_entryDx, m_unitLabelDx ),
  146. m_vSpacing( aParent, m_labelDy, m_entryDy, m_unitLabelDy ),
  147. m_hOffset( aParent, m_labelOffsetX, m_entryOffsetX, m_unitLabelOffsetX ),
  148. m_vOffset( aParent, m_labelOffsetY, m_entryOffsetY, m_unitLabelOffsetY ),
  149. m_hCentre( aParent, m_labelCentreX, m_entryCentreX, m_unitLabelCentreX ),
  150. m_vCentre( aParent, m_labelCentreY, m_entryCentreY, m_unitLabelCentreY ),
  151. m_circRadius( aParent, m_labelCircRadius, m_valueCircRadius, m_unitLabelCircRadius ),
  152. m_circAngle( aParent, m_labelCircAngle, m_entryCircAngle, m_unitLabelCircAngle ),
  153. m_cfg_persister( s_arrayOptions.m_OptionsSet )
  154. {
  155. // Configure display origin transforms
  156. m_hSpacing.SetCoordType( ORIGIN_TRANSFORMS::REL_X_COORD );
  157. m_vSpacing.SetCoordType( ORIGIN_TRANSFORMS::REL_Y_COORD );
  158. m_hOffset.SetCoordType( ORIGIN_TRANSFORMS::REL_X_COORD );
  159. m_vOffset.SetCoordType( ORIGIN_TRANSFORMS::REL_Y_COORD );
  160. m_hCentre.SetCoordType( ORIGIN_TRANSFORMS::ABS_X_COORD );
  161. m_vCentre.SetCoordType( ORIGIN_TRANSFORMS::ABS_Y_COORD );
  162. // Set up numbering scheme drop downs character set strings
  163. for( const auto& numData : numberingTypeData )
  164. {
  165. const wxString label = wxGetTranslation( numData.m_label );
  166. void* clientData = (void*) &numData;
  167. m_choicePriAxisNumbering->Append( label, clientData );
  168. m_choiceSecAxisNumbering->Append( label, clientData );
  169. m_choiceCircNumbering->Append( label, clientData );
  170. }
  171. m_choicePriAxisNumbering->SetSelection( 0 );
  172. m_choiceSecAxisNumbering->SetSelection( 0 );
  173. m_choiceCircNumbering->SetSelection( 0 );
  174. m_circAngle.SetUnits( EDA_UNITS::DEGREES );
  175. // bind grid options to persister
  176. m_cfg_persister.Add( *m_entryNx, s_arrayOptions.m_GridNx );
  177. m_cfg_persister.Add( *m_entryNy, s_arrayOptions.m_GridNy );
  178. m_cfg_persister.Add( m_hSpacing, s_arrayOptions.m_GridDx );
  179. m_cfg_persister.Add( m_vSpacing, s_arrayOptions.m_GridDy );
  180. m_cfg_persister.Add( m_hOffset, s_arrayOptions.m_GridOffsetX );
  181. m_cfg_persister.Add( m_vOffset, s_arrayOptions.m_GridOffsetY );
  182. m_cfg_persister.Add( *m_entryStagger, s_arrayOptions.m_GridStagger );
  183. m_cfg_persister.Add( *m_radioBoxGridStaggerType, s_arrayOptions.m_GridStaggerType );
  184. m_cfg_persister.Add( *m_radioBoxGridNumberingAxis, s_arrayOptions.m_GridNumberingAxis );
  185. m_cfg_persister.Add( *m_checkBoxGridReverseNumbering, s_arrayOptions.m_GridNumReverseAlt );
  186. m_cfg_persister.Add( *m_rbGridStartNumberingOpt, s_arrayOptions.m_GridNumStartSet );
  187. m_cfg_persister.Add( *m_radioBoxGridNumberingScheme, s_arrayOptions.m_Grid2dArrayNumbering );
  188. m_cfg_persister.Add( *m_choicePriAxisNumbering, s_arrayOptions.m_GridPrimaryAxisScheme );
  189. m_cfg_persister.Add( *m_choiceSecAxisNumbering, s_arrayOptions.m_GridSecondaryAxisScheme );
  190. m_cfg_persister.Add( *m_entryGridPriNumberingOffset, s_arrayOptions.m_GridPrimaryNumOffset );
  191. m_cfg_persister.Add( *m_entryGridSecNumberingOffset, s_arrayOptions.m_GridSecondaryNumOffset );
  192. m_cfg_persister.Add( *m_entryGridPriNumberingStep, s_arrayOptions.m_GridPrimaryAxisStep );
  193. m_cfg_persister.Add( *m_entryGridSecNumberingStep, s_arrayOptions.m_GridSecondaryAxisStep );
  194. // bind circular options to persister
  195. m_cfg_persister.Add( m_hCentre, s_arrayOptions.m_CircCentreX );
  196. m_cfg_persister.Add( m_vCentre, s_arrayOptions.m_CircCentreY );
  197. m_cfg_persister.Add( m_circAngle, s_arrayOptions.m_CircAngle );
  198. m_cfg_persister.Add( *m_entryCircCount, s_arrayOptions.m_CircCount );
  199. m_cfg_persister.Add( *m_entryRotateItemsCb, s_arrayOptions.m_CircRotatationStep );
  200. m_cfg_persister.Add( *m_rbCircStartNumberingOpt, s_arrayOptions.m_CircNumStartSet );
  201. m_cfg_persister.Add( *m_choiceCircNumbering, s_arrayOptions.m_GridCircNumScheme );
  202. m_cfg_persister.Add( *m_entryCircNumberingStart, s_arrayOptions.m_CircNumberingOffset );
  203. m_cfg_persister.Add( *m_entryCircNumberingStep, s_arrayOptions.m_CircNumberingStep );
  204. m_cfg_persister.Add( *m_gridTypeNotebook, s_arrayOptions.m_ArrayTypeTab );
  205. m_cfg_persister.Add( *m_radioBtnKeepRefs, s_arrayOptions.m_FootprintKeepAnnotations );
  206. m_cfg_persister.Add( *m_radioBtnUniqueRefs, s_arrayOptions.m_FootprintReannotate );
  207. m_cfg_persister.RestoreConfigToControls();
  208. // Run the callbacks once to process the dialog contents
  209. setControlEnablement();
  210. calculateCircularArrayProperties();
  211. m_stdButtonsOK->SetDefault();
  212. Fit();
  213. SetMinSize( GetSize() );
  214. }
  215. void DIALOG_CREATE_ARRAY::OnParameterChanged( wxCommandEvent& event )
  216. {
  217. setControlEnablement();
  218. calculateCircularArrayProperties();
  219. }
  220. /**
  221. * Validate and save a long integer entry
  222. *
  223. * @param entry the text entry to read from
  224. * @param dest the value destination
  225. * @param description description of the field (used if the value is not OK)
  226. * @param errors a list of errors to add any error to
  227. * @return valid
  228. */
  229. static bool validateLongEntry( const wxTextEntry& entry, long& dest, const wxString& description,
  230. wxArrayString& errors )
  231. {
  232. bool ok = true;
  233. if( !entry.GetValue().ToLong( &dest ) )
  234. {
  235. wxString err;
  236. err.Printf( _( "Bad numeric value for %s: %s" ), description, entry.GetValue() );
  237. errors.Add( err );
  238. ok = false;
  239. }
  240. return ok;
  241. }
  242. /**
  243. * Validates and saves (if valid) the type and offset of an array axis numbering
  244. *
  245. * @param offsetEntry the entry of the offset (text)
  246. * @param typeEntry the entry of the axis nmbering scheme (choice)
  247. * @param type the destination of the type if valid
  248. * @param offset the destination of the offset if valid
  249. * @param errors error string accumulator
  250. * @return if all valid
  251. */
  252. static bool validateAxisOptions( const wxTextCtrl& offsetEntry, const wxChoice& typeEntry,
  253. const wxTextCtrl& aStepEntry, ARRAY_AXIS& aAxis,
  254. wxArrayString& errors )
  255. {
  256. void* clientData = typeEntry.GetClientData( typeEntry.GetSelection() );
  257. const NUMBERING_LIST_DATA* numberingData = static_cast<NUMBERING_LIST_DATA*>( clientData );
  258. wxCHECK_MSG( numberingData, false, "Failed to get client data from list control." );
  259. aAxis.SetAxisType( numberingData->m_numbering_type );
  260. const wxString text = offsetEntry.GetValue();
  261. bool ok = aAxis.SetOffset( text );
  262. if( !ok )
  263. {
  264. errors.Add( wxString::Format( _( "Could not determine numbering start from '%s': "
  265. "expected value consistent with alphabet '%s'." ),
  266. text,
  267. aAxis.GetAlphabet() ) );
  268. return false;
  269. }
  270. long step;
  271. ok = validateLongEntry( aStepEntry, step, _( "step value" ), errors );
  272. if( ok )
  273. aAxis.SetStep( step );
  274. return ok;
  275. }
  276. bool DIALOG_CREATE_ARRAY::TransferDataFromWindow()
  277. {
  278. std::unique_ptr<ARRAY_OPTIONS> newSettings;
  279. wxArrayString errors;
  280. const wxWindow* page = m_gridTypeNotebook->GetCurrentPage();
  281. if( page == m_gridPanel )
  282. {
  283. auto newGrid = std::make_unique<ARRAY_GRID_OPTIONS>();
  284. bool ok = true;
  285. // ints
  286. ok = ok && validateLongEntry(*m_entryNx, newGrid->m_nx, _("horizontal count"), errors);
  287. ok = ok && validateLongEntry(*m_entryNy, newGrid->m_ny, _("vertical count"), errors);
  288. newGrid->m_delta.x = m_hSpacing.GetValue();
  289. newGrid->m_delta.y = m_vSpacing.GetValue();
  290. newGrid->m_offset.x = m_hOffset.GetValue();
  291. newGrid->m_offset.y = m_vOffset.GetValue();
  292. ok = ok && validateLongEntry(*m_entryStagger, newGrid->m_stagger, _("stagger"), errors);
  293. newGrid->m_stagger_rows = m_radioBoxGridStaggerType->GetSelection() == 0;
  294. newGrid->m_horizontalThenVertical = m_radioBoxGridNumberingAxis->GetSelection() == 0;
  295. newGrid->m_reverseNumberingAlternate = m_checkBoxGridReverseNumbering->GetValue();
  296. newGrid->SetShouldNumber( m_isFootprintEditor );
  297. if( m_isFootprintEditor )
  298. {
  299. newGrid->SetNumberingStartIsSpecified( m_rbGridStartNumberingOpt->GetSelection() == 1 );
  300. if( newGrid->GetNumberingStartIsSpecified() )
  301. {
  302. newGrid->m_2dArrayNumbering = m_radioBoxGridNumberingScheme->GetSelection() != 0;
  303. // validate from the input fields
  304. bool numOk = validateAxisOptions( *m_entryGridPriNumberingOffset,
  305. *m_choicePriAxisNumbering, *m_entryGridPriNumberingStep,
  306. newGrid->m_pri_axis, errors );
  307. if( newGrid->m_2dArrayNumbering )
  308. {
  309. numOk = validateAxisOptions( *m_entryGridSecNumberingOffset,
  310. *m_choiceSecAxisNumbering, *m_entryGridSecNumberingStep,
  311. newGrid->m_sec_axis, errors )
  312. && numOk;
  313. }
  314. ok = ok && numOk;
  315. }
  316. else
  317. {
  318. // artificial linear numeric scheme from 1
  319. newGrid->m_2dArrayNumbering = false;
  320. newGrid->m_pri_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
  321. newGrid->m_pri_axis.SetOffset( 1 );
  322. }
  323. }
  324. // Only use settings if all values are good
  325. if( ok )
  326. newSettings = std::move( newGrid );
  327. }
  328. else if( page == m_circularPanel )
  329. {
  330. auto newCirc = std::make_unique<ARRAY_CIRCULAR_OPTIONS>();
  331. bool ok = true;
  332. newCirc->m_centre.x = m_hCentre.GetValue();
  333. newCirc->m_centre.y = m_vCentre.GetValue();
  334. newCirc->m_angle = DoubleValueFromString( EDA_UNITS::DEGREES,
  335. m_entryCircAngle->GetValue() );
  336. ok = ok && validateLongEntry(*m_entryCircCount, newCirc->m_nPts, _("point count"), errors);
  337. newCirc->m_rotateItems = m_entryRotateItemsCb->GetValue();
  338. newCirc->SetShouldNumber( m_isFootprintEditor );
  339. if( m_isFootprintEditor )
  340. {
  341. newCirc->SetNumberingStartIsSpecified( m_rbCircStartNumberingOpt->GetSelection() == 1 );
  342. if( newCirc->GetNumberingStartIsSpecified() )
  343. {
  344. ok = ok
  345. && validateAxisOptions( *m_entryCircNumberingStart, *m_choiceCircNumbering,
  346. *m_entryCircNumberingStep, newCirc->m_axis, errors );
  347. }
  348. else
  349. {
  350. // artificial linear numeric scheme from 1
  351. newCirc->m_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC );
  352. newCirc->m_axis.SetOffset( 1 ); // Start at "1"
  353. }
  354. }
  355. // Only use settings if all values are good
  356. if( ok )
  357. newSettings = std::move( newCirc );
  358. }
  359. // If we got good settings, send them out and finish
  360. if( newSettings )
  361. {
  362. // assign pointer and ownership here
  363. m_settings = std::move( newSettings );
  364. m_settings->SetSShouldReannotateFootprints( m_radioBtnUniqueRefs->GetValue() );
  365. // persist the control state for next time
  366. m_cfg_persister.ReadConfigFromControls();
  367. return true;
  368. }
  369. else
  370. {
  371. wxString errorStr;
  372. if( errors.IsEmpty() )
  373. errorStr = _("Bad parameters");
  374. else
  375. errorStr = boost::algorithm::join( errors, "\n" );
  376. wxMessageBox( errorStr );
  377. return false;
  378. }
  379. }
  380. void DIALOG_CREATE_ARRAY::setControlEnablement()
  381. {
  382. if( m_isFootprintEditor )
  383. {
  384. m_footprintReannotatePanel->Show( false );
  385. m_gridPadNumberingPanel->Show( true );
  386. m_circularPadNumberingPanel->Show( true );
  387. // If we set the start number, we can set the other options,
  388. // otherwise it's a hardcoded linear array
  389. const bool use_set_start_grid = m_rbGridStartNumberingOpt->GetSelection() == 1;
  390. m_radioBoxGridNumberingScheme->Enable( use_set_start_grid );
  391. m_labelPriAxisNumbering->Enable( use_set_start_grid );
  392. m_choicePriAxisNumbering->Enable( use_set_start_grid );
  393. // Disable the secondary axis numbering option if the
  394. // numbering scheme doesn't have two axes
  395. const bool num2d = m_radioBoxGridNumberingScheme->GetSelection() != 0;
  396. m_labelSecAxisNumbering->Enable( use_set_start_grid && num2d );
  397. m_choiceSecAxisNumbering->Enable( use_set_start_grid && num2d );
  398. // We can only set an offset if we're setting the start number
  399. m_labelGridNumberingOffset->Enable( use_set_start_grid );
  400. m_entryGridPriNumberingOffset->Enable( use_set_start_grid );
  401. m_entryGridSecNumberingOffset->Enable( use_set_start_grid && num2d );
  402. // disable the circular number offset in the same way
  403. const bool use_set_start_circ = m_rbCircStartNumberingOpt->GetSelection() == 1;
  404. m_entryCircNumberingStart->Enable( use_set_start_circ );
  405. }
  406. else
  407. {
  408. // grid
  409. m_rbGridStartNumberingOpt->Enable( false );
  410. m_radioBoxGridNumberingScheme->Enable( false );
  411. m_labelPriAxisNumbering->Enable( false );
  412. m_labelSecAxisNumbering->Enable( false );
  413. m_choiceSecAxisNumbering->Enable( false );
  414. m_choicePriAxisNumbering->Enable( false );
  415. m_labelGridNumberingOffset->Enable( false );
  416. m_entryGridPriNumberingOffset->Enable( false );
  417. m_entryGridSecNumberingOffset->Enable( false );
  418. m_gridPadNumberingPanel->Show( false );
  419. // circular
  420. m_rbCircStartNumberingOpt->Enable( false );
  421. m_entryCircNumberingStart->Enable( false );
  422. m_circularPadNumberingPanel->Show( false );
  423. m_footprintReannotatePanel->Show( true );
  424. }
  425. }
  426. void DIALOG_CREATE_ARRAY::calculateCircularArrayProperties()
  427. {
  428. VECTOR2I centre( m_hCentre.GetValue(), m_vCentre.GetValue() );
  429. // Find the radius, etc of the circle
  430. centre -= m_originalItemPosition;
  431. m_circRadius.SetValue( int( centre.EuclideanNorm() ) );
  432. }