Eeschema: Symbol editor: pin table CSV interchange

This adds the ability to export the pin table content to a CSV file
or the clipboard as CSV, then re-import it from CSV or TSV. This allows:

* to round-trip pin table data via a spreadsheet program, so that the pin
  data can be manipulated in a richer/more familiar editing environment
* an import method to bring in tabular pin data from other formats
  "semi-automatically", without having to write a full-blown symbol generator.

Relates-To: https://gitlab.com/kicad/code/kicad/-/issues/19207
This commit is contained in:
John Beard 2025-05-02 23:17:30 +08:00
parent ceab28bc21
commit 4c8b971021
23 changed files with 4348 additions and 724 deletions

View File

@ -244,6 +244,7 @@ target_link_libraries( kicommon
fmt::fmt
CURL::libcurl
picosha2
rapidcsv
${ZSTD_LIBRARY}
${wxWidgets_LIBRARIES}
${LIBGIT2_LIBRARIES}
@ -556,6 +557,8 @@ set( COMMON_IO_SRCS
# EasyEDA pro
io/easyedapro/easyedapro_parser.cpp
io/easyedapro/easyedapro_import_utils.cpp
io/csv.cpp
)
set ( COMMON_TRANSLINE_CALCULATION_SRCS

View File

@ -26,6 +26,12 @@
#include <wx/clipbrd.h>
#include <wx/image.h>
#include <wx/log.h>
#include <wx/string.h>
#include <wx/sstream.h>
#include <sstream>
#include <io/csv.h>
bool SaveClipboard( const std::string& aTextUTF8 )
@ -106,4 +112,66 @@ std::unique_ptr<wxImage> GetImageFromClipboard()
}
return image;
}
}
bool SaveTabularDataToClipboard( const std::vector<std::vector<wxString>>& aData )
{
wxLogNull doNotLog; // disable logging of failed clipboard actions
if( wxTheClipboard->Open() )
{
wxDataObjectComposite* data = new wxDataObjectComposite();
// Set plain text CSV
{
wxStringOutputStream os;
CSV_WRITER writer( os );
writer.WriteLines( aData );
data->Add( new wxTextDataObject( os.GetString() ), true );
}
// At this point, it would be great if we could add some format that spreadsheet
// programs can understand without asking the user for options: perhaps SYLK or DIF.
// But it doesn't seem like WX allows to put arbitrary MIME types on the clipboard,
// even with wxCustomDataObject( wxDataFormat( "mime/type" ) ), which just ends up as
// wxDF_PRIVATE, and wxDF_SYLK/DIF aren't mapped on GTK.
wxTheClipboard->SetData( data );
wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
wxTheClipboard->Close();
return true;
}
return false;
}
bool GetTabularDataFromClipboard( std::vector<std::vector<wxString>>& aData )
{
// Again, it would be ideal if we could detect a spreadsheet mimetype here,
// but WX doesn't seem to do that on Linux, at least.
bool ok = false;
// First try for text data
if( wxTheClipboard->Open() )
{
if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
{
wxTextDataObject data;
if( wxTheClipboard->GetData( data ) )
{
ok = AutoDecodeCSV( data.GetText(), aData );
}
}
// We could also handle .csv wxDF_FILENAMEs here
wxTheClipboard->Close();
}
return ok;
}

98
common/io/csv.cpp Normal file
View File

@ -0,0 +1,98 @@
#include "csv.h"
#include <wx/txtstrm.h>
#include <rapidcsv/rapidcsv.h>
CSV_WRITER::CSV_WRITER( wxOutputStream& aStream ) :
m_stream( aStream ), m_delimiter( wxT( "," ) ), m_quote( wxT( "\"" ) ), m_lineTerminator( wxT( "\n" ) ),
m_escape( wxEmptyString )
{
}
void CSV_WRITER::WriteLines( const std::vector<std::vector<wxString>>& aRows )
{
for( const auto& row : aRows )
{
WriteLine( row );
}
}
void CSV_WRITER::WriteLine( const std::vector<wxString>& cols )
{
wxString line;
for( size_t i = 0; i < cols.size(); ++i )
{
wxString colVal = cols[i];
if( i > 0 )
line += m_delimiter;
double test;
if( !colVal.ToDouble( &test ) )
{
bool useEscape = m_escape.size();
if( useEscape )
{
colVal.Replace( m_quote, m_escape + m_quote, true );
}
else
{
colVal.Replace( m_quote, m_quote + m_quote, true );
}
colVal = m_quote + colVal + m_quote;
}
line += colVal;
}
line += m_lineTerminator;
m_stream.Write( line.c_str(), line.length() );
}
bool AutoDecodeCSV( const wxString& aInput, std::vector<std::vector<wxString>>& aData )
{
// Read the first line to determine the delimiter
bool trimCells = true;
bool skipCommentLines = true;
bool skipEmptyLines = true;
char commentPrefix = '#';
char delimiter = ',';
// Assume if we find a tab, we are dealing with a TSV file
if( aInput.find( '\t' ) != std::string::npos )
{
delimiter = '\t';
}
std::istringstream inputStream( aInput.ToStdString() );
rapidcsv::Document doc( inputStream, rapidcsv::LabelParams( -1, -1 ),
rapidcsv::SeparatorParams( delimiter, trimCells ), rapidcsv::ConverterParams(),
rapidcsv::LineReaderParams( skipCommentLines, commentPrefix, skipEmptyLines ) );
// Read the data into aData
aData.clear();
for( size_t i = 0; i < doc.GetRowCount(); ++i )
{
std::vector<wxString>& row = aData.emplace_back();
for( size_t j = 0; j < doc.GetColumnCount(); ++j )
{
std::string cell = doc.GetCell<std::string>( j, i );
row.emplace_back( cell );
}
}
// Anything in the first row?
return aData[0].size() > 0;
}

78
common/io/csv.h Normal file
View File

@ -0,0 +1,78 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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 3
* 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:
* https://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <wx/string.h>
#include <wx/stream.h>
#include <istream>
class CSV_WRITER
{
public:
CSV_WRITER( wxOutputStream& aStream );
/**
* Write a single row to the stream.
*/
void WriteLine( const std::vector<wxString>& aCols );
/**
* Write a vector of rows to the stream.
*
* @param cols The rows to write.
*/
void WriteLines( const std::vector<std::vector<wxString>>& aRows );
void SetDelimiter( const wxString& aDelimiter )
{
m_delimiter = aDelimiter;
}
/**
* Set the delimiter escape char. If set to wxEmptyString (default), the
* delimiter is doubled for escaping.
*/
void SetEscape( const wxString& aEscape )
{
m_escape = aEscape;
}
private:
wxOutputStream& m_stream;
wxString m_delimiter;
wxString m_quote;
wxString m_lineTerminator;
wxString m_escape;
};
/**
* Try to guess the format of a T/CSV file and decode it into aData.
*
* This can handle the most common cases of CSV files and TSV files
* (double/single quoted strings, commas or tabs as delimiters, but it's
* not completely foolproof).
*/
bool AutoDecodeCSV( const wxString& aInput, std::vector<std::vector<wxString>>& aData );

View File

@ -198,6 +198,7 @@ const std::string FILEEXT::JpegFileExtension( "jpg" );
const std::string FILEEXT::TextFileExtension( "txt" );
const std::string FILEEXT::MarkdownFileExtension( "md" );
const std::string FILEEXT::CsvFileExtension( "csv" );
const std::string FILEEXT::TsvFileExtension( "tsv" );
const std::string FILEEXT::XmlFileExtension( "xml" );
const std::string FILEEXT::JsonFileExtension( "json" );
const std::string FILEEXT::PythonFileExtension( "py" );
@ -415,6 +416,12 @@ wxString FILEEXT::CsvFileWildcard()
}
wxString FILEEXT::CsvTsvFileWildcard()
{
return _( "CSV/TSV Files" ) + AddFileExtListToFilter( { CsvFileExtension, TsvFileExtension } );
}
wxString FILEEXT::PdfFileWildcard()
{
return _( "Portable document format files" ) + AddFileExtListToFilter( { "pdf" } );

View File

@ -22,23 +22,33 @@
*/
#include "dialog_lib_edit_pin_table.h"
#include <wx/filedlg.h>
#include <wx/wfstream.h>
#include <wx/msgdlg.h>
#include <wx/tokenzr.h>
#include "grid_tricks.h"
#include <richio.h>
#include <sch_pin.h>
#include <pin_numbers.h>
#include "pgm_base.h"
#include <base_units.h>
#include <bitmaps.h>
#include <clipboard.h>
#include <confirm.h>
#include <symbol_edit_frame.h>
#include <symbol_editor_settings.h>
#include <io/csv.h>
#include <kiplatform/ui.h>
#include <widgets/grid_icon_text_helpers.h>
#include <widgets/grid_combobox.h>
#include <widgets/wx_grid.h>
#include <widgets/bitmap_button.h>
#include <widgets/std_bitmap_button.h>
#include <wildcards_and_files_ext.h>
#include <settings/settings_manager.h>
#include <wx/tokenzr.h>
#include <tool/action_menu.h>
#include <string_utils.h>
#define UNITS_ALL _( "ALL" )
@ -47,6 +57,266 @@
#define DEMORGAN_ALT _( "Alternate" )
static wxString GetPinTableColLabel( int aCol )
{
switch( aCol )
{
case COL_PIN_COUNT: return _( "Count" );
case COL_NUMBER: return _( "Number" );
case COL_NAME: return _( "Name" );
case COL_TYPE: return _( "Electrical Type" );
case COL_SHAPE: return _( "Graphic Style" );
case COL_ORIENTATION: return _( "Orientation" );
case COL_NUMBER_SIZE: return _( "Number Text Size" );
case COL_NAME_SIZE: return _( "Name Text Size" );
case COL_LENGTH: return _( "Length" );
case COL_POSX: return _( "X Position" );
case COL_POSY: return _( "Y Position" );
case COL_VISIBLE: return _( "Visible" );
case COL_UNIT: return _( "Unit" );
case COL_DEMORGAN: return _( "De Morgan" );
default: wxFAIL; return wxEmptyString;
}
}
static COL_ORDER GetColTypeForString( const wxString& aStr )
{
for( int i = 0; i < COL_COUNT; i++ )
{
if( GetPinTableColLabel( i ).IsSameAs( aStr, false ) )
return (COL_ORDER) i;
}
return COL_COUNT;
}
/**
* Class that handles conversion of various pin data fields into strings for display in the
* UI or serialisation to formats like CSV.
*/
class PIN_INFO_FORMATTER
{
public:
enum class BOOL_FORMAT
{
ZERO_ONE,
TRUE_FALSE,
};
PIN_INFO_FORMATTER( UNITS_PROVIDER& aUnitsProvider, bool aIncludeUnits, BOOL_FORMAT aBoolFormat ) :
m_unitsProvider( aUnitsProvider ),
m_includeUnits( aIncludeUnits ),
m_boolFormat( aBoolFormat )
{
}
wxString Format( const SCH_PIN& aPin, int aFieldId ) const
{
wxString val;
switch( aFieldId )
{
case COL_NAME:
val << aPin.GetName();
break;
case COL_NUMBER:
val << aPin.GetNumber();
break;
case COL_TYPE:
val << PinTypeNames()[static_cast<int>( aPin.GetType() )];
break;
case COL_SHAPE:
val << PinShapeNames()[static_cast<int>( aPin.GetShape() )];
break;
case COL_ORIENTATION:
{
const int index = PinOrientationIndex( aPin.GetOrientation() );
if( index >= 0)
val << PinOrientationNames()[ index ];
break;
}
case COL_NUMBER_SIZE:
val << m_unitsProvider.StringFromValue( aPin.GetNumberTextSize(), m_includeUnits );
break;
case COL_NAME_SIZE:
val << m_unitsProvider.StringFromValue( aPin.GetNameTextSize(), m_includeUnits );
break;
case COL_LENGTH:
val << m_unitsProvider.StringFromValue( aPin.GetLength(), m_includeUnits );
break;
case COL_POSX:
val << m_unitsProvider.StringFromValue( aPin.GetPosition().x, m_includeUnits );
break;
case COL_POSY:
val << m_unitsProvider.StringFromValue( aPin.GetPosition().y, m_includeUnits );
break;
case COL_VISIBLE:
val << stringFromBool( aPin.IsVisible() );
break;
case COL_UNIT:
if( aPin.GetUnit() )
val << LIB_SYMBOL::LetterSubReference( aPin.GetUnit(), 'A' );
else
val << UNITS_ALL;
break;
case COL_DEMORGAN:
switch( aPin.GetBodyStyle() )
{
case BODY_STYLE::BASE:
val << DEMORGAN_STD;
break;
case BODY_STYLE::DEMORGAN:
val << DEMORGAN_ALT;
break;
default:
val << DEMORGAN_ALL;
break;
}
break;
default:
wxFAIL_MSG( wxString::Format( "Invalid field id %d", aFieldId ) );
break;
}
return val;
}
/**
* Update the pin from the given col/string.
*
* How much this should follow the format is debatable, but for now it's fairly permissive
* (e.g. bools import as 0/1 and no/yes).
*/
void UpdatePin( SCH_PIN& aPin, const wxString& aValue, int aFieldId, const LIB_SYMBOL& aSymbol ) const
{
switch( aFieldId )
{
case COL_NUMBER:
aPin.SetNumber( aValue );
break;
case COL_NAME:
aPin.SetName( aValue );
break;
case COL_TYPE:
if( PinTypeNames().Index( aValue, false ) != wxNOT_FOUND )
aPin.SetType( (ELECTRICAL_PINTYPE) PinTypeNames().Index( aValue ) );
break;
case COL_SHAPE:
if( PinShapeNames().Index( aValue, false ) != wxNOT_FOUND )
aPin.SetShape( (GRAPHIC_PINSHAPE) PinShapeNames().Index( aValue ) );
break;
case COL_ORIENTATION:
if( PinOrientationNames().Index( aValue, false ) != wxNOT_FOUND )
aPin.SetOrientation( (PIN_ORIENTATION) PinOrientationNames().Index( aValue ) );
break;
case COL_NUMBER_SIZE:
aPin.SetNumberTextSize( m_unitsProvider.ValueFromString( aValue ) );
break;
case COL_NAME_SIZE:
aPin.SetNameTextSize( m_unitsProvider.ValueFromString( aValue ) );
break;
case COL_LENGTH:
aPin.ChangeLength( m_unitsProvider.ValueFromString( aValue ) );
break;
case COL_POSX:
aPin.SetPosition( VECTOR2I( m_unitsProvider.ValueFromString( aValue ),
aPin.GetPosition().y ) );
break;
case COL_POSY:
aPin.SetPosition( VECTOR2I( aPin.GetPosition().x, m_unitsProvider.ValueFromString( aValue ) ) );
break;
case COL_VISIBLE:
aPin.SetVisible(boolFromString( aValue ));
break;
case COL_UNIT:
if( aValue == UNITS_ALL )
{
aPin.SetUnit( 0 );
}
else
{
for( int i = 1; i <= aSymbol.GetUnitCount(); i++ )
{
if( aValue == LIB_SYMBOL::LetterSubReference( i, 'A' ) )
{
aPin.SetUnit( i );
break;
}
}
}
break;
case COL_DEMORGAN:
if( aValue == DEMORGAN_STD )
aPin.SetBodyStyle( 1 );
else if( aValue == DEMORGAN_ALT )
aPin.SetBodyStyle( 2 );
else
aPin.SetBodyStyle( 0 );
break;
default:
wxFAIL_MSG( wxString::Format( "Invalid field id %d", aFieldId ) );
break;
}
}
private:
wxString stringFromBool( bool aValue ) const
{
switch( m_boolFormat )
{
case BOOL_FORMAT::ZERO_ONE: return aValue ? wxT( "1" ) : wxT( "0" );
case BOOL_FORMAT::TRUE_FALSE:
return aValue ? _( "True" ) : _( "False" );
// no default
}
wxCHECK_MSG( false, wxEmptyString, "Invalid BOOL_FORMAT" );
}
bool boolFromString( const wxString& aValue ) const
{
if( aValue == wxS( "1" ) )
{
return true;
}
else if( aValue == wxS( "0" ) )
{
return false;
}
else if( aValue.IsSameAs( _( "True" ), false ) )
{
return true;
}
else if( aValue.IsSameAs( _( "False" ), false ) )
{
return false;
}
else
{
wxFAIL_MSG( wxString::Format( "string '%s' can't be converted to boolean correctly, "
"it will have been perceived as FALSE",
aValue ) );
return false;
}
}
UNITS_PROVIDER& m_unitsProvider;
bool m_includeUnits;
BOOL_FORMAT m_boolFormat;
};
void getSelectedArea( WX_GRID* aGrid, int* aRowStart, int* aRowCount )
{
wxGridCellCoordsArray topLeft = aGrid->GetSelectionBlockTopLeft();
@ -114,24 +384,7 @@ public:
wxString GetColLabelValue( int aCol ) override
{
switch( aCol )
{
case COL_PIN_COUNT: return _( "Count" );
case COL_NUMBER: return _( "Number" );
case COL_NAME: return _( "Name" );
case COL_TYPE: return _( "Electrical Type" );
case COL_SHAPE: return _( "Graphic Style" );
case COL_ORIENTATION: return _( "Orientation" );
case COL_NUMBER_SIZE: return _( "Number Text Size" );
case COL_NAME_SIZE: return _( "Name Text Size" );
case COL_LENGTH: return _( "Length" );
case COL_POSX: return _( "X Position" );
case COL_POSY: return _( "Y Position" );
case COL_VISIBLE: return _( "Visible" );
case COL_UNIT: return _( "Unit" );
case COL_DEMORGAN: return _( "De Morgan" );
default: wxFAIL; return wxEmptyString;
}
return GetPinTableColLabel( aCol );
}
bool IsEmptyCell( int row, int col ) override
@ -163,87 +416,18 @@ public:
if( pins.empty() )
return fieldValue;
for( SCH_PIN* pin : pins )
PIN_INFO_FORMATTER formatter( *aParentFrame, true, PIN_INFO_FORMATTER::BOOL_FORMAT::ZERO_ONE );
for( const SCH_PIN* pin : pins )
{
wxString val;
switch( aCol )
{
case COL_PIN_COUNT:
val << pins.size();
break;
case COL_NUMBER:
val = pin->GetNumber();
break;
case COL_NAME:
val = pin->GetName();
break;
case COL_TYPE:
val = PinTypeNames()[static_cast<int>( pin->GetType() )];
break;
case COL_SHAPE:
val = PinShapeNames()[static_cast<int>( pin->GetShape() )];
break;
case COL_ORIENTATION:
if( PinOrientationIndex( pin->GetOrientation() ) >= 0 )
val = PinOrientationNames()[ PinOrientationIndex( pin->GetOrientation() ) ];
break;
case COL_NUMBER_SIZE:
val = aParentFrame->StringFromValue( pin->GetNumberTextSize(), true );
break;
case COL_NAME_SIZE:
val = aParentFrame->StringFromValue( pin->GetNameTextSize(), true );
break;
case COL_LENGTH:
val = aParentFrame->StringFromValue( pin->GetLength(), true );
break;
case COL_POSX:
val = aParentFrame->StringFromValue( pin->GetPosition().x, true );
break;
case COL_POSY:
val = aParentFrame->StringFromValue( pin->GetPosition().y, true );
break;
case COL_VISIBLE:
val = StringFromBool( pin->IsVisible() );
break;
case COL_UNIT:
if( pin->GetUnit() )
val = LIB_SYMBOL::LetterSubReference( pin->GetUnit(), 'A' );
else
val = UNITS_ALL;
break;
case COL_DEMORGAN:
switch( pin->GetBodyStyle() )
{
case BODY_STYLE::BASE:
val = DEMORGAN_STD;
break;
case BODY_STYLE::DEMORGAN:
val = DEMORGAN_ALT;
break;
default:
val = DEMORGAN_ALL;
break;
}
break;
default:
wxFAIL;
val << formatter.Format( *pin, aCol );
break;
}
@ -365,96 +549,11 @@ public:
return;
}
PIN_INFO_FORMATTER formatter( *m_frame, true, PIN_INFO_FORMATTER::BOOL_FORMAT::ZERO_ONE );
for( SCH_PIN* pin : pins )
{
switch( aCol )
{
case COL_NUMBER:
if( !m_pinTable->IsDisplayGrouped() )
pin->SetNumber( value );
break;
case COL_NAME:
pin->SetName( value );
break;
case COL_TYPE:
if( PinTypeNames().Index( value ) != wxNOT_FOUND )
pin->SetType( (ELECTRICAL_PINTYPE) PinTypeNames().Index( value ) );
break;
case COL_SHAPE:
if( PinShapeNames().Index( value ) != wxNOT_FOUND )
pin->SetShape( (GRAPHIC_PINSHAPE) PinShapeNames().Index( value ) );
break;
case COL_ORIENTATION:
if( PinOrientationNames().Index( value ) != wxNOT_FOUND )
pin->SetOrientation(
PinOrientationCode( PinOrientationNames().Index( value ) ) );
break;
case COL_NUMBER_SIZE:
pin->SetNumberTextSize( m_frame->ValueFromString( value ) );
break;
case COL_NAME_SIZE:
pin->SetNameTextSize( m_frame->ValueFromString( value ) );
break;
case COL_LENGTH:
pin->ChangeLength( m_frame->ValueFromString( value ) );
break;
case COL_POSX:
pin->SetPosition( VECTOR2I( m_frame->ValueFromString( value ),
pin->GetPosition().y ) );
break;
case COL_POSY:
pin->SetPosition( VECTOR2I( pin->GetPosition().x,
m_frame->ValueFromString( value ) ) );
break;
case COL_VISIBLE:
pin->SetVisible(BoolFromString( value ));
break;
case COL_UNIT:
if( value == UNITS_ALL )
{
pin->SetUnit( 0 );
}
else
{
for( int i = 1; i <= m_symbol->GetUnitCount(); i++ )
{
if( value == LIB_SYMBOL::LetterSubReference( i, 'A' ) )
{
pin->SetUnit( i );
break;
}
}
}
break;
case COL_DEMORGAN:
if( value == DEMORGAN_STD )
pin->SetBodyStyle( 1 );
else if( value == DEMORGAN_ALT )
pin->SetBodyStyle( 2 );
else
pin->SetBodyStyle( 0 );
break;
default:
wxFAIL;
break;
}
formatter.UpdatePin( *pin, value, aCol, *m_symbol );
}
m_edited = true;
@ -672,34 +771,6 @@ public:
return m_edited;
}
private:
static wxString StringFromBool( bool aValue )
{
if( aValue )
return wxT( "1" );
else
return wxT( "0" );
}
static bool BoolFromString( wxString aValue )
{
if( aValue == wxS( "1" ) )
{
return true;
}
else if( aValue == wxS( "0" ) )
{
return false;
}
else
{
wxFAIL_MSG( wxString::Format( "string '%s' can't be converted to boolean correctly, "
"it will have been perceived as FALSE",
aValue ) );
return false;
}
}
private:
SYMBOL_EDIT_FRAME* m_frame;
@ -719,6 +790,185 @@ private:
};
class PIN_TABLE_EXPORT
{
public:
PIN_TABLE_EXPORT( UNITS_PROVIDER& aUnitsProvider ) :
m_unitsProvider( aUnitsProvider )
{
}
void ExportData( std::vector<SCH_PIN*>& aPins, const wxString& aToFile ) const
{
std::vector<int> exportCols {
COL_NUMBER,
COL_NAME,
COL_TYPE,
COL_SHAPE,
COL_ORIENTATION,
COL_NUMBER_SIZE,
COL_NAME_SIZE,
COL_LENGTH,
COL_POSX,
COL_POSY,
COL_VISIBLE,
COL_UNIT,
COL_DEMORGAN,
};
std::vector<std::vector<wxString>> exportTable;
exportTable.reserve( aPins.size() + 1 );
std::vector<wxString> headers;
for( int col : exportCols )
{
headers.push_back( GetPinTableColLabel( col ) );
}
exportTable.emplace_back( std::move( headers ) );
PIN_INFO_FORMATTER formatter( m_unitsProvider, false, PIN_INFO_FORMATTER::BOOL_FORMAT::TRUE_FALSE );
for( const SCH_PIN* pin : aPins )
{
std::vector<wxString>& cols = exportTable.emplace_back( 0 );
cols.reserve( exportCols.size() );
for( int col : exportCols )
{
cols.emplace_back( formatter.Format( *pin, col ) );
}
}
if( !aToFile.IsEmpty() )
{
wxFileOutputStream os( aToFile );
CSV_WRITER writer( os );
writer.WriteLines( exportTable );
}
else
{
SaveTabularDataToClipboard( exportTable );
}
}
private:
UNITS_PROVIDER& m_unitsProvider;
};
class PIN_TABLE_IMPORT
{
public:
PIN_TABLE_IMPORT( EDA_BASE_FRAME& aFrame ) :
m_frame( aFrame )
{
}
std::vector<std::unique_ptr<SCH_PIN>> ImportData( bool aFromFile, LIB_SYMBOL& aSym ) const
{
wxString path;
if( aFromFile )
{
path = promptForFile();
if( path.IsEmpty() )
return {};
}
std::vector<std::vector<wxString>> csvData;
bool ok = false;
if( !path.IsEmpty() )
{
// Read file content
wxString csvFileContent = SafeReadFile( path, "r" );
ok = AutoDecodeCSV( csvFileContent, csvData );
}
else
{
ok = GetTabularDataFromClipboard( csvData );
}
std::vector<std::unique_ptr<SCH_PIN>> pins;
PIN_INFO_FORMATTER formatter( m_frame, false, PIN_INFO_FORMATTER::BOOL_FORMAT::TRUE_FALSE );
if( ok )
{
// The first thing we need to do is map the CSV columns to the pin table columns
// (in case the user reorders them)
std::vector<COL_ORDER> headerCols = getColOrderFromCSV( csvData[0] );
for( size_t i = 1; i < csvData.size(); ++i )
{
std::vector<wxString>& cols = csvData[i];
auto pin = std::make_unique<SCH_PIN>( &aSym );
// Ignore cells that stick out to the right of the headers
size_t maxCol = std::min( headerCols.size(), cols.size() );
for( size_t j = 0; j < maxCol; ++j )
{
// Skip unrecognised columns
if( headerCols[j] == COL_COUNT )
continue;
formatter.UpdatePin( *pin, cols[j], headerCols[j], aSym );
}
pins.emplace_back( std::move( pin ) );
}
}
return pins;
}
private:
wxString promptForFile() const
{
wxFileDialog dlg( &m_frame, _( "Select pin data file" ), "", "", FILEEXT::CsvTsvFileWildcard(),
wxFD_OPEN | wxFD_FILE_MUST_EXIST );
if( dlg.ShowModal() == wxID_CANCEL )
return wxEmptyString;
return dlg.GetPath();
}
std::vector<COL_ORDER> getColOrderFromCSV( const std::vector<wxString>& aHeaderRow ) const
{
std::vector<COL_ORDER> colOrder;
wxArrayString unknownHeaders;
for( size_t i = 0; i < aHeaderRow.size(); ++i )
{
COL_ORDER col = GetColTypeForString( aHeaderRow[i] );
if( col >= COL_COUNT )
unknownHeaders.push_back( aHeaderRow[i] );
colOrder.push_back( col );
}
if( unknownHeaders.size() )
{
wxString msg = wxString::Format( _( "Unknown columns in data: %s. These columns will be ignored." ),
AccumulateDescriptions( unknownHeaders ) );
showWarning( msg );
}
return colOrder;
}
void showWarning( const wxString& aMsg ) const
{
wxMessageBox( aMsg, _( "CSV import warning" ), wxOK | wxICON_WARNING );
}
EDA_BASE_FRAME& m_frame;
};
DIALOG_LIB_EDIT_PIN_TABLE::DIALOG_LIB_EDIT_PIN_TABLE( SYMBOL_EDIT_FRAME* parent,
LIB_SYMBOL* aSymbol ) :
DIALOG_LIB_EDIT_PIN_TABLE_BASE( parent ),
@ -1134,6 +1384,82 @@ void DIALOG_LIB_EDIT_PIN_TABLE::OnFilterChoice( wxCommandEvent& event )
}
void DIALOG_LIB_EDIT_PIN_TABLE::OnImportButtonClick( wxCommandEvent& event )
{
bool fromFile = event.GetEventObject() == m_btnImportFromFile;
bool replaceAll = m_rbReplaceAll->GetValue();
PIN_TABLE_IMPORT importer( *m_editFrame );
std::vector<std::unique_ptr<SCH_PIN>> newPins = importer.ImportData( fromFile, *m_symbol );
if( !newPins.size() )
{
return;
}
if( replaceAll )
{
// This is quite a dance with a segfault without smart pointers
for( SCH_PIN* pin : m_pins )
delete pin;
m_pins.clear();
}
for( auto& newPin : newPins )
{
m_pins.push_back( newPin.release() );
}
m_cbGroup->SetValue( false );
m_dataModel->RebuildRows( m_pins, false, false );
updateSummary();
}
void DIALOG_LIB_EDIT_PIN_TABLE::OnExportButtonClick( wxCommandEvent& event )
{
bool toFile = event.GetEventObject() == m_btnExportToFile;
bool onlyShown = m_rbExportOnlyShownPins->GetValue();
wxString filePath = wxEmptyString;
if( toFile )
{
wxFileName fn( m_symbol->GetName() );
fn.SetExt( FILEEXT::CsvFileExtension );
wxFileDialog dlg( this, _( "Select pin data file" ), "", fn.GetFullName(), FILEEXT::CsvFileWildcard(),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
if( dlg.ShowModal() == wxID_CANCEL )
return;
filePath = dlg.GetPath();
}
std::vector<SCH_PIN*> pinsToExport;
if( onlyShown )
{
for( int i = 0; i < m_dataModel->GetNumberRows(); ++i )
{
for( SCH_PIN* pin : m_dataModel->GetRowPins( i ) )
{
pinsToExport.push_back( pin );
}
}
}
else
{
pinsToExport = m_pins;
}
PIN_TABLE_EXPORT exporter( *m_editFrame );
exporter.ExportData( pinsToExport, filePath );
}
void DIALOG_LIB_EDIT_PIN_TABLE::adjustGridColumns()
{
// Account for scroll bars

View File

@ -46,6 +46,7 @@ enum COL_ORDER
};
class ACTION_MENU;
class PIN_TABLE_DATA_MODEL;
class SYMBOL_EDIT_FRAME;
@ -69,6 +70,8 @@ public:
void OnGroupSelected( wxCommandEvent& event ) override;
void OnFilterCheckBox( wxCommandEvent& event ) override;
void OnFilterChoice( wxCommandEvent& event ) override;
void OnImportButtonClick( wxCommandEvent& event ) override;
void OnExportButtonClick( wxCommandEvent& event ) override;
void OnUpdateUI( wxUpdateUIEvent& event ) override;
void OnCancel( wxCommandEvent& event ) override;
void OnClose( wxCloseEvent& event ) override;

View File

@ -20,6 +20,12 @@ DIALOG_LIB_EDIT_PIN_TABLE_BASE::DIALOG_LIB_EDIT_PIN_TABLE_BASE( wxWindow* parent
wxBoxSizer* top_sizer;
top_sizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer* bSizer6;
bSizer6 = new wxBoxSizer( wxHORIZONTAL );
wxBoxSizer* bLeftGridSizer;
bLeftGridSizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer* bSummarySizer;
bSummarySizer = new wxBoxSizer( wxHORIZONTAL );
@ -54,7 +60,7 @@ DIALOG_LIB_EDIT_PIN_TABLE_BASE::DIALOG_LIB_EDIT_PIN_TABLE_BASE( wxWindow* parent
bSummarySizer->Add( m_duplicate_pins, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
top_sizer->Add( bSummarySizer, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 );
bLeftGridSizer->Add( bSummarySizer, 0, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 10 );
wxBoxSizer* bGridMarginsSizer;
bGridMarginsSizer = new wxBoxSizer( wxVERTICAL );
@ -115,7 +121,79 @@ DIALOG_LIB_EDIT_PIN_TABLE_BASE::DIALOG_LIB_EDIT_PIN_TABLE_BASE( wxWindow* parent
bGridMarginsSizer->Add( m_grid, 1, wxEXPAND|wxLEFT|wxRIGHT|wxTOP, 10 );
top_sizer->Add( bGridMarginsSizer, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
bLeftGridSizer->Add( bGridMarginsSizer, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
bSizer6->Add( bLeftGridSizer, 1, wxEXPAND, 5 );
wxBoxSizer* bRightPaneSizer;
bRightPaneSizer = new wxBoxSizer( wxVERTICAL );
m_exportPane = new wxCollapsiblePane( this, wxID_ANY, _("Export"), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE );
m_exportPane->Collapse( false );
wxBoxSizer* bExportSizer;
bExportSizer = new wxBoxSizer( wxVERTICAL );
m_rbExportAllPins = new wxRadioButton( m_exportPane->GetPane(), wxID_ANY, _("Export all pins"), wxDefaultPosition, wxDefaultSize, 0 );
bExportSizer->Add( m_rbExportAllPins, 0, wxALL, 5 );
m_rbExportOnlyShownPins = new wxRadioButton( m_exportPane->GetPane(), wxID_ANY, _("Export only shown pins"), wxDefaultPosition, wxDefaultSize, 0 );
bExportSizer->Add( m_rbExportOnlyShownPins, 0, wxALL, 5 );
wxBoxSizer* bExportBtnSizer;
bExportBtnSizer = new wxBoxSizer( wxHORIZONTAL );
m_btnExportToFile = new wxButton( m_exportPane->GetPane(), wxID_ANY, _("To File..."), wxDefaultPosition, wxDefaultSize, 0 );
bExportBtnSizer->Add( m_btnExportToFile, 0, wxALL, 5 );
m_btnExportToClipboard = new wxButton( m_exportPane->GetPane(), wxID_ANY, _("To Clipboard"), wxDefaultPosition, wxDefaultSize, 0 );
bExportBtnSizer->Add( m_btnExportToClipboard, 0, wxALL, 5 );
bExportSizer->Add( bExportBtnSizer, 1, wxEXPAND, 5 );
m_exportPane->GetPane()->SetSizer( bExportSizer );
m_exportPane->GetPane()->Layout();
bExportSizer->Fit( m_exportPane->GetPane() );
bRightPaneSizer->Add( m_exportPane, 1, wxEXPAND | wxALL, 5 );
m_importPane = new wxCollapsiblePane( this, wxID_ANY, _("Import"), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE );
m_importPane->Collapse( false );
wxBoxSizer* bImportSizer;
bImportSizer = new wxBoxSizer( wxVERTICAL );
m_rbReplaceAll = new wxRadioButton( m_importPane->GetPane(), wxID_ANY, _("Replace all existing pins"), wxDefaultPosition, wxDefaultSize, 0 );
bImportSizer->Add( m_rbReplaceAll, 0, wxALL, 5 );
m_radioBtn1 = new wxRadioButton( m_importPane->GetPane(), wxID_ANY, _("Append to existing pins"), wxDefaultPosition, wxDefaultSize, 0 );
bImportSizer->Add( m_radioBtn1, 0, wxALL, 5 );
wxBoxSizer* bImportBtnSizer;
bImportBtnSizer = new wxBoxSizer( wxHORIZONTAL );
m_btnImportFromFile = new wxButton( m_importPane->GetPane(), wxID_ANY, _("From File..."), wxDefaultPosition, wxDefaultSize, 0 );
bImportBtnSizer->Add( m_btnImportFromFile, 0, wxALL, 5 );
m_btnImportFromClipboard = new wxButton( m_importPane->GetPane(), wxID_ANY, _("From Clipboard"), wxDefaultPosition, wxDefaultSize, 0 );
bImportBtnSizer->Add( m_btnImportFromClipboard, 0, wxALL, 5 );
bImportSizer->Add( bImportBtnSizer, 1, wxEXPAND, 5 );
m_importPane->GetPane()->SetSizer( bImportSizer );
m_importPane->GetPane()->Layout();
bImportSizer->Fit( m_importPane->GetPane() );
bRightPaneSizer->Add( m_importPane, 1, wxEXPAND | wxALL, 5 );
bSizer6->Add( bRightPaneSizer, 0, wxEXPAND, 5 );
top_sizer->Add( bSizer6, 1, wxEXPAND, 5 );
wxBoxSizer* bBottomSizer;
bBottomSizer = new wxBoxSizer( wxHORIZONTAL );
@ -188,6 +266,10 @@ DIALOG_LIB_EDIT_PIN_TABLE_BASE::DIALOG_LIB_EDIT_PIN_TABLE_BASE( wxWindow* parent
m_grid->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnCellSelected ), NULL, this );
m_grid->Connect( wxEVT_GRID_EDITOR_SHOWN, wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnCellSelected ), NULL, this );
m_grid->Connect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnSize ), NULL, this );
m_btnExportToFile->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnExportButtonClick ), NULL, this );
m_btnExportToClipboard->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnExportButtonClick ), NULL, this );
m_btnImportFromFile->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnImportButtonClick ), NULL, this );
m_btnImportFromClipboard->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnImportButtonClick ), NULL, this );
m_addButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnAddRow ), NULL, this );
m_deleteButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnDeleteRow ), NULL, this );
m_cbGroup->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnRebuildRows ), NULL, this );
@ -207,6 +289,10 @@ DIALOG_LIB_EDIT_PIN_TABLE_BASE::~DIALOG_LIB_EDIT_PIN_TABLE_BASE()
m_grid->Disconnect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnCellSelected ), NULL, this );
m_grid->Disconnect( wxEVT_GRID_EDITOR_SHOWN, wxGridEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnCellSelected ), NULL, this );
m_grid->Disconnect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnSize ), NULL, this );
m_btnExportToFile->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnExportButtonClick ), NULL, this );
m_btnExportToClipboard->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnExportButtonClick ), NULL, this );
m_btnImportFromFile->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnImportButtonClick ), NULL, this );
m_btnImportFromClipboard->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnImportButtonClick ), NULL, this );
m_addButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnAddRow ), NULL, this );
m_deleteButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnDeleteRow ), NULL, this );
m_cbGroup->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_LIB_EDIT_PIN_TABLE_BASE::OnRebuildRows ), NULL, this );

File diff suppressed because it is too large Load Diff

View File

@ -23,11 +23,13 @@ class WX_GRID;
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/bmpbuttn.h>
#include <wx/radiobut.h>
#include <wx/button.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/collpane.h>
#include <wx/bmpbuttn.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/dialog.h>
@ -49,6 +51,16 @@ class DIALOG_LIB_EDIT_PIN_TABLE_BASE : public DIALOG_SHIM
wxStaticText* m_staticTextDuplicatePins;
wxStaticText* m_duplicate_pins;
WX_GRID* m_grid;
wxCollapsiblePane* m_exportPane;
wxRadioButton* m_rbExportAllPins;
wxRadioButton* m_rbExportOnlyShownPins;
wxButton* m_btnExportToFile;
wxButton* m_btnExportToClipboard;
wxCollapsiblePane* m_importPane;
wxRadioButton* m_rbReplaceAll;
wxRadioButton* m_radioBtn1;
wxButton* m_btnImportFromFile;
wxButton* m_btnImportFromClipboard;
STD_BITMAP_BUTTON* m_addButton;
STD_BITMAP_BUTTON* m_deleteButton;
BITMAP_BUTTON* m_divider1;
@ -68,6 +80,8 @@ class DIALOG_LIB_EDIT_PIN_TABLE_BASE : public DIALOG_SHIM
virtual void OnCellEdited( wxGridEvent& event ) = 0;
virtual void OnCellSelected( wxGridEvent& event ) = 0;
virtual void OnSize( wxSizeEvent& event ) = 0;
virtual void OnExportButtonClick( wxCommandEvent& event ) = 0;
virtual void OnImportButtonClick( wxCommandEvent& event ) = 0;
virtual void OnAddRow( wxCommandEvent& event ) = 0;
virtual void OnDeleteRow( wxCommandEvent& event ) = 0;
virtual void OnRebuildRows( wxCommandEvent& event ) = 0;

View File

@ -0,0 +1 @@
#pragma once

View File

@ -25,8 +25,10 @@
#include <memory>
#include <string>
#include <vector>
class wxImage;
class wxString;
/**
* Store information to the system clipboard.
@ -37,6 +39,16 @@ class wxImage;
*/
bool SaveClipboard( const std::string& aTextUTF8 );
/**
* Store tabular data to the system clipboard.
*/
bool SaveTabularDataToClipboard( const std::vector<std::vector<wxString>>& aData );
/**
* Attempt to get tabular data from the clipboard.
*/
bool GetTabularDataFromClipboard( std::vector<std::vector<wxString>>& aData );
/**
* Return the information currently stored in the system clipboard.
*
@ -52,4 +64,4 @@ std::string GetClipboardUTF8();
*
* If there's a filename there, and it can be loaded as an image, do that.
*/
std::unique_ptr<wxImage> GetImageFromClipboard();
std::unique_ptr<wxImage> GetImageFromClipboard();

View File

@ -358,6 +358,27 @@ inline void AccumulateDescription( wxString& aDesc, const wxString& aItem )
aDesc << aItem;
}
/**
* Build a comma-separated list from a collection of wxStrings.
* (e.g. std::vector, wxArrayString, etc).
*/
template <typename T>
inline void AccumulateDescriptions( wxString& aDesc, const T& aItemCollection )
{
for( const auto& item : aItemCollection )
AccumulateDescription( aDesc, item );
}
template <typename T>
inline wxString AccumulateDescriptions( const T& aItemCollection )
{
wxString desc;
AccumulateDescriptions( desc, aItemCollection );
return desc;
}
/**
* Split \a aString to a string list separated at \a aSplitter.
*

View File

@ -187,6 +187,7 @@ public:
static const std::string TextFileExtension;
static const std::string MarkdownFileExtension;
static const std::string CsvFileExtension;
static const std::string TsvFileExtension;
static const std::string XmlFileExtension;
static const std::string JsonFileExtension;
static const std::string PythonFileExtension;
@ -245,6 +246,7 @@ public:
static wxString PADSNetlistFileWildcard();
static wxString HtmlFileWildcard();
static wxString CsvFileWildcard();
static wxString CsvTsvFileWildcard();
static wxString PcbFileWildcard();
static wxString CadstarArchiveFilesWildcard();
static wxString AltiumProjectFilesWildcard();

View File

@ -93,6 +93,7 @@ static ARC_EDIT_MODE arcEditModeToEnum( int aIndex )
wxFAIL_MSG( wxString::Format( "Invalid index for ARC_EDIT_MODE: %d", aIndex ) );
break;
}
return ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS;
};

View File

@ -120,9 +120,9 @@ std::optional<TOOLBAR_CONFIGURATION> FOOTPRINT_EDIT_TOOLBAR_SETTINGS::DefaultToo
{
std::unique_ptr<ACTION_MENU> arcMenu = std::make_unique<ACTION_MENU>( false, selTool );
arcMenu->Add( PCB_ACTIONS::pointEditorArcKeepCenter, ACTION_MENU::CHECK );
arcMenu->Add( PCB_ACTIONS::pointEditorArcKeepEndpoint, ACTION_MENU::CHECK );
arcMenu->Add( PCB_ACTIONS::pointEditorArcKeepRadius, ACTION_MENU::CHECK );
arcMenu->Add( ACTIONS::pointEditorArcKeepCenter, ACTION_MENU::CHECK );
arcMenu->Add( ACTIONS::pointEditorArcKeepEndpoint, ACTION_MENU::CHECK );
arcMenu->Add( ACTIONS::pointEditorArcKeepRadius, ACTION_MENU::CHECK );
return arcMenu;
};

View File

@ -60,6 +60,7 @@ set( QA_COMMON_SRCS
libeval/test_numeric_evaluator.cpp
io/test_csv.cpp
io/altium/test_altium_parser.cpp
io/altium/test_altium_parser_utils.cpp
io/cadstar/test_cadstar_parts_parser.cpp
@ -117,4 +118,4 @@ endif()
kicad_add_boost_test( qa_common qa_common )
setup_qa_env( qa_common )
setup_qa_env( qa_common )

View File

@ -0,0 +1,160 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <boost/test/unit_test.hpp>
#include <io/csv.h>
#include <wx/sstream.h>
/**
* Define a stream function for logging this type.
*/
template <typename T>
std::ostream& boost_test_print_type( std::ostream& os, const std::vector<std::vector<T>>& aTable )
{
os << "TABLE[ " << std::endl;
for( size_t i = 0; i < aTable.size(); ++i )
{
const auto& row = aTable[i];
os << " Row " << i << " [ ";
for( size_t j = 0; j < row.size(); ++j )
{
os << row[j];
if( j < row.size() - 1 )
os << ", ";
}
os << "] " << std::endl;
}
os << " ]" << std::endl;
return os;
}
static bool TableDataEqual( const std::vector<std::vector<wxString>>& aExpected,
const std::vector<std::vector<wxString>>& aActual )
{
if( aExpected.size() != aActual.size() )
{
BOOST_TEST_MESSAGE( "Row count mismatch: " << aExpected.size() << " != " << aActual.size() );
return false;
}
for( size_t i = 0; i < aExpected.size(); ++i )
{
BOOST_TEST_INFO_SCOPE( "Row " << i );
if( aExpected[i].size() != aActual[i].size() )
return false;
for( size_t j = 0; j < aExpected[i].size(); ++j )
{
if( aExpected[i][j] != aActual[i][j] )
return false;
}
}
return true;
}
BOOST_AUTO_TEST_SUITE( CsvTests )
struct CsvRoundTripCase
{
wxString m_name;
std::vector<std::vector<wxString>> m_rows;
wxString m_expected;
};
BOOST_AUTO_TEST_CASE( BasicRoundTrips )
{
// clang-format off
static const std::vector<CsvRoundTripCase> testCases = {
{
"Basic CSV, Double Quoted, Backslash escaped",
{
{ "Head 1", "Head 2", "Head, \"3\"" },
{ "Row 1 Col 1", "Row 1 Col 2", "Row 1 Col 3" }
},
"\"Head 1\",\"Head 2\",\"Head, \"\"3\"\"\"\n"
"\"Row 1 Col 1\",\"Row 1 Col 2\",\"Row 1 Col 3\"\n",
},
};
// clang-format on
for( const auto& testCase : testCases )
{
BOOST_TEST_INFO_SCOPE( testCase.m_name );
wxStringOutputStream os;
CSV_WRITER writer( os );
writer.WriteLines( testCase.m_rows );
BOOST_CHECK_EQUAL( os.GetString(), testCase.m_expected );
std::vector<std::vector<wxString>> readRows;
bool result = AutoDecodeCSV( os.GetString(), readRows );
BOOST_CHECK( result );
BOOST_CHECK_PREDICATE( TableDataEqual, ( testCase.m_rows )( readRows ) );
}
}
struct CsvDecodeCase
{
wxString m_name;
wxString m_input;
std::vector<std::vector<wxString>> m_expectedRows;
};
BOOST_AUTO_TEST_CASE( BasicDecode )
{
// clang-format off
static const std::vector<CsvDecodeCase> testCases = {
{
"Basic TSV, Double Quoted",
"\"Head 1\"\t\"Head 2\"\t\"Head, 3\"\n"
"\"Row 1 Col 1\"\t\"Row 1 Col 2\"\t\"Row 1 Col 3\"\n",
{
{ "Head 1", "Head 2", "Head, 3" },
{ "Row 1 Col 1", "Row 1 Col 2", "Row 1 Col 3" }
},
}
};
// clang-format on
for( const auto& testCase : testCases )
{
BOOST_TEST_INFO_SCOPE( testCase.m_name );
std::vector<std::vector<wxString>> readRows;
bool result = AutoDecodeCSV( testCase.m_input, readRows );
BOOST_CHECK( result );
BOOST_CHECK_PREDICATE( TableDataEqual, ( testCase.m_expectedRows )( readRows ) );
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -68,5 +68,6 @@ add_subdirectory( picosha2 )
add_subdirectory( json_schema_validator )
add_subdirectory( pegtl )
add_subdirectory( 3dxware_sdk )
add_subdirectory( rapidcsv )
add_subdirectory( turtle )
add_subdirectory( thread-pool )

7
thirdparty/rapidcsv/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,7 @@
add_library( rapidcsv INTERFACE )
target_include_directories( rapidcsv INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )
target_sources( rapidcsv INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/rapidcsv/rapidcsv.h
)

29
thirdparty/rapidcsv/LICENSE.BSD3 vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, Kristofer Berggren
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

6
thirdparty/rapidcsv/README.md vendored Normal file
View File

@ -0,0 +1,6 @@
This directory contains the rapidcsv library, which is a C++ CSV parser and writer.
It is a header-only library, and released under the BSD 3-Clause License, a
copy of which is included in the LICENSE file.
The files come from:
* https://github.com/d99kris/rapidcsv

1943
thirdparty/rapidcsv/rapidcsv/rapidcsv.h vendored Normal file

File diff suppressed because it is too large Load Diff