/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "dialogs/dialog_create_array.h" #include #include #include #include #include #include /** * Struct containing the last-entered values for the dialog. */ struct CREATE_ARRAY_DIALOG_ENTRIES { /** * Construct with some sensible defaults. * In future, this could be loaded from config? */ CREATE_ARRAY_DIALOG_ENTRIES() : m_OptionsSet( true ), m_GridNx( 5 ), m_GridNy( 5 ), m_GridDx( pcbIUScale.mmToIU( 2.54 ) ), m_GridDy( pcbIUScale.mmToIU( 2.54 ) ), m_GridOffsetX( 0 ), m_GridOffsetY( 0 ), m_GridStagger( 1 ), m_GridStaggerRows( true ), m_GridNumberingAxis( 0 ), // h then v m_GridNumReverseAlt( false ), m_GridNumStartSet( 1 ), // use specified start m_Grid2dArrayNumbering( 0 ), // linear numbering m_GridPrimaryAxisScheme( 0 ), // numeric m_GridSecondaryAxisScheme( 0 ), // numeric m_GridPrimaryNumOffset( wxT( "1" ) ), // numeric m_GridSecondaryNumOffset( wxT( "1" ) ), // numeric m_GridPrimaryAxisStep( 1 ), m_GridSecondaryAxisStep( 1 ), m_CircCentreX( 0 ), m_CircCentreY( 0 ), m_CircCount( 4 ), m_CircNumStartSet( 1 ), // use specified start m_GridCircNumScheme( 0 ), m_CircNumberingOffset( wxT( "1" ) ), m_CircNumberingStep( 1 ), m_CircRotatationStep( false ), m_ArrayTypeTab( 0 ), // start on grid view m_FootprintKeepAnnotations( false ), m_FootprintReannotate( true ) // Assign unique by default { } bool m_OptionsSet; long m_GridNx; long m_GridNy; long m_GridDx; long m_GridDy; long m_GridOffsetX; long m_GridOffsetY; long m_GridStagger; bool m_GridStaggerRows; long m_GridNumberingAxis; bool m_GridNumReverseAlt; long m_GridNumStartSet; long m_Grid2dArrayNumbering; long m_GridPrimaryAxisScheme; long m_GridSecondaryAxisScheme; wxString m_GridPrimaryNumOffset; wxString m_GridSecondaryNumOffset; long m_GridPrimaryAxisStep; long m_GridSecondaryAxisStep; long m_CircCentreX; long m_CircCentreY; EDA_ANGLE m_CircAngle; long m_CircCount; long m_CircNumStartSet; long m_GridCircNumScheme; wxString m_CircNumberingOffset; long m_CircNumberingStep; bool m_CircRotatationStep; long m_ArrayTypeTab; bool m_FootprintKeepAnnotations; bool m_FootprintReannotate; }; // Persistent options settings static CREATE_ARRAY_DIALOG_ENTRIES s_arrayOptions; /** * Local mapping for list-box <-> numbering type */ struct NUMBERING_LIST_DATA { ARRAY_AXIS::NUMBERING_TYPE m_numbering_type; wxString m_label; }; /** * List of type <--> name mappings (in order) for the numbering type * list boxes */ static const std::vector numberingTypeData { { ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC, _( "Numerals (0,1,2,...,9,10)" ), }, { ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_HEX, _( "Hexadecimal (0,1,...,F,10,...)" ), }, { ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_NO_IOSQXZ, _( "Alphabet, minus IOSQXZ" ), }, { ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_ALPHA_FULL, _( "Alphabet, full 26 characters" ), }, }; DIALOG_CREATE_ARRAY::DIALOG_CREATE_ARRAY( PCB_BASE_FRAME* aParent, std::unique_ptr& aSettings, bool aIsFootprintEditor, const VECTOR2I& aOrigPos ) : DIALOG_CREATE_ARRAY_BASE( aParent ), m_settings( aSettings ), m_originalItemPosition( aOrigPos ), m_isFootprintEditor( aIsFootprintEditor ), m_hSpacing( aParent, m_labelDx, m_entryDx, m_unitLabelDx ), m_vSpacing( aParent, m_labelDy, m_entryDy, m_unitLabelDy ), m_hOffset( aParent, m_labelOffsetX, m_entryOffsetX, m_unitLabelOffsetX ), m_vOffset( aParent, m_labelOffsetY, m_entryOffsetY, m_unitLabelOffsetY ), m_refPosX( aParent, m_stRefPosXTxt, m_tcRefPosX, m_stRefPosXUnit ), m_refPosY( aParent, m_stRefPosYTxt, m_tcRefPosY, m_stRefPosYUnit ), m_hCentre( aParent, m_labelCentreX, m_entryCentreX, m_unitLabelCentreX ), m_vCentre( aParent, m_labelCentreY, m_entryCentreY, m_unitLabelCentreY ), m_circRadius( aParent, m_labelCircRadius, m_tcValueCircRadius, m_unitLabelCircRadius ), m_circCenterAngle( aParent, m_labelCircCenterAngle, m_tcValueCircCenterAngle, m_unitLabelCircCenterAngle ), m_circAngle( aParent, m_labelCircAngle, m_entryCircAngle, m_unitLabelCircAngle ), m_cfg_persister( pcbIUScale, s_arrayOptions.m_OptionsSet ) { // Configure display origin transforms m_hSpacing.SetCoordType( ORIGIN_TRANSFORMS::REL_X_COORD ); m_vSpacing.SetCoordType( ORIGIN_TRANSFORMS::REL_Y_COORD ); m_hOffset.SetCoordType( ORIGIN_TRANSFORMS::REL_X_COORD ); m_vOffset.SetCoordType( ORIGIN_TRANSFORMS::REL_Y_COORD ); m_hCentre.SetCoordType( ORIGIN_TRANSFORMS::ABS_X_COORD ); m_vCentre.SetCoordType( ORIGIN_TRANSFORMS::ABS_Y_COORD ); // Set up numbering scheme drop downs character set strings for( const auto& numData : numberingTypeData ) { const wxString label = wxGetTranslation( numData.m_label ); void* clientData = (void*) &numData; m_choicePriAxisNumbering->Append( label, clientData ); m_choiceSecAxisNumbering->Append( label, clientData ); m_choiceCircNumbering->Append( label, clientData ); } m_choicePriAxisNumbering->SetSelection( 0 ); m_choiceSecAxisNumbering->SetSelection( 0 ); m_choiceCircNumbering->SetSelection( 0 ); m_circCenterAngle.SetUnits( EDA_UNITS::DEGREES ); m_circAngle.SetUnits( EDA_UNITS::DEGREES ); // bind grid options to persister m_cfg_persister.Add( *m_entryNx, s_arrayOptions.m_GridNx ); m_cfg_persister.Add( *m_entryNy, s_arrayOptions.m_GridNy ); m_cfg_persister.Add( m_hSpacing, s_arrayOptions.m_GridDx ); m_cfg_persister.Add( m_vSpacing, s_arrayOptions.m_GridDy ); m_cfg_persister.Add( m_hOffset, s_arrayOptions.m_GridOffsetX ); m_cfg_persister.Add( m_vOffset, s_arrayOptions.m_GridOffsetY ); m_cfg_persister.Add( *m_entryStagger, s_arrayOptions.m_GridStagger ); m_cfg_persister.Add( *m_staggerRows, s_arrayOptions.m_GridStaggerRows ); m_cfg_persister.Add( *m_radioBoxGridNumberingAxis, s_arrayOptions.m_GridNumberingAxis ); m_cfg_persister.Add( *m_checkBoxGridReverseNumbering, s_arrayOptions.m_GridNumReverseAlt ); m_cfg_persister.Add( *m_rbGridStartNumberingOpt, s_arrayOptions.m_GridNumStartSet ); m_cfg_persister.Add( *m_radioBoxGridNumberingScheme, s_arrayOptions.m_Grid2dArrayNumbering ); m_cfg_persister.Add( *m_choicePriAxisNumbering, s_arrayOptions.m_GridPrimaryAxisScheme ); m_cfg_persister.Add( *m_choiceSecAxisNumbering, s_arrayOptions.m_GridSecondaryAxisScheme ); m_cfg_persister.Add( *m_entryGridPriNumberingOffset, s_arrayOptions.m_GridPrimaryNumOffset ); m_cfg_persister.Add( *m_entryGridSecNumberingOffset, s_arrayOptions.m_GridSecondaryNumOffset ); m_cfg_persister.Add( *m_entryGridPriNumberingStep, s_arrayOptions.m_GridPrimaryAxisStep ); m_cfg_persister.Add( *m_entryGridSecNumberingStep, s_arrayOptions.m_GridSecondaryAxisStep ); // bind circular options to persister m_cfg_persister.Add( m_hCentre, s_arrayOptions.m_CircCentreX ); m_cfg_persister.Add( m_vCentre, s_arrayOptions.m_CircCentreY ); m_cfg_persister.Add( m_circAngle, s_arrayOptions.m_CircAngle ); m_cfg_persister.Add( *m_entryCircCount, s_arrayOptions.m_CircCount ); m_cfg_persister.Add( *m_entryRotateItemsCb, s_arrayOptions.m_CircRotatationStep ); m_cfg_persister.Add( *m_rbCircStartNumberingOpt, s_arrayOptions.m_CircNumStartSet ); m_cfg_persister.Add( *m_choiceCircNumbering, s_arrayOptions.m_GridCircNumScheme ); m_cfg_persister.Add( *m_entryCircNumberingStart, s_arrayOptions.m_CircNumberingOffset ); m_cfg_persister.Add( *m_entryCircNumberingStep, s_arrayOptions.m_CircNumberingStep ); m_cfg_persister.Add( *m_gridTypeNotebook, s_arrayOptions.m_ArrayTypeTab ); m_cfg_persister.Add( *m_radioBtnKeepRefs, s_arrayOptions.m_FootprintKeepAnnotations ); m_cfg_persister.Add( *m_radioBtnUniqueRefs, s_arrayOptions.m_FootprintReannotate ); m_cfg_persister.RestoreConfigToControls(); // Run the callbacks once to process the dialog contents setControlEnablement(); setCircularArrayEnablement(); calculateCircularArrayProperties(); SetupStandardButtons(); Fit(); SetMinSize( GetSize() ); } void DIALOG_CREATE_ARRAY::OnButtonPosition( wxCommandEvent& event ) { setCircularArrayEnablement(); } void DIALOG_CREATE_ARRAY::OnButtonRadius( wxCommandEvent& event ) { setCircularArrayEnablement(); } void DIALOG_CREATE_ARRAY::setCircularArrayEnablement() { if( m_radioBtnSetByRadius->GetValue() ) { m_entryCentreX->Disable(); m_entryCentreY->Disable(); m_tcValueCircRadius->Enable(); m_tcValueCircCenterAngle->Enable(); } else { m_entryCentreX->Enable(); m_entryCentreY->Enable(); m_tcValueCircRadius->Disable(); m_tcValueCircCenterAngle->Disable(); } } void DIALOG_CREATE_ARRAY::OnParameterChanged( wxCommandEvent& event ) { setCircularArrayEnablement(); if( m_radioBtnSetByPos->GetValue() ) { setControlEnablement(); calculateCircularArrayProperties(); } } void DIALOG_CREATE_ARRAY::OnRadiusChanged( wxCommandEvent& event ) { setCircularArrayEnablement(); if( m_radioBtnSetByRadius->GetValue() ) { setControlEnablement(); calculateCircularArrayProperties(); } } /** * Validate and save a long integer entry * * @param entry the text entry to read from * @param dest the value destination * @param description description of the field (used if the value is not OK) * @param errors a list of errors to add any error to * @return valid */ static bool validateLongEntry( const wxTextEntry& entry, long& dest, const wxString& description, wxArrayString& errors ) { bool ok = true; if( !entry.GetValue().ToLong( &dest ) ) { wxString err; err.Printf( _( "Bad numeric value for %s: %s" ), description, entry.GetValue() ); errors.Add( err ); ok = false; } return ok; } /** * Validates and saves (if valid) the type and offset of an array axis numbering * * @param offsetEntry the entry of the offset (text) * @param typeEntry the entry of the axis nmbering scheme (choice) * @param type the destination of the type if valid * @param offset the destination of the offset if valid * @param errors error string accumulator * @return if all valid */ static bool validateAxisOptions( const wxTextCtrl& offsetEntry, const wxChoice& typeEntry, const wxTextCtrl& aStepEntry, ARRAY_AXIS& aAxis, wxArrayString& errors ) { void* clientData = typeEntry.GetClientData( typeEntry.GetSelection() ); const NUMBERING_LIST_DATA* numberingData = static_cast( clientData ); wxCHECK_MSG( numberingData, false, wxT( "Failed to get client data from list control." ) ); aAxis.SetAxisType( numberingData->m_numbering_type ); const wxString text = offsetEntry.GetValue(); bool ok = aAxis.SetOffset( text ); if( !ok ) { errors.Add( wxString::Format( _( "Could not determine numbering start from '%s': " "expected value consistent with alphabet '%s'." ), text, aAxis.GetAlphabet() ) ); return false; } long step; ok = validateLongEntry( aStepEntry, step, _( "step value" ), errors ); if( ok ) aAxis.SetStep( step ); return ok; } bool DIALOG_CREATE_ARRAY::TransferDataFromWindow() { std::unique_ptr newSettings; wxArrayString errors; const wxWindow* page = m_gridTypeNotebook->GetCurrentPage(); if( page == m_gridPanel ) { auto newGrid = std::make_unique(); bool ok = true; // ints ok &= validateLongEntry(*m_entryNx, newGrid->m_nx, _("horizontal count"), errors); ok &= validateLongEntry(*m_entryNy, newGrid->m_ny, _("vertical count"), errors); newGrid->m_delta.x = m_hSpacing.GetIntValue(); newGrid->m_delta.y = m_vSpacing.GetIntValue(); newGrid->m_offset.x = m_hOffset.GetIntValue(); newGrid->m_offset.y = m_vOffset.GetIntValue(); ok &= validateLongEntry(*m_entryStagger, newGrid->m_stagger, _("stagger"), errors); newGrid->m_stagger_rows = m_staggerRows->GetValue(); newGrid->m_horizontalThenVertical = m_radioBoxGridNumberingAxis->GetSelection() == 0; newGrid->m_reverseNumberingAlternate = m_checkBoxGridReverseNumbering->GetValue(); newGrid->SetShouldNumber( m_isFootprintEditor ); if( m_isFootprintEditor ) { newGrid->SetNumberingStartIsSpecified( m_rbGridStartNumberingOpt->GetSelection() == 1 ); if( newGrid->GetNumberingStartIsSpecified() ) { newGrid->m_2dArrayNumbering = m_radioBoxGridNumberingScheme->GetSelection() != 0; // validate from the input fields bool numOk = validateAxisOptions( *m_entryGridPriNumberingOffset, *m_choicePriAxisNumbering, *m_entryGridPriNumberingStep, newGrid->m_pri_axis, errors ); if( newGrid->m_2dArrayNumbering ) { numOk &= validateAxisOptions( *m_entryGridSecNumberingOffset, *m_choiceSecAxisNumbering, *m_entryGridSecNumberingStep, newGrid->m_sec_axis, errors ); } ok &= numOk; } else { // artificial linear numeric scheme from 1 newGrid->m_2dArrayNumbering = false; newGrid->m_pri_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC ); newGrid->m_pri_axis.SetOffset( 1 ); } } // Only use settings if all values are good if( ok ) newSettings = std::move( newGrid ); } else if( page == m_circularPanel ) { auto newCirc = std::make_unique(); bool ok = true; double angle = EDA_UNIT_UTILS::UI::DoubleValueFromString( m_entryCircAngle->GetValue() ); newCirc->m_centre.x = m_hCentre.GetIntValue(); newCirc->m_centre.y = m_vCentre.GetIntValue(); newCirc->m_angle = EDA_ANGLE( angle, DEGREES_T ); ok = validateLongEntry(*m_entryCircCount, newCirc->m_nPts, _("point count"), errors); newCirc->m_rotateItems = m_entryRotateItemsCb->GetValue(); newCirc->SetShouldNumber( m_isFootprintEditor ); if( m_isFootprintEditor ) { newCirc->SetNumberingStartIsSpecified( m_rbCircStartNumberingOpt->GetSelection() == 1 ); if( newCirc->GetNumberingStartIsSpecified() ) { ok &= validateAxisOptions( *m_entryCircNumberingStart, *m_choiceCircNumbering, *m_entryCircNumberingStep, newCirc->m_axis, errors ); } else { // artificial linear numeric scheme from 1 newCirc->m_axis.SetAxisType( ARRAY_AXIS::NUMBERING_TYPE::NUMBERING_NUMERIC ); newCirc->m_axis.SetOffset( 1 ); // Start at "1" } } // Only use settings if all values are good if( ok ) newSettings = std::move( newCirc ); } // If we got good settings, send them out and finish if( newSettings ) { // assign pointer and ownership here m_settings = std::move( newSettings ); m_settings->SetSShouldReannotateFootprints( m_radioBtnUniqueRefs->GetValue() ); // persist the control state for next time m_cfg_persister.ReadConfigFromControls(); return true; } else { wxString errorStr; if( errors.IsEmpty() ) errorStr = _("Bad parameters"); else errorStr = boost::algorithm::join( errors, wxT( "\n" ) ); wxMessageBox( errorStr ); return false; } } void DIALOG_CREATE_ARRAY::setControlEnablement() { if( m_isFootprintEditor ) { m_footprintReannotatePanel->Show( false ); m_gridPadNumberingPanel->Show( true ); m_circularPadNumberingPanel->Show( true ); // If we set the start number, we can set the other options, // otherwise it's a hardcoded linear array const bool use_set_start_grid = m_rbGridStartNumberingOpt->GetSelection() == 1; m_radioBoxGridNumberingScheme->Enable( use_set_start_grid ); m_labelPriAxisNumbering->Enable( use_set_start_grid ); m_choicePriAxisNumbering->Enable( use_set_start_grid ); // Disable the secondary axis numbering option if the // numbering scheme doesn't have two axes const bool num2d = m_radioBoxGridNumberingScheme->GetSelection() != 0; m_labelSecAxisNumbering->Enable( use_set_start_grid && num2d ); m_choiceSecAxisNumbering->Enable( use_set_start_grid && num2d ); // We can only set an offset if we're setting the start number m_labelGridNumberingOffset->Enable( use_set_start_grid ); m_entryGridPriNumberingOffset->Enable( use_set_start_grid ); m_entryGridSecNumberingOffset->Enable( use_set_start_grid && num2d ); // disable the circular number offset in the same way const bool use_set_start_circ = m_rbCircStartNumberingOpt->GetSelection() == 1; m_entryCircNumberingStart->Enable( use_set_start_circ ); } else { // grid m_rbGridStartNumberingOpt->Enable( false ); m_radioBoxGridNumberingScheme->Enable( false ); m_labelPriAxisNumbering->Enable( false ); m_labelSecAxisNumbering->Enable( false ); m_choiceSecAxisNumbering->Enable( false ); m_choicePriAxisNumbering->Enable( false ); m_labelGridNumberingOffset->Enable( false ); m_entryGridPriNumberingOffset->Enable( false ); m_entryGridSecNumberingOffset->Enable( false ); m_gridPadNumberingPanel->Show( false ); // circular m_rbCircStartNumberingOpt->Enable( false ); m_entryCircNumberingStart->Enable( false ); m_circularPadNumberingPanel->Show( false ); m_footprintReannotatePanel->Show( true ); } } void DIALOG_CREATE_ARRAY::calculateCircularArrayProperties() { if( m_radioBtnSetByPos->GetValue() ) { VECTOR2I centre( m_hCentre.GetIntValue(), m_vCentre.GetIntValue() ); // Find the radius, etc of the circle centre -= m_originalItemPosition; EDA_ANGLE angle( centre ); m_circRadius.SetValue( int( centre.EuclideanNorm() ) ); m_circCenterAngle.SetAngleValue( angle.Round( 4 ) ); m_refPosX.SetValue( m_originalItemPosition.x ); m_refPosY.SetValue( m_originalItemPosition.y ); } else { m_refPosX.SetValue( m_originalItemPosition.x ); m_refPosY.SetValue( m_originalItemPosition.y ); double radius = m_circRadius.GetIntValue(); EDA_ANGLE angle = m_circCenterAngle.GetAngleValue(); m_hCentre.SetValue( m_originalItemPosition.x + radius * angle.Cos() ); m_vCentre.SetValue( m_originalItemPosition.y + radius * angle.Sin() ); } }