kicad-source/eeschema/class_library.cpp
Wayne Stambaugh 73bbc35c3e Make the schematic I/O plugin the only option.
Add SCH_PLUGIN object to PART_LIB object.

Convert all PART_LIB I/O to use SCH_PLUGIN.

Remove library caching from PART_LIB and use caching provided by SCH_PLUGIN.

Add support to use PROPERTIES for buffering and document file write control
instead of adding code the SCH_PLUGIN object in the SCH_LEGACY_PLUGIN that
will not be required when the new file formats are implemented.

Add buffering to SCH_LEGACY_PLUGIN to prevent cache from writing file on
every change to library.  This is to prevent the cache library from being
written every time a new symbol is added.

Add option to not save library document file when saving library.  This is
primarily used by the cache library write code.

Move symbol library write code out of LIB_PART and into SCH_LEGACY_PLUGIN.

Add exception handling where LIB_PART caught the exception and returned
an error status.

Remove KICAD_SCH_IO_MANAGER build option as it is no longer optional.
2017-02-10 08:36:57 -05:00

670 lines
17 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
* Copyright (C) 2008-2017 Wayne Stambaugh <stambaughw@verizon.net>
* Copyright (C) 2004-2017 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
*/
/**
* @file class_library.cpp
*/
#include <fctsys.h>
#include <kiface_i.h>
#include <gr_basic.h>
#include <macros.h>
#include <kicad_string.h>
#include <gestfich.h>
#include <eda_doc.h>
#include <wxstruct.h>
#include <richio.h>
#include <config_params.h>
#include <wildcards_and_files_ext.h>
#include <project_rescue.h>
#include <properties.h>
#include <general.h>
#include <class_library.h>
#include <sch_legacy_plugin.h>
#include <wx/progdlg.h>
#include <wx/tokenzr.h>
#include <wx/regex.h>
#define DUPLICATE_NAME_MSG \
_( "Library '%s' has duplicate entry name '%s'.\n" \
"This may cause some unexpected behavior when loading components into a schematic." )
PART_LIB::PART_LIB( int aType, const wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aPluginType ) :
// start @ != 0 so each additional library added
// is immediately detectable, zero would not be.
m_mod_hash( PART_LIBS::s_modify_generation ),
m_pluginType( aPluginType )
{
type = aType;
isModified = false;
timeStamp = 0;
isCache = false;
timeStamp = wxDateTime::Now();
versionMajor = 0; // Will be updated after reading the lib file
versionMinor = 0; // Will be updated after reading the lib file
m_buffering = false;
fileName = aFileName;
if( !fileName.IsOk() )
fileName = "unnamed.lib";
m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
}
PART_LIB::~PART_LIB()
{
}
void PART_LIB::Save( bool aSaveDocFile )
{
wxCHECK_RET( m_plugin != NULL, wxString::Format( "no plugin defined for library `%s`.",
fileName.GetFullPath() ) );
std::unique_ptr< PROPERTIES > props;
if( !aSaveDocFile )
{
props.reset( new PROPERTIES );
(*props.get())[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
}
m_plugin->SaveLibrary( fileName.GetFullPath(), props.get() );
}
void PART_LIB::Create( const wxString& aFileName )
{
std::unique_ptr< PROPERTIES > props;
if( isCache )
{
props.reset( new PROPERTIES );
(*props.get())[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
}
wxString tmpFileName = fileName.GetFullPath();
if( !aFileName.IsEmpty() )
tmpFileName = aFileName;
m_plugin->CreateSymbolLib( tmpFileName, props.get() );
}
void PART_LIB::SetPluginType( SCH_IO_MGR::SCH_FILE_T aPluginType )
{
if( m_pluginType != aPluginType )
{
m_pluginType = aPluginType;
m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
}
}
void PART_LIB::GetAliasNames( wxArrayString& aNames )
{
m_plugin->EnumerateSymbolLib( aNames, fileName.GetFullPath() );
aNames.Sort();
}
void PART_LIB::GetEntryTypePowerNames( wxArrayString& aNames )
{
wxArrayString aliases;
m_plugin->EnumerateSymbolLib( aliases, fileName.GetFullPath() );
for( size_t i = 0; i < aliases.GetCount(); i++ )
{
LIB_ALIAS* alias = m_plugin->LoadSymbol( fileName.GetFullPath(), aliases[i] );
wxCHECK2_MSG( alias != NULL, continue,
wxString::Format( "alias '%s' not found in symbol library '%s'",
aliases[i], fileName.GetFullPath() ) );
LIB_PART* root = alias->GetPart();
if( !root || !root->IsPower() )
continue;
aNames.Add( aliases[i] );
}
aNames.Sort();
}
LIB_ALIAS* PART_LIB::FindAlias( const wxString& aName )
{
return m_plugin->LoadSymbol( fileName.GetFullPath(), aName );
}
LIB_PART* PART_LIB::FindPart( const wxString& aName )
{
LIB_ALIAS* alias = FindAlias( aName );
if( alias != NULL )
return alias->GetPart();
return NULL;
}
bool PART_LIB::HasPowerParts()
{
// return true if at least one power part is found in lib
wxArrayString aliases;
m_plugin->EnumerateSymbolLib( aliases, fileName.GetFullPath() );
for( size_t i = 0; i < aliases.GetCount(); i++ )
{
LIB_ALIAS* alias = m_plugin->LoadSymbol( fileName.GetFullPath(), aliases[i] );
wxCHECK2_MSG( alias != NULL, continue,
wxString::Format( "alias '%s' not found in symbol library '%s'",
aliases[i], fileName.GetFullPath() ) );
LIB_PART* root = alias->GetPart();
if( !root || root->IsPower() )
return true;
}
return false;
}
void PART_LIB::AddPart( LIB_PART* aPart )
{
std::unique_ptr< PROPERTIES > props;
if( isCache || m_buffering )
{
props.reset( new PROPERTIES );
if( isCache )
(*props.get())[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
if( m_buffering )
(*props.get())[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
}
// add a clone, not the caller's copy, the plugin take ownership of the new symbol.
m_plugin->SaveSymbol( fileName.GetFullPath(), new LIB_PART( *aPart, this ), props.get() );
// If we are not buffering, the library file is updated immediately when the plugin
// SaveSymbol() function is called.
if( m_buffering )
isModified = true;
++m_mod_hash;
}
LIB_ALIAS* PART_LIB::RemoveAlias( LIB_ALIAS* aEntry )
{
wxCHECK_MSG( aEntry != NULL, NULL, "NULL pointer cannot be removed from library." );
std::unique_ptr< PROPERTIES > props;
if( isCache || m_buffering )
{
props.reset( new PROPERTIES );
if( isCache )
(*props.get())[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
if( m_buffering )
(*props.get())[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
}
m_plugin->DeleteAlias( fileName.GetFullPath(), aEntry->GetName(), props.get() );
// If we are not buffering, the library file is updated immediately when the plugin
// SaveSymbol() function is called.
if( m_buffering )
isModified = true;
++m_mod_hash;
return NULL;
}
LIB_PART* PART_LIB::ReplacePart( LIB_PART* aOldPart, LIB_PART* aNewPart )
{
wxASSERT( aOldPart != NULL );
wxASSERT( aNewPart != NULL );
std::unique_ptr< PROPERTIES > props;
if( isCache || m_buffering )
{
props.reset( new PROPERTIES );
if( isCache )
(*props.get())[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
if( m_buffering )
(*props.get())[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
}
m_plugin->DeleteSymbol( fileName.GetFullPath(), aOldPart->GetName(), props.get() );
LIB_PART* my_part = new LIB_PART( *aNewPart, this );
m_plugin->SaveSymbol( fileName.GetFullPath(), my_part, props.get() );
// If we are not buffering, the library file is updated immediately when the plugin
// SaveSymbol() function is called.
if( m_buffering )
isModified = true;
++m_mod_hash;
return my_part;
}
PART_LIB* PART_LIB::LoadLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer )
{
std::unique_ptr<PART_LIB> lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA, aFileName ) );
wxArrayString tmp;
// This loads the library although we could probably do lazy loading.
lib->GetCount();
PART_LIB* ret = lib.release();
return ret;
}
PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName ) throw( IO_ERROR, boost::bad_pointer )
{
PART_LIB* lib;
wxFileName fn = aFileName;
// Don't reload the library if it is already loaded.
lib = FindLibrary( fn.GetName() );
if( lib )
return lib;
lib = PART_LIB::LoadLibrary( aFileName );
push_back( lib );
return lib;
}
PART_LIB* PART_LIBS::AddLibrary( const wxString& aFileName, PART_LIBS::iterator& aIterator )
throw( IO_ERROR, boost::bad_pointer )
{
// Don't reload the library if it is already loaded.
wxFileName fn( aFileName );
PART_LIB* lib = FindLibrary( fn.GetName() );
if( lib )
return lib;
lib = PART_LIB::LoadLibrary( aFileName );
if( aIterator >= begin() && aIterator < end() )
insert( aIterator, lib );
else
push_back( lib );
return lib;
}
PART_LIB* PART_LIBS::FindLibrary( const wxString& aName )
{
for( PART_LIBS::iterator it = begin(); it!=end(); ++it )
{
if( it->GetName() == aName )
return &*it;
}
return NULL;
}
wxArrayString PART_LIBS::GetLibraryNames( bool aSorted )
{
wxArrayString cacheNames;
wxArrayString names;
for( PART_LIB& lib : *this )
{
if( lib.IsCache() && aSorted )
cacheNames.Add( lib.GetName() );
else
names.Add( lib.GetName() );
}
// Even sorted, the cache library is always at the end of the list.
if( aSorted )
names.Sort();
for( unsigned int i = 0; i<cacheNames.Count(); i++ )
names.Add( cacheNames.Item( i ) );
return names;
}
LIB_PART* PART_LIBS::FindLibPart( const wxString& aPartName, const wxString& aLibraryName )
{
LIB_PART* part = NULL;
for( PART_LIB& lib : *this )
{
if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
continue;
part = lib.FindPart( aPartName );
if( part )
break;
}
return part;
}
LIB_ALIAS* PART_LIBS::FindLibraryAlias( const wxString& aEntryName, const wxString& aLibraryName )
{
LIB_ALIAS* entry = NULL;
for( PART_LIB& lib : *this )
{
if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
continue;
entry = lib.FindAlias( aEntryName );
if( entry )
break;
}
return entry;
}
/* searches all libraries in the list for an entry, using a case insensitive comparison.
* Used to find an entry, when the normal (case sensitive) search fails.
*/
void PART_LIBS::FindLibraryNearEntries( std::vector<LIB_ALIAS*>& aCandidates,
const wxString& aEntryName,
const wxString& aLibraryName )
{
for( PART_LIB& lib : *this )
{
if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
continue;
wxArrayString aliasNames;
lib.GetAliasNames( aliasNames );
if( aliasNames.IsEmpty() )
continue;
for( size_t i = 0; i < aliasNames.size(); i++ )
{
if( aliasNames[i].CmpNoCase( aEntryName ) == 0 )
aCandidates.push_back( lib.FindAlias( aliasNames[i] ) );
}
}
}
int PART_LIBS::s_modify_generation = 1; // starts at 1 and goes up
int PART_LIBS::GetModifyHash()
{
int hash = 0;
for( PART_LIBS::const_iterator it = begin(); it != end(); ++it )
{
hash += it->GetModHash();
}
return hash;
}
void PART_LIBS::LibNamesAndPaths( PROJECT* aProject, bool doSave,
wxString* aPaths, wxArrayString* aNames )
throw( IO_ERROR, boost::bad_pointer )
{
wxString pro = aProject->GetProjectFullName();
PARAM_CFG_ARRAY ca;
if( aPaths )
ca.push_back( new PARAM_CFG_FILENAME( "LibDir", aPaths ) );
if( aNames )
ca.push_back( new PARAM_CFG_LIBNAME_LIST( wxT( "LibName" ), aNames, GROUP_SCH_LIBS ) );
if( doSave )
{
aProject->ConfigSave( Kiface().KifaceSearch(), GROUP_SCH, ca );
/*
{
wxString msg = wxString::Format( _(
"Unable save project's '%s' file" ),
GetChars( pro )
);
THROW_IO_ERROR( msg );
}
*/
}
else
{
if( !aProject->ConfigLoad( Kiface().KifaceSearch(), GROUP_SCH, ca ) )
{
wxString msg = wxString::Format( _(
"Unable to load project's '%s' file" ),
GetChars( pro )
);
THROW_IO_ERROR( msg );
}
}
}
const wxString PART_LIBS::CacheName( const wxString& aFullProjectFilename )
{
/* until apr 2009 the project cache lib was named: <root_name>.cache.lib,
* and after: <root_name>-cache.lib. So if the <name>-cache.lib is not found,
* the old file will be renamed and returned.
*/
wxFileName new_name = aFullProjectFilename;
new_name.SetName( new_name.GetName() + "-cache" );
new_name.SetExt( SchematicLibraryFileExtension );
if( new_name.FileExists() )
return new_name.GetFullPath();
else
{
wxFileName old_name = aFullProjectFilename;
old_name.SetExt( "cache.lib" );
if( old_name.FileExists() )
{
wxRenameFile( old_name.GetFullPath(), new_name.GetFullPath() );
return new_name.GetFullPath();
}
}
return wxEmptyString;
}
void PART_LIBS::LoadAllLibraries( PROJECT* aProject, bool aShowProgress ) throw( IO_ERROR, boost::bad_pointer )
{
wxString filename;
wxString libs_not_found;
SEARCH_STACK* lib_search = aProject->SchSearchS();
#if defined(DEBUG) && 0
lib_search->Show( __func__ );
#endif
wxArrayString lib_names;
LibNamesAndPaths( aProject, false, NULL, &lib_names );
// If the list is empty, force loading the standard power symbol library.
if( !lib_names.GetCount() )
lib_names.Add( "power" );
wxASSERT( !size() ); // expect to load into "this" empty container.
wxProgressDialog lib_dialog( _( "Loading symbol libraries" ),
wxEmptyString,
lib_names.GetCount(),
NULL,
wxPD_APP_MODAL );
if( aShowProgress )
{
lib_dialog.Show();
}
wxString progress_message;
for( unsigned i = 0; i < lib_names.GetCount(); ++i )
{
if( aShowProgress )
{
lib_dialog.Update( i, _( "Loading " + lib_names[i] ) );
}
wxFileName fn = lib_names[i];
// lib_names[] does not store the file extension. Set it:
fn.SetExt( SchematicLibraryFileExtension );
// Skip if the file name is not valid..
if( !fn.IsOk() )
continue;
if( !fn.FileExists() )
{
filename = lib_search->FindValidPath( fn.GetFullPath() );
if( !filename )
{
libs_not_found += fn.GetFullPath();
libs_not_found += '\n';
continue;
}
}
else
{ // ensure the lib filename has a absolute path.
// If the lib has no absolute path, and is found in the cwd by fn.FileExists(),
// make a full absolute path, to avoid issues with load library functions which
// expects an absolute path.
if( !fn.IsAbsolute() )
fn.MakeAbsolute();
filename = fn.GetFullPath();
}
try
{
AddLibrary( filename );
}
catch( const IO_ERROR& ioe )
{
wxString msg;
msg.Printf( _( "Part library '%s' failed to load. Error:\n %s" ),
GetChars( filename ), GetChars( ioe.What() ) );
wxLogError( msg );
}
}
if( aShowProgress )
{
lib_dialog.Destroy();
}
// add the special cache library.
wxString cache_name = CacheName( aProject->GetProjectFullName() );
PART_LIB* cache_lib;
if( !cache_name.IsEmpty() )
{
try
{
cache_lib = AddLibrary( cache_name );
if( cache_lib )
cache_lib->SetCache();
}
catch( const IO_ERROR& ioe )
{
wxString msg = wxString::Format( _(
"Part library '%s' failed to load.\nError: %s" ),
GetChars( cache_name ),
GetChars( ioe.What() )
);
THROW_IO_ERROR( msg );
}
}
// Print the libraries not found
if( !libs_not_found.IsEmpty() )
{
// Use a different exception type so catch()er can route to proper use
// of the HTML_MESSAGE_BOX.
THROW_PARSE_ERROR( wxEmptyString, UTF8( __func__ ), UTF8( libs_not_found ), 0, 0 );
}
#if defined(DEBUG) && 0
printf( "%s: lib_names:\n", __func__ );
for( PART_LIBS::const_iterator it = begin(); it < end(); ++it )
printf( " %s\n", TO_UTF8( it->GetName() ) );
#endif
}