32739d383c
The NETNAME_VALIDATOR was inconsistent: eeschema dialogs explicitly allowed spaces but PCBNew net inspector used the default (no spaces). This caused confusion where net names could be created with spaces in eeschema but the net inspector rejected them. Changed the default m_allowSpaces from false to true to make behavior consistent across all validators. Added unit tests to verify the behavior. Fixes https://gitlab.com/kicad/code/kicad/-/issues/9258
417 lines
12 KiB
C++
417 lines
12 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2013 Wayne Stambaugh <stambaughw@gmail.com>
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
* Copyright (C) 2018 CERN
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
/**
|
|
* @file validators.cpp
|
|
* @brief Custom text control validator implementations.
|
|
*/
|
|
|
|
#include <string_utils.h>
|
|
#include <confirm.h>
|
|
#include <validators.h>
|
|
#include <template_fieldnames.h>
|
|
|
|
#include <wx/grid.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/textentry.h>
|
|
#include <wx/log.h>
|
|
#include <wx/combo.h>
|
|
#include <wx/msgdlg.h>
|
|
#include <refdes_utils.h>
|
|
|
|
|
|
FOOTPRINT_NAME_VALIDATOR::FOOTPRINT_NAME_VALIDATOR( wxString* aValue ) :
|
|
wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST, aValue )
|
|
{
|
|
// This list of characters follows the string from footprint.cpp which, in turn mimics the
|
|
// strings from lib_id.cpp
|
|
// TODO: Unify forbidden character lists
|
|
wxString illegalChars = wxS( "%$<>\t\n\r\"\\/:" );
|
|
SetCharExcludes( illegalChars );
|
|
}
|
|
|
|
|
|
ENV_VAR_NAME_VALIDATOR::ENV_VAR_NAME_VALIDATOR( wxString* aValue ) :
|
|
wxTextValidator()
|
|
{
|
|
Connect( wxEVT_CHAR, wxKeyEventHandler( ENV_VAR_NAME_VALIDATOR::OnChar ) );
|
|
}
|
|
|
|
|
|
ENV_VAR_NAME_VALIDATOR::ENV_VAR_NAME_VALIDATOR( const ENV_VAR_NAME_VALIDATOR& val )
|
|
: wxTextValidator()
|
|
{
|
|
wxValidator::Copy( val );
|
|
|
|
Connect( wxEVT_CHAR, wxKeyEventHandler( ENV_VAR_NAME_VALIDATOR::OnChar ) );
|
|
}
|
|
|
|
|
|
ENV_VAR_NAME_VALIDATOR::~ENV_VAR_NAME_VALIDATOR()
|
|
{
|
|
Disconnect( wxEVT_CHAR, wxKeyEventHandler( ENV_VAR_NAME_VALIDATOR::OnChar ) );
|
|
}
|
|
|
|
|
|
void ENV_VAR_NAME_VALIDATOR::OnChar( wxKeyEvent& aEvent )
|
|
{
|
|
if( !m_validatorWindow )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
int keyCode = aEvent.GetKeyCode();
|
|
|
|
// we don't filter special keys and delete
|
|
if( keyCode < WXK_SPACE || keyCode == WXK_DELETE || keyCode >= WXK_START )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
wxUniChar c = (wxUChar) keyCode;
|
|
|
|
if( c == wxT( '_' ) )
|
|
{
|
|
// OK anywhere
|
|
aEvent.Skip();
|
|
}
|
|
else if( wxIsdigit( c ) )
|
|
{
|
|
// not as first character
|
|
long from, to;
|
|
GetTextEntry()->GetSelection( &from, &to );
|
|
|
|
if( from < 1 )
|
|
wxBell();
|
|
else
|
|
aEvent.Skip();
|
|
}
|
|
else if( wxIsalpha( c ) )
|
|
{
|
|
// Capitals only.
|
|
|
|
if( wxIslower( c ) )
|
|
{
|
|
// You may wonder why this scope is so twisted, so make yourself comfortable and read:
|
|
// 1. Changing the keyCode and/or uniChar in the event and passing it on
|
|
// doesn't work. Some platforms look at the original copy as long as the event
|
|
// isn't vetoed.
|
|
// 2. Inserting characters by hand does not move the cursor, meaning either you insert
|
|
// text backwards (lp:#1798869) or always append, no matter where is the cursor.
|
|
// wxTextEntry::{Get/Set}InsertionPoint() do not work at all here.
|
|
// 3. There is wxTextEntry::ForceUpper(), but it is not yet available in common
|
|
// wxWidgets packages.
|
|
//
|
|
// So here we are, with a command event handler that converts
|
|
// the text to upper case upon every change.
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( GetTextEntry() );
|
|
|
|
if( textCtrl )
|
|
{
|
|
textCtrl->Connect( textCtrl->GetId(), wxEVT_COMMAND_TEXT_UPDATED,
|
|
wxCommandEventHandler( ENV_VAR_NAME_VALIDATOR::OnTextChanged ) );
|
|
}
|
|
}
|
|
|
|
aEvent.Skip();
|
|
}
|
|
else
|
|
{
|
|
wxBell();
|
|
}
|
|
}
|
|
|
|
|
|
void ENV_VAR_NAME_VALIDATOR::OnTextChanged( wxCommandEvent& event )
|
|
{
|
|
wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( event.GetEventObject() );
|
|
|
|
if( textCtrl )
|
|
{
|
|
if( !textCtrl->IsModified() )
|
|
return;
|
|
|
|
long insertionPoint = textCtrl->GetInsertionPoint();
|
|
textCtrl->ChangeValue( textCtrl->GetValue().Upper() );
|
|
textCtrl->SetInsertionPoint( insertionPoint );
|
|
textCtrl->Disconnect( textCtrl->GetId(), wxEVT_COMMAND_TEXT_UPDATED );
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
NETNAME_VALIDATOR::NETNAME_VALIDATOR( wxString *aVal ) :
|
|
wxTextValidator(),
|
|
m_allowSpaces( true )
|
|
{
|
|
}
|
|
|
|
|
|
NETNAME_VALIDATOR::NETNAME_VALIDATOR( const NETNAME_VALIDATOR& aValidator ) :
|
|
wxTextValidator( aValidator ),
|
|
m_allowSpaces( aValidator.m_allowSpaces )
|
|
{
|
|
}
|
|
|
|
|
|
NETNAME_VALIDATOR::NETNAME_VALIDATOR( bool aAllowSpaces ) :
|
|
wxTextValidator(),
|
|
m_allowSpaces( aAllowSpaces )
|
|
{
|
|
}
|
|
|
|
|
|
bool NETNAME_VALIDATOR::Validate( wxWindow *aParent )
|
|
{
|
|
// If window is disabled, simply return
|
|
if ( !m_validatorWindow->IsEnabled() )
|
|
return true;
|
|
|
|
wxTextEntry * const text = GetTextEntry();
|
|
|
|
if ( !text )
|
|
return false;
|
|
|
|
const wxString& errormsg = IsValid( text->GetValue() );
|
|
|
|
if( !errormsg.empty() )
|
|
{
|
|
m_validatorWindow->SetFocus();
|
|
wxMessageBox( errormsg, _( "Invalid signal name" ), wxOK | wxICON_EXCLAMATION, aParent );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
wxString NETNAME_VALIDATOR::IsValid( const wxString& str ) const
|
|
{
|
|
if( str.Contains( '\r' ) || str.Contains( '\n' ) )
|
|
return _( "Signal names cannot contain CR or LF characters" );
|
|
|
|
if( !m_allowSpaces && ( str.Contains( ' ' ) || str.Contains( '\t' ) ) )
|
|
return _( "Signal names cannot contain spaces" );
|
|
|
|
return wxString();
|
|
}
|
|
|
|
|
|
void KIUI::ValidatorTransferToWindowWithoutEvents( wxValidator& aValidator )
|
|
{
|
|
wxWindow* ctrl = aValidator.GetWindow();
|
|
|
|
wxCHECK_RET( ctrl != nullptr, wxS( "Transferring validator data without a control" ) );
|
|
|
|
wxEventBlocker orient_update_blocker( ctrl, wxEVT_ANY );
|
|
aValidator.TransferToWindow();
|
|
}
|
|
|
|
|
|
FIELD_VALIDATOR::FIELD_VALIDATOR( FIELD_T aFieldId, wxString* aValue ) :
|
|
wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST, aValue ),
|
|
m_fieldId( aFieldId )
|
|
{
|
|
// Fields cannot contain carriage returns, line feeds, or tabs.
|
|
wxString excludes( wxT( "\r\n\t" ) );
|
|
|
|
// The reference and sheet name fields cannot contain spaces.
|
|
if( aFieldId == FIELD_T::REFERENCE )
|
|
{
|
|
excludes += wxT( " " );
|
|
}
|
|
else if( m_fieldId == FIELD_T::SHEET_NAME )
|
|
{
|
|
excludes += wxT( "/" );
|
|
}
|
|
|
|
long style = GetStyle();
|
|
|
|
// The reference, sheetname and sheetfilename fields cannot be empty.
|
|
if( aFieldId == FIELD_T::REFERENCE
|
|
|| aFieldId == FIELD_T::SHEET_NAME
|
|
|| aFieldId == FIELD_T::SHEET_FILENAME )
|
|
{
|
|
style |= wxFILTER_EMPTY;
|
|
}
|
|
|
|
SetStyle( style );
|
|
SetCharExcludes( excludes );
|
|
}
|
|
|
|
|
|
FIELD_VALIDATOR::FIELD_VALIDATOR( const FIELD_VALIDATOR& aValidator ) :
|
|
wxTextValidator( aValidator ),
|
|
m_fieldId( aValidator.m_fieldId )
|
|
{
|
|
}
|
|
|
|
|
|
bool FIELD_VALIDATOR::Validate( wxWindow* aParent )
|
|
{
|
|
// If window is disabled, simply return
|
|
if( !m_validatorWindow->IsEnabled() )
|
|
return true;
|
|
|
|
wxTextEntry* const text = GetTextEntry();
|
|
|
|
if( !text )
|
|
return false;
|
|
|
|
wxString val( text->GetValue() );
|
|
|
|
return DoValidate( val, aParent );
|
|
}
|
|
|
|
|
|
wxString GetFieldValidationErrorMessage( FIELD_T aFieldId, const wxString& aValue )
|
|
{
|
|
FIELD_VALIDATOR validator( aFieldId );
|
|
wxString msg;
|
|
|
|
if( validator.HasFlag( wxFILTER_EMPTY ) && aValue.empty() )
|
|
{
|
|
switch( aFieldId )
|
|
{
|
|
case FIELD_T::SHEET_NAME: msg = _( "A sheet must have a name." ); break;
|
|
case FIELD_T::SHEET_FILENAME: msg = _( "A sheet must have a file specified." ); break;
|
|
default: msg = _( "The value of the field cannot be empty." ); break;
|
|
}
|
|
}
|
|
|
|
if( msg.empty() && validator.HasFlag( wxFILTER_EXCLUDE_CHAR_LIST ) )
|
|
{
|
|
wxArrayString badCharsFound;
|
|
|
|
for( const wxUniCharRef& excludeChar : validator.GetCharExcludes() )
|
|
{
|
|
if( aValue.Find( excludeChar ) != wxNOT_FOUND )
|
|
{
|
|
if( excludeChar == '\r' )
|
|
badCharsFound.Add( _( "carriage return" ) );
|
|
else if( excludeChar == '\n' )
|
|
badCharsFound.Add( _( "line feed" ) );
|
|
else if( excludeChar == '\t' )
|
|
badCharsFound.Add( _( "tab" ) );
|
|
else if( excludeChar == ' ' )
|
|
badCharsFound.Add( _( "space" ) );
|
|
else
|
|
badCharsFound.Add( wxString::Format( wxT( "'%c'" ), excludeChar ) );
|
|
}
|
|
}
|
|
|
|
if( !badCharsFound.IsEmpty() )
|
|
{
|
|
wxString badChars;
|
|
|
|
for( size_t i = 0; i < badCharsFound.GetCount(); i++ )
|
|
{
|
|
if( !badChars.IsEmpty() )
|
|
{
|
|
if( badCharsFound.GetCount() == 2 )
|
|
{
|
|
badChars += _( " or " );
|
|
}
|
|
else
|
|
{
|
|
if( i < badCharsFound.GetCount() - 2 )
|
|
badChars += _( ", or " );
|
|
else
|
|
badChars += wxT( ", " );
|
|
}
|
|
}
|
|
|
|
badChars += badCharsFound.Item( i );
|
|
}
|
|
|
|
switch( aFieldId )
|
|
{
|
|
case FIELD_T::REFERENCE:
|
|
msg.Printf( _( "The reference designator cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
case FIELD_T::VALUE:
|
|
msg.Printf( _( "The value field cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
case FIELD_T::FOOTPRINT:
|
|
msg.Printf( _( "The footprint field cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
case FIELD_T::DATASHEET:
|
|
msg.Printf( _( "The datasheet field cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
case FIELD_T::SHEET_NAME:
|
|
msg.Printf( _( "The sheet name cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
case FIELD_T::SHEET_FILENAME:
|
|
msg.Printf( _( "The sheet filename cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
|
|
default:
|
|
msg.Printf( _( "The field cannot contain %s character(s)." ), badChars );
|
|
break;
|
|
};
|
|
}
|
|
}
|
|
|
|
if( msg.empty() )
|
|
{
|
|
if( aFieldId == FIELD_T::REFERENCE && aValue.Contains( wxT( "${" ) ) )
|
|
{
|
|
msg.Printf( _( "The reference designator cannot contain text variable references" ) );
|
|
}
|
|
else if( aFieldId == FIELD_T::REFERENCE && UTIL::GetRefDesPrefix( aValue ).IsEmpty() )
|
|
{
|
|
msg.Printf( _( "References must start with a letter." ) );
|
|
}
|
|
}
|
|
|
|
return msg;
|
|
}
|
|
|
|
|
|
bool FIELD_VALIDATOR::DoValidate( const wxString& aValue, wxWindow* aParent )
|
|
{
|
|
wxString msg = GetFieldValidationErrorMessage( m_fieldId, aValue );
|
|
|
|
if( !msg.empty() )
|
|
{
|
|
if( m_validatorWindow )
|
|
m_validatorWindow->SetFocus();
|
|
|
|
wxMessageBox( msg, _( "Field Validation Error" ), wxOK | wxICON_EXCLAMATION, aParent );
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|