/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 1992-2019 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 #include #include #include #include #include #include "dialog_create_array.h" /** * 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( Millimeter2iu( 2.54 ) ), m_gridDy( Millimeter2iu( 2.54 ) ), m_gridOffsetX( 0 ), m_gridOffsetY( 0 ), m_gridStagger( 1 ), m_gridStaggerType( 0 ), // rows m_gridNumberingAxis( 0 ), // h then v m_gridNumberingReverseAlternate( false ), m_gridNumberingStartSet( 1 ), // use specified start m_grid2dArrayNumbering( 0 ), // linear numbering m_gridPriAxisNumScheme( 0 ), // numeric m_gridSecAxisNumScheme( 0 ), // numeric m_gridPriNumberingOffset( "1" ), // numeric m_gridSecNumberingOffset( "1" ), // numeric m_circCentreX( 0 ), m_circCentreY( 0 ), m_circAngle( 0.0 ), m_circCount( 4 ), m_circNumberingStartSet( 1 ), // use specified start m_circNumberingOffset( "1" ), m_circRotate( false ), m_arrayTypeTab( 0 ) // start on grid view { } bool m_optionsSet; long m_gridNx, m_gridNy; long m_gridDx, m_gridDy; long m_gridOffsetX, m_gridOffsetY; long m_gridStagger; long m_gridStaggerType, m_gridNumberingAxis; bool m_gridNumberingReverseAlternate; long m_gridNumberingStartSet; long m_grid2dArrayNumbering; long m_gridPriAxisNumScheme, m_gridSecAxisNumScheme; wxString m_gridPriNumberingOffset, m_gridSecNumberingOffset; long m_circCentreX, m_circCentreY; long m_circAngle; long m_circCount; long m_circNumberingStartSet; wxString m_circNumberingOffset; bool m_circRotate; long m_arrayTypeTab; }; // Persistent options settings static CREATE_ARRAY_DIALOG_ENTRIES saved_array_options; DIALOG_CREATE_ARRAY::DIALOG_CREATE_ARRAY( PCB_BASE_FRAME* aParent, bool enableNumbering, wxPoint aOrigPos ) : DIALOG_CREATE_ARRAY_BASE( aParent ), m_settings( NULL ), 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_hCentre( aParent, m_labelCentreX, m_entryCentreX, m_unitLabelCentreX ), m_vCentre( aParent, m_labelCentreY, m_entryCentreY, m_unitLabelCentreY ), m_circRadius( aParent, m_labelCircRadius, m_valueCircRadius, m_unitLabelCircRadius ), m_circAngle( aParent, m_labelCircAngle, m_entryCircAngle, m_unitLabelCircAngle ), m_cfg_persister( saved_array_options.m_optionsSet ), m_originalItemPosition( aOrigPos ), m_numberingEnabled( enableNumbering ) { // Set up numbering scheme drop downs // // character set // NOTE: do not change the order of this relative to the NUMBERING_TYPE_T enum const wxString charSetDescriptions[] = { _( "Numerals (0,1,2,...,9,10)" ), _( "Hexadecimal (0,1,...,F,10,...)" ), _( "Alphabet, minus IOSQXZ" ), _( "Alphabet, full 26 characters" ) }; m_choicePriAxisNumbering->Set( arrayDim( charSetDescriptions ), charSetDescriptions ); m_choiceSecAxisNumbering->Set( arrayDim( charSetDescriptions ), charSetDescriptions ); m_choicePriAxisNumbering->SetSelection( 0 ); m_choiceSecAxisNumbering->SetSelection( 0 ); m_circAngle.SetUnits( EDA_UNITS_T::DEGREES ); // bind grid options to persister m_cfg_persister.Add( *m_entryNx, saved_array_options.m_gridNx ); m_cfg_persister.Add( *m_entryNy, saved_array_options.m_gridNy ); m_cfg_persister.Add( m_hSpacing, saved_array_options.m_gridDx ); m_cfg_persister.Add( m_vSpacing, saved_array_options.m_gridDy ); m_cfg_persister.Add( m_hOffset, saved_array_options.m_gridOffsetX ); m_cfg_persister.Add( m_vOffset, saved_array_options.m_gridOffsetY ); m_cfg_persister.Add( *m_entryStagger, saved_array_options.m_gridStagger ); m_cfg_persister.Add( *m_radioBoxGridStaggerType, saved_array_options.m_gridStaggerType ); m_cfg_persister.Add( *m_radioBoxGridNumberingAxis, saved_array_options.m_gridNumberingAxis ); m_cfg_persister.Add( *m_checkBoxGridReverseNumbering, saved_array_options.m_gridNumberingReverseAlternate ); m_cfg_persister.Add( *m_rbGridStartNumberingOpt, saved_array_options.m_gridNumberingStartSet ); m_cfg_persister.Add( *m_radioBoxGridNumberingScheme, saved_array_options.m_grid2dArrayNumbering ); m_cfg_persister.Add( *m_choicePriAxisNumbering, saved_array_options.m_gridPriAxisNumScheme ); m_cfg_persister.Add( *m_choiceSecAxisNumbering, saved_array_options.m_gridSecAxisNumScheme ); m_cfg_persister.Add( *m_entryGridPriNumberingOffset, saved_array_options.m_gridPriNumberingOffset ); m_cfg_persister.Add( *m_entryGridSecNumberingOffset, saved_array_options.m_gridSecNumberingOffset ); // bind circular options to persister m_cfg_persister.Add( m_hCentre, saved_array_options.m_circCentreX ); m_cfg_persister.Add( m_vCentre, saved_array_options.m_circCentreY ); m_cfg_persister.Add( m_circAngle, saved_array_options.m_circAngle ); m_cfg_persister.Add( *m_entryCircCount, saved_array_options.m_circCount ); m_cfg_persister.Add( *m_entryRotateItemsCb, saved_array_options.m_circRotate ); m_cfg_persister.Add( *m_rbCircStartNumberingOpt, saved_array_options.m_circNumberingStartSet ); m_cfg_persister.Add( *m_entryCircNumberingStart, saved_array_options.m_circNumberingOffset ); m_cfg_persister.Add( *m_gridTypeNotebook, saved_array_options.m_arrayTypeTab ); m_cfg_persister.RestoreConfigToControls(); // Run the callbacks once to process the dialog contents setControlEnablement(); calculateCircularArrayProperties(); m_stdButtonsOK->SetDefault(); Fit(); SetMinSize( GetSize() ); } DIALOG_CREATE_ARRAY::~DIALOG_CREATE_ARRAY() { if( m_settings != NULL ) delete m_settings; } void DIALOG_CREATE_ARRAY::OnParameterChanged( wxCommandEvent& event ) { setControlEnablement(); calculateCircularArrayProperties(); } /** * 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 validateNumberingTypeAndOffset( const wxTextCtrl& offsetEntry, const wxChoice& typeEntry, ARRAY_OPTIONS::NUMBERING_TYPE_T& type, int& offset, wxArrayString& errors ) { const int typeVal = typeEntry.GetSelection(); // mind undefined casts to enums (should not be able to happen) bool ok = typeVal <= ARRAY_OPTIONS::NUMBERING_TYPE_MAX; if( ok ) { type = (ARRAY_OPTIONS::NUMBERING_TYPE_T) typeVal; } else { wxString err; err.Printf( _("Unrecognized numbering scheme: %d"), typeVal ); errors.Add( err ); // we can't proceed - we don't know the numbering type return false; } const wxString text = offsetEntry.GetValue(); ok = ARRAY_OPTIONS::GetNumberingOffset( text, type, offset ); if( !ok ) { const wxString& alphabet = ARRAY_OPTIONS::AlphabetFromNumberingScheme( type ); wxString err; err.Printf( _( "Could not determine numbering start from \"%s\": " "expected value consistent with alphabet \"%s\"" ), text, alphabet ); errors.Add(err); } return ok; } /** * 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; } bool DIALOG_CREATE_ARRAY::TransferDataFromWindow() { ARRAY_OPTIONS* newSettings = NULL; wxArrayString errors; const wxWindow* page = m_gridTypeNotebook->GetCurrentPage(); if( page == m_gridPanel ) { ARRAY_GRID_OPTIONS* newGrid = new ARRAY_GRID_OPTIONS(); bool ok = true; // ints ok = ok && validateLongEntry(*m_entryNx, newGrid->m_nx, _("horizontal count"), errors); ok = ok && validateLongEntry(*m_entryNy, newGrid->m_ny, _("vertical count"), errors); newGrid->m_delta.x = m_hSpacing.GetValue(); newGrid->m_delta.y = m_vSpacing.GetValue(); newGrid->m_offset.x = m_hOffset.GetValue(); newGrid->m_offset.y = m_vOffset.GetValue(); ok = ok && validateLongEntry(*m_entryStagger, newGrid->m_stagger, _("stagger"), errors); newGrid->m_stagger_rows = m_radioBoxGridStaggerType->GetSelection() == 0; newGrid->m_horizontalThenVertical = m_radioBoxGridNumberingAxis->GetSelection() == 0; newGrid->m_reverseNumberingAlternate = m_checkBoxGridReverseNumbering->GetValue(); newGrid->SetShouldNumber( m_numberingEnabled ); if ( m_numberingEnabled ) { newGrid->SetNumberingStartIsSpecified( m_rbGridStartNumberingOpt->GetSelection() == 1 ); if( newGrid->GetNumberingStartIsSpecified() ) { newGrid->m_2dArrayNumbering = m_radioBoxGridNumberingScheme->GetSelection() != 0; // validate from the input fields bool numOk = validateNumberingTypeAndOffset( *m_entryGridPriNumberingOffset, *m_choicePriAxisNumbering, newGrid->m_priAxisNumType, newGrid->m_numberingOffsetX, errors ); if( newGrid->m_2dArrayNumbering ) { numOk = validateNumberingTypeAndOffset( *m_entryGridSecNumberingOffset, *m_choiceSecAxisNumbering, newGrid->m_secAxisNumType, newGrid->m_numberingOffsetY, errors ) && numOk; } ok = ok && numOk; } else { // artificial linear numeric scheme from 1 newGrid->m_2dArrayNumbering = false; newGrid->m_priAxisNumType = ARRAY_OPTIONS::NUMBERING_TYPE_T::NUMBERING_NUMERIC; newGrid->m_numberingOffsetX = 1; // Start at "1" } } // Only use settings if all values are good if( ok ) newSettings = newGrid; else delete newGrid; } else if( page == m_circularPanel ) { ARRAY_CIRCULAR_OPTIONS* newCirc = new ARRAY_CIRCULAR_OPTIONS(); bool ok = true; newCirc->m_centre.x = m_hCentre.GetValue(); newCirc->m_centre.y = m_vCentre.GetValue(); newCirc->m_angle = DoubleValueFromString( DEGREES, m_entryCircAngle->GetValue() ); ok = ok && validateLongEntry(*m_entryCircCount, newCirc->m_nPts, _("point count"), errors); newCirc->m_rotateItems = m_entryRotateItemsCb->GetValue(); newCirc->SetShouldNumber( m_numberingEnabled ); if ( m_numberingEnabled ) { newCirc->SetNumberingStartIsSpecified( m_rbCircStartNumberingOpt->GetSelection() == 1 ); if( newCirc->GetNumberingStartIsSpecified() ) { newCirc->m_numberingType = ARRAY_OPTIONS::NUMBERING_NUMERIC; ok = ok && validateLongEntry( *m_entryCircNumberingStart, newCirc->m_numberingOffset, _( "numbering start" ), errors ); } else { // artificial linear numeric scheme from 1 newCirc->m_numberingOffset = 1; // Start at "1" } } // Only use settings if all values are good if( ok ) newSettings = newCirc; else delete newCirc; } // If we got good settings, send them out and finish if( newSettings ) { delete m_settings; // assign pointer and ownership here m_settings = newSettings; m_cfg_persister.ReadConfigFromControls(); return true; } else { wxString errorStr; if( errors.IsEmpty() ) errorStr = _("Bad parameters"); else errorStr = boost::algorithm::join( errors, "\n" ); wxMessageBox( errorStr ); return false; } } void DIALOG_CREATE_ARRAY::setControlEnablement() { if ( m_numberingEnabled ) { // 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 ); // circular m_rbCircStartNumberingOpt->Enable( false ); m_entryCircNumberingStart->Enable( false ); } } void DIALOG_CREATE_ARRAY::calculateCircularArrayProperties() { VECTOR2I centre( m_hCentre.GetValue(), m_vCentre.GetValue() ); // Find the radius, etc of the circle centre -= m_originalItemPosition; m_circRadius.SetValue( int( centre.EuclideanNorm() ) ); }