From cfaca0e4bbb5b56ead94066189992c84065dd36b Mon Sep 17 00:00:00 2001 From: Wayne Stambaugh Date: Mon, 22 Mar 2021 08:19:47 -0400 Subject: [PATCH] Revert "Eeschema: remove all legacy schematic and symbol library save code." This reverts commit 9c19c2306ec1abd7bbe409970d389145182179d8. Unfortunately writing legacy symbols libraries can never be removed because of the symbol rescue feature. :( Fixes https://gitlab.com/kicad/code/kicad/issues/7986 --- .../sch_plugins/legacy/sch_legacy_plugin.cpp | 1259 ++++++++++++++++- .../sch_plugins/legacy/sch_legacy_plugin.h | 29 +- 2 files changed, 1281 insertions(+), 7 deletions(-) diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp index e87dfcf3d1..fe3bbeb41c 100644 --- a/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp +++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -66,6 +67,7 @@ #include #include // for MAX_UNIT_COUNT_PER_PACKAGE definition #include // for PropPowerSymsOnly definintion. +#include #include #include // For some default values @@ -483,7 +485,7 @@ class SCH_LEGACY_PLUGIN_CACHE bool m_isModified; int m_versionMajor; int m_versionMinor; - SCH_LIB_TYPE m_libType; // Is this cache a component or symbol library. + SCH_LIB_TYPE m_libType; // Is this cache a component or symbol library. void loadHeader( FILE_LINE_READER& aReader ); static void loadAliases( std::unique_ptr& aPart, LINE_READER& aReader, @@ -504,8 +506,19 @@ class SCH_LEGACY_PLUGIN_CACHE static LIB_POLYLINE* loadPolyLine( std::unique_ptr& aPart, LINE_READER& aReader ); static LIB_BEZIER* loadBezier( std::unique_ptr& aPart, LINE_READER& aReader ); - static FILL_TYPE parseFillMode( LINE_READER& aReader, const char* aLine, - const char** aOutput ); + static FILL_TYPE parseFillMode( LINE_READER& aReader, const char* aLine, + const char** aOutput ); + LIB_PART* removeSymbol( LIB_PART* aAlias ); + + void saveDocFile(); + static void saveArc( LIB_ARC* aArc, OUTPUTFORMATTER& aFormatter ); + static void saveBezier( LIB_BEZIER* aBezier, OUTPUTFORMATTER& aFormatter ); + static void saveCircle( LIB_CIRCLE* aCircle, OUTPUTFORMATTER& aFormatter ); + static void saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter ); + static void savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter ); + static void savePolyLine( LIB_POLYLINE* aPolyLine, OUTPUTFORMATTER& aFormatter ); + static void saveRectangle( LIB_RECTANGLE* aRectangle, OUTPUTFORMATTER& aFormatter ); + static void saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter ); friend SCH_LEGACY_PLUGIN; @@ -519,8 +532,15 @@ public: // error codes nor user interface calls from here, nor in any SCH_PLUGIN objects. // Catch these exceptions higher up please. + /// Save the entire library to file m_libFileName; + void Save( bool aSaveDocFile = true ); + void Load(); + void AddSymbol( const LIB_PART* aPart ); + + void DeleteSymbol( const wxString& aName ); + // If m_libFileName is a symlink follow it to the real source file wxFileName GetRealFile() const; @@ -540,6 +560,8 @@ public: static LIB_PART* LoadPart( LINE_READER& aReader, int aMajorVersion, int aMinorVersion, LIB_PART_MAP* aMap = nullptr ); + static void SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + LIB_PART_MAP* aMap = nullptr ); }; @@ -1798,6 +1820,581 @@ std::shared_ptr SCH_LEGACY_PLUGIN::loadBusAlias( LINE_READER& aReader } +void SCH_LEGACY_PLUGIN::Save( const wxString& aFileName, SCH_SHEET* aSheet, SCHEMATIC* aSchematic, + const PROPERTIES* aProperties ) +{ + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET object." ); + wxCHECK_RET( !aFileName.IsEmpty(), "No schematic file name defined." ); + + LOCALE_IO toggle; // toggles on, then off, the C locale, to write floating point values. + + init( aSchematic, aProperties ); + + wxFileName fn = aFileName; + + // File names should be absolute. Don't assume everything relative to the project path + // works properly. + wxASSERT( fn.IsAbsolute() ); + + FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() ); + + m_out = &formatter; // no ownership + + Format( aSheet ); +} + + +void SCH_LEGACY_PLUGIN::Format( SCH_SHEET* aSheet ) +{ + wxCHECK_RET( aSheet != NULL, "NULL SCH_SHEET* object." ); + wxCHECK_RET( m_schematic != NULL, "NULL SCHEMATIC* object." ); + + SCH_SCREEN* screen = aSheet->GetScreen(); + + wxCHECK( screen, /* void */ ); + + // Write the header + m_out->Print( 0, "%s %s %d\n", "EESchema", SCHEMATIC_HEAD_STRING, EESCHEMA_VERSION ); + + // This section is not used, but written for file compatibility + m_out->Print( 0, "EELAYER %d %d\n", SCH_LAYER_ID_COUNT, 0 ); + m_out->Print( 0, "EELAYER END\n" ); + + /* Write page info, ScreenNumber and NumberOfScreen; not very meaningful for + * SheetNumber and Sheet Count in a complex hierarchy, but useful in + * simple hierarchy and flat hierarchy. Used also to search the root + * sheet ( ScreenNumber = 1 ) within the files + */ + const TITLE_BLOCK& tb = screen->GetTitleBlock(); + const PAGE_INFO& page = screen->GetPageSettings(); + + m_out->Print( 0, "$Descr %s %d %d%s\n", TO_UTF8( page.GetType() ), + page.GetWidthMils(), + page.GetHeightMils(), + !page.IsCustom() && page.IsPortrait() ? " portrait" : "" ); + m_out->Print( 0, "encoding utf-8\n" ); + m_out->Print( 0, "Sheet %d %d\n", screen->GetVirtualPageNumber(), screen->GetPageCount() ); + m_out->Print( 0, "Title %s\n", EscapedUTF8( tb.GetTitle() ).c_str() ); + m_out->Print( 0, "Date %s\n", EscapedUTF8( tb.GetDate() ).c_str() ); + m_out->Print( 0, "Rev %s\n", EscapedUTF8( tb.GetRevision() ).c_str() ); + m_out->Print( 0, "Comp %s\n", EscapedUTF8( tb.GetCompany() ).c_str() ); + m_out->Print( 0, "Comment1 %s\n", EscapedUTF8( tb.GetComment( 0 ) ).c_str() ); + m_out->Print( 0, "Comment2 %s\n", EscapedUTF8( tb.GetComment( 1 ) ).c_str() ); + m_out->Print( 0, "Comment3 %s\n", EscapedUTF8( tb.GetComment( 2 ) ).c_str() ); + m_out->Print( 0, "Comment4 %s\n", EscapedUTF8( tb.GetComment( 3 ) ).c_str() ); + m_out->Print( 0, "Comment5 %s\n", EscapedUTF8( tb.GetComment( 4 ) ).c_str() ); + m_out->Print( 0, "Comment6 %s\n", EscapedUTF8( tb.GetComment( 5 ) ).c_str() ); + m_out->Print( 0, "Comment7 %s\n", EscapedUTF8( tb.GetComment( 6 ) ).c_str() ); + m_out->Print( 0, "Comment8 %s\n", EscapedUTF8( tb.GetComment( 7 ) ).c_str() ); + m_out->Print( 0, "Comment9 %s\n", EscapedUTF8( tb.GetComment( 8 ) ).c_str() ); + m_out->Print( 0, "$EndDescr\n" ); + + for( const auto& alias : screen->GetBusAliases() ) + { + saveBusAlias( alias ); + } + + // Enforce item ordering + auto cmp = []( const SCH_ITEM* a, const SCH_ITEM* b ) { return *a < *b; }; + std::multiset save_map( cmp ); + + for( auto item : screen->Items() ) + save_map.insert( item ); + + + for( auto& item : save_map ) + { + switch( item->Type() ) + { + case SCH_COMPONENT_T: + saveComponent( static_cast( item ) ); + break; + case SCH_BITMAP_T: + saveBitmap( static_cast( item ) ); + break; + case SCH_SHEET_T: + saveSheet( static_cast( item ) ); + break; + case SCH_JUNCTION_T: + saveJunction( static_cast( item ) ); + break; + case SCH_NO_CONNECT_T: + saveNoConnect( static_cast( item ) ); + break; + case SCH_BUS_WIRE_ENTRY_T: + case SCH_BUS_BUS_ENTRY_T: + saveBusEntry( static_cast( item ) ); + break; + case SCH_LINE_T: + saveLine( static_cast( item ) ); + break; + case SCH_TEXT_T: + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIER_LABEL_T: + saveText( static_cast( item ) ); + break; + default: + wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" ); + } + } + + m_out->Print( 0, "$EndSCHEMATC\n" ); +} + + +void SCH_LEGACY_PLUGIN::Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ) +{ + m_out = aFormatter; + + for( unsigned i = 0; i < aSelection->GetSize(); ++i ) + { + SCH_ITEM* item = (SCH_ITEM*) aSelection->GetItem( i ); + + switch( item->Type() ) + { + case SCH_COMPONENT_T: + saveComponent( static_cast< SCH_COMPONENT* >( item ) ); + break; + case SCH_BITMAP_T: + saveBitmap( static_cast< SCH_BITMAP* >( item ) ); + break; + case SCH_SHEET_T: + saveSheet( static_cast< SCH_SHEET* >( item ) ); + break; + case SCH_JUNCTION_T: + saveJunction( static_cast< SCH_JUNCTION* >( item ) ); + break; + case SCH_NO_CONNECT_T: + saveNoConnect( static_cast< SCH_NO_CONNECT* >( item ) ); + break; + case SCH_BUS_WIRE_ENTRY_T: + case SCH_BUS_BUS_ENTRY_T: + saveBusEntry( static_cast< SCH_BUS_ENTRY_BASE* >( item ) ); + break; + case SCH_LINE_T: + saveLine( static_cast< SCH_LINE* >( item ) ); + break; + case SCH_TEXT_T: + case SCH_LABEL_T: + case SCH_GLOBAL_LABEL_T: + case SCH_HIER_LABEL_T: + saveText( static_cast< SCH_TEXT* >( item ) ); + break; + default: + wxASSERT( "Unexpected schematic object type in SCH_LEGACY_PLUGIN::Format()" ); + } + } +} + + +void SCH_LEGACY_PLUGIN::saveComponent( SCH_COMPONENT* aComponent ) +{ + std::string name1; + std::string name2; + + static wxString delimiters( wxT( " " ) ); + + // This is redundant with the AR entries below, but it makes the files backwards-compatible. + if( aComponent->GetInstanceReferences().size() > 0 ) + { + const SYMBOL_INSTANCE_REFERENCE& instance = aComponent->GetInstanceReferences()[0]; + name1 = toUTFTildaText( instance.m_Reference ); + } + else + { + if( aComponent->GetField( REFERENCE_FIELD )->GetText().IsEmpty() ) + name1 = toUTFTildaText( aComponent->GetPrefix() ); + else + name1 = toUTFTildaText( aComponent->GetField( REFERENCE_FIELD )->GetText() ); + } + + wxString part_name = aComponent->GetLibId().Format(); + + if( part_name.size() ) + { + name2 = toUTFTildaText( part_name ); + } + else + { + name2 = "_NONAME_"; + } + + m_out->Print( 0, "$Comp\n" ); + m_out->Print( 0, "L %s %s\n", name2.c_str(), name1.c_str() ); + + // Generate unit number, conversion and timestamp + m_out->Print( 0, "U %d %d %8.8X\n", + aComponent->GetUnit(), + aComponent->GetConvert(), + aComponent->m_Uuid.AsLegacyTimestamp() ); + + // Save the position + m_out->Print( 0, "P %d %d\n", + Iu2Mils( aComponent->GetPosition().x ), + Iu2Mils( aComponent->GetPosition().y ) ); + + /* If this is a complex hierarchy; save hierarchical references. + * but for simple hierarchies it is not necessary. + * the reference inf is already saved + * this is useful for old Eeschema version compatibility + */ + if( aComponent->GetInstanceReferences().size() > 1 ) + { + for( const SYMBOL_INSTANCE_REFERENCE& instance : aComponent->GetInstanceReferences() ) + { + /*format: + * AR Path="/140/2" Ref="C99" Part="1" + * where 140 is the uid of the containing sheet + * and 2 is the timestamp of this component. + * (timestamps are actually 8 hex chars) + * Ref is the conventional component reference for this 'path' + * Part is the conventional component part selection for this 'path' + */ + wxString path = "/"; + + // Skip root sheet + for( int i = 1; i < (int) instance.m_Path.size(); ++i ) + path += instance.m_Path[i].AsLegacyTimestampString() + "/"; + + m_out->Print( 0, "AR Path=\"%s\" Ref=\"%s\" Part=\"%d\" \n", + TO_UTF8( path + aComponent->m_Uuid.AsLegacyTimestampString() ), + TO_UTF8( instance.m_Reference ), + instance.m_Unit ); + } + } + + // update the ugly field id, which I would like to see go away someday soon. + for( int i = 0; i < aComponent->GetFieldCount(); ++i ) + aComponent->GetFields()[i].SetId( i ); + + // Fixed fields: + // Save mandatory fields even if they are blank, + // because the visibility, size and orientation are set from library editor. + for( unsigned i = 0; i < MANDATORY_FIELDS; ++i ) + saveField( &aComponent->GetFields()[i] ); + + // User defined fields: + // The *policy* about which user defined fields are part of a symbol is now + // only in the dialog editors. No policy should be enforced here, simply + // save all the user defined fields, they are present because a dialog editor + // thought they should be. If you disagree, go fix the dialog editors. + for( int i = MANDATORY_FIELDS; i < aComponent->GetFieldCount(); ++i ) + saveField( &aComponent->GetFields()[i] ); + + // Unit number, position, box ( old standard ) + m_out->Print( 0, "\t%-4d %-4d %-4d\n", aComponent->GetUnit(), + Iu2Mils( aComponent->GetPosition().x ), + Iu2Mils( aComponent->GetPosition().y ) ); + + TRANSFORM transform = aComponent->GetTransform(); + + m_out->Print( 0, "\t%-4d %-4d %-4d %-4d\n", + transform.x1, transform.y1, transform.x2, transform.y2 ); + m_out->Print( 0, "$EndComp\n" ); +} + + +void SCH_LEGACY_PLUGIN::saveField( SCH_FIELD* aField ) +{ + char hjustify = 'C'; + + if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT ) + hjustify = 'L'; + else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT ) + hjustify = 'R'; + + char vjustify = 'C'; + + if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM ) + vjustify = 'B'; + else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP ) + vjustify = 'T'; + + m_out->Print( 0, "F %d %s %c %-3d %-3d %-3d %4.4X %c %c%c%c", + aField->GetId(), + EscapedUTF8( aField->GetText() ).c_str(), // wraps in quotes too + aField->GetTextAngle() == TEXT_ANGLE_HORIZ ? 'H' : 'V', + Iu2Mils( aField->GetLibPosition().x ), + Iu2Mils( aField->GetLibPosition().y ), + Iu2Mils( aField->GetTextWidth() ), + !aField->IsVisible(), + hjustify, vjustify, + aField->IsItalic() ? 'I' : 'N', + aField->IsBold() ? 'B' : 'N' ); + + // Save field name, if the name is user definable + if( aField->GetId() >= MANDATORY_FIELDS ) + m_out->Print( 0, " %s", EscapedUTF8( aField->GetName() ).c_str() ); + + m_out->Print( 0, "\n" ); +} + + +void SCH_LEGACY_PLUGIN::saveBitmap( SCH_BITMAP* aBitmap ) +{ + wxCHECK_RET( aBitmap != NULL, "SCH_BITMAP* is NULL" ); + + const wxImage* image = aBitmap->GetImage()->GetImageData(); + + wxCHECK_RET( image != NULL, "wxImage* is NULL" ); + + m_out->Print( 0, "$Bitmap\n" ); + m_out->Print( 0, "Pos %-4d %-4d\n", + Iu2Mils( aBitmap->GetPosition().x ), + Iu2Mils( aBitmap->GetPosition().y ) ); + m_out->Print( 0, "Scale %f\n", aBitmap->GetImage()->GetScale() ); + m_out->Print( 0, "Data\n" ); + + wxMemoryOutputStream stream; + + image->SaveFile( stream, wxBITMAP_TYPE_PNG ); + + // Write binary data in hexadecimal form (ASCII) + wxStreamBuffer* buffer = stream.GetOutputStreamBuffer(); + char* begin = (char*) buffer->GetBufferStart(); + + for( int ii = 0; begin < buffer->GetBufferEnd(); begin++, ii++ ) + { + if( ii >= 32 ) + { + ii = 0; + + m_out->Print( 0, "\n" ); + } + + m_out->Print( 0, "%2.2X ", *begin & 0xFF ); + } + + m_out->Print( 0, "\nEndData\n" ); + m_out->Print( 0, "$EndBitmap\n" ); +} + + +void SCH_LEGACY_PLUGIN::saveSheet( SCH_SHEET* aSheet ) +{ + wxCHECK_RET( aSheet != NULL, "SCH_SHEET* is NULL" ); + + m_out->Print( 0, "$Sheet\n" ); + m_out->Print( 0, "S %-4d %-4d %-4d %-4d\n", + Iu2Mils( aSheet->GetPosition().x ), + Iu2Mils( aSheet->GetPosition().y ), + Iu2Mils( aSheet->GetSize().x ), + Iu2Mils( aSheet->GetSize().y ) ); + + m_out->Print( 0, "U %8.8X\n", aSheet->m_Uuid.AsLegacyTimestamp() ); + + SCH_FIELD& sheetName = aSheet->GetFields()[SHEETNAME]; + SCH_FIELD& fileName = aSheet->GetFields()[SHEETFILENAME]; + + if( !sheetName.GetText().IsEmpty() ) + m_out->Print( 0, "F0 %s %d\n", + EscapedUTF8( sheetName.GetText() ).c_str(), + Iu2Mils( sheetName.GetTextSize().x ) ); + + if( !fileName.GetText().IsEmpty() ) + m_out->Print( 0, "F1 %s %d\n", + EscapedUTF8( fileName.GetText() ).c_str(), + Iu2Mils( fileName.GetTextSize().x ) ); + + for( const SCH_SHEET_PIN* pin : aSheet->GetPins() ) + { + int type, side; + + if( pin->GetText().IsEmpty() ) + break; + + switch( pin->GetEdge() ) + { + default: + case SHEET_LEFT_SIDE: side = 'L'; break; + case SHEET_RIGHT_SIDE: side = 'R'; break; + case SHEET_TOP_SIDE: side = 'T'; break; + case SHEET_BOTTOM_SIDE: side = 'B'; break; + } + + switch( pin->GetShape() ) + { + default: + case PINSHEETLABEL_SHAPE::PS_UNSPECIFIED: type = 'U'; break; + case PINSHEETLABEL_SHAPE::PS_INPUT: type = 'I'; break; + case PINSHEETLABEL_SHAPE::PS_OUTPUT: type = 'O'; break; + case PINSHEETLABEL_SHAPE::PS_BIDI: type = 'B'; break; + case PINSHEETLABEL_SHAPE::PS_TRISTATE: type = 'T'; break; + } + + m_out->Print( 0, "F%d %s %c %c %-3d %-3d %-3d\n", + pin->GetNumber(), + EscapedUTF8( pin->GetText() ).c_str(), // supplies wrapping quotes + type, side, Iu2Mils( pin->GetPosition().x ), + Iu2Mils( pin->GetPosition().y ), + Iu2Mils( pin->GetTextWidth() ) ); + } + + m_out->Print( 0, "$EndSheet\n" ); +} + + +void SCH_LEGACY_PLUGIN::saveJunction( SCH_JUNCTION* aJunction ) +{ + wxCHECK_RET( aJunction != NULL, "SCH_JUNCTION* is NULL" ); + + m_out->Print( 0, "Connection ~ %-4d %-4d\n", + Iu2Mils( aJunction->GetPosition().x ), + Iu2Mils( aJunction->GetPosition().y ) ); +} + + +void SCH_LEGACY_PLUGIN::saveNoConnect( SCH_NO_CONNECT* aNoConnect ) +{ + wxCHECK_RET( aNoConnect != NULL, "SCH_NOCONNECT* is NULL" ); + + m_out->Print( 0, "NoConn ~ %-4d %-4d\n", + Iu2Mils( aNoConnect->GetPosition().x ), + Iu2Mils( aNoConnect->GetPosition().y ) ); +} + + +void SCH_LEGACY_PLUGIN::saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry ) +{ + wxCHECK_RET( aBusEntry != NULL, "SCH_BUS_ENTRY_BASE* is NULL" ); + + if( aBusEntry->GetLayer() == LAYER_WIRE ) + m_out->Print( 0, "Entry Wire Line\n\t%-4d %-4d %-4d %-4d\n", + Iu2Mils( aBusEntry->GetPosition().x ), + Iu2Mils( aBusEntry->GetPosition().y ), + Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) ); + else + m_out->Print( 0, "Entry Bus Bus\n\t%-4d %-4d %-4d %-4d\n", + Iu2Mils( aBusEntry->GetPosition().x ), + Iu2Mils( aBusEntry->GetPosition().y ), + Iu2Mils( aBusEntry->GetEnd().x ), Iu2Mils( aBusEntry->GetEnd().y ) ); +} + + +void SCH_LEGACY_PLUGIN::saveLine( SCH_LINE* aLine ) +{ + wxCHECK_RET( aLine != NULL, "SCH_LINE* is NULL" ); + + const char* layer = "Notes"; + const char* width = "Line"; + + if( aLine->GetLayer() == LAYER_WIRE ) + layer = "Wire"; + else if( aLine->GetLayer() == LAYER_BUS ) + layer = "Bus"; + + m_out->Print( 0, "Wire %s %s", layer, width ); + + // Write line style (width, type, color) only for non default values + if( aLine->IsGraphicLine() ) + { + if( aLine->GetLineSize() != 0 ) + m_out->Print( 0, " %s %d", T_WIDTH, Iu2Mils( aLine->GetLineSize() ) ); + + if( aLine->GetLineStyle() != aLine->GetDefaultStyle() ) + m_out->Print( 0, " %s %s", T_STYLE, + SCH_LINE::GetLineStyleName( aLine->GetLineStyle() ) ); + + if( aLine->GetLineColor() != COLOR4D::UNSPECIFIED ) + m_out->Print( 0, " %s", + TO_UTF8( aLine->GetLineColor().ToColour().GetAsString( wxC2S_CSS_SYNTAX ) ) ); + } + + m_out->Print( 0, "\n" ); + + m_out->Print( 0, "\t%-4d %-4d %-4d %-4d", + Iu2Mils( aLine->GetStartPoint().x ), Iu2Mils( aLine->GetStartPoint().y ), + Iu2Mils( aLine->GetEndPoint().x ), Iu2Mils( aLine->GetEndPoint().y ) ); + + m_out->Print( 0, "\n"); +} + + +void SCH_LEGACY_PLUGIN::saveText( SCH_TEXT* aText ) +{ + wxCHECK_RET( aText != NULL, "SCH_TEXT* is NULL" ); + + const char* italics = "~"; + const char* textType = "Notes"; + + if( aText->IsItalic() ) + italics = "Italic"; + + wxString text = aText->GetText(); + + SCH_LAYER_ID layer = aText->GetLayer(); + + if( layer == LAYER_NOTES || layer == LAYER_LOCLABEL ) + { + if( layer == LAYER_NOTES ) + { + // For compatibility reasons, the text must be saved in only one text line + // so replace all EOLs with \\n + text.Replace( wxT( "\n" ), wxT( "\\n" ) ); + + // Here we should have no CR or LF character in line + // This is not always the case if a multiline text was copied (using a copy/paste + // function) from a text that uses E.O.L characters that differs from the current + // EOL format. This is mainly the case under Linux using LF symbol when copying + // a text from Windows (using CRLF symbol) so we must just remove the extra CR left + // (or LF left under MacOSX) + for( unsigned ii = 0; ii < text.Len(); ) + { + if( text[ii] == 0x0A || text[ii] == 0x0D ) + text.erase( ii, 1 ); + else + ii++; + } + } + else + { + textType = "Label"; + } + + // Local labels must have their spin style inverted for left and right + int spinStyle = static_cast( aText->GetLabelSpinStyle() ); + + if( spinStyle == 0 ) + spinStyle = 2; + else if( spinStyle == 2 ) + spinStyle = 0; + + m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %d\n%s\n", textType, + Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ), + spinStyle, + Iu2Mils( aText->GetTextWidth() ), + italics, Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) ); + } + else if( layer == LAYER_GLOBLABEL || layer == LAYER_HIERLABEL ) + { + textType = ( layer == LAYER_GLOBLABEL ) ? "GLabel" : "HLabel"; + + auto shapeLabelIt = sheetLabelNames.find( aText->GetShape() ); + wxCHECK_RET( shapeLabelIt != sheetLabelNames.end(), "Shape not found in names list" ); + + m_out->Print( 0, "Text %s %-4d %-4d %-4d %-4d %s %s %d\n%s\n", textType, + Iu2Mils( aText->GetPosition().x ), Iu2Mils( aText->GetPosition().y ), + static_cast( aText->GetLabelSpinStyle() ), + Iu2Mils( aText->GetTextWidth() ), + shapeLabelIt->second, + italics, + Iu2Mils( aText->GetTextThickness() ), TO_UTF8( text ) ); + } +} + + +void SCH_LEGACY_PLUGIN::saveBusAlias( std::shared_ptr aAlias ) +{ + wxCHECK_RET( aAlias != NULL, "BUS_ALIAS* is NULL" ); + + wxString members = boost::algorithm::join( aAlias->Members(), " " ); + + m_out->Print( 0, "BusAlias %s %s\n", + TO_UTF8( aAlias->GetName() ), TO_UTF8( members ) ); +} + + int SCH_LEGACY_PLUGIN_CACHE::m_modHash = 1; // starts at 1 and goes up @@ -1876,6 +2473,88 @@ bool SCH_LEGACY_PLUGIN_CACHE::IsFileChanged() const } +LIB_PART* SCH_LEGACY_PLUGIN_CACHE::removeSymbol( LIB_PART* aPart ) +{ + wxCHECK_MSG( aPart != NULL, NULL, "NULL pointer cannot be removed from library." ); + + LIB_PART* firstChild = NULL; + LIB_PART_MAP::iterator it = m_symbols.find( aPart->GetName() ); + + if( it == m_symbols.end() ) + return NULL; + + // If the entry pointer doesn't match the name it is mapped to in the library, we + // have done something terribly wrong. + wxCHECK_MSG( *it->second == aPart, NULL, + "Pointer mismatch while attempting to remove alias entry <" + aPart->GetName() + + "> from library cache <" + m_libFileName.GetName() + ">." ); + + // If the symbol is a root symbol used by other symbols find the first alias that uses + // the root part and make it the new root. + if( aPart->IsRoot() ) + { + for( auto entry : m_symbols ) + { + if( entry.second->IsAlias() + && entry.second->GetParent().lock() == aPart->SharedPtr() ) + { + firstChild = entry.second; + break; + } + } + + if( firstChild ) + { + for( LIB_ITEM& drawItem : aPart->GetDrawItems() ) + { + if( drawItem.Type() == LIB_FIELD_T ) + { + LIB_FIELD& field = static_cast( drawItem ); + + if( firstChild->FindField( field.GetCanonicalName() ) ) + continue; + } + + LIB_ITEM* newItem = (LIB_ITEM*) drawItem.Clone(); + drawItem.SetParent( firstChild ); + firstChild->AddDrawItem( newItem ); + } + + // Reparent the remaining aliases. + for( auto entry : m_symbols ) + { + if( entry.second->IsAlias() + && entry.second->GetParent().lock() == aPart->SharedPtr() ) + entry.second->SetParent( firstChild ); + } + } + } + + m_symbols.erase( it ); + delete aPart; + m_isModified = true; + ++m_modHash; + return firstChild; +} + + +void SCH_LEGACY_PLUGIN_CACHE::AddSymbol( const LIB_PART* aPart ) +{ + // aPart is cloned in PART_LIB::AddPart(). The cache takes ownership of aPart. + wxString name = aPart->GetName(); + LIB_PART_MAP::iterator it = m_symbols.find( name ); + + if( it != m_symbols.end() ) + { + removeSymbol( it->second ); + } + + m_symbols[ name ] = const_cast< LIB_PART* >( aPart ); + m_isModified = true; + ++m_modHash; +} + + void SCH_LEGACY_PLUGIN_CACHE::Load() { if( !m_libFileName.FileExists() ) @@ -3040,6 +3719,491 @@ void SCH_LEGACY_PLUGIN_CACHE::loadFootprintFilters( std::unique_ptr& a } +void SCH_LEGACY_PLUGIN_CACHE::Save( bool aSaveDocFile ) +{ + if( !m_isModified ) + return; + + // Write through symlinks, don't replace them + wxFileName fn = GetRealFile(); + + auto formatter = std::make_unique( fn.GetFullPath() ); + formatter->Print( 0, "%s %d.%d\n", LIBFILE_IDENT, LIB_VERSION_MAJOR, LIB_VERSION_MINOR ); + formatter->Print( 0, "#encoding utf-8\n"); + + for( LIB_PART_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); it++ ) + { + if( !it->second->IsRoot() ) + continue; + + SaveSymbol( it->second, *formatter.get(), &m_symbols ); + } + + formatter->Print( 0, "#\n#End Library\n" ); + formatter.reset(); + + m_fileModTime = fn.GetModificationTime(); + m_isModified = false; + + if( aSaveDocFile ) + saveDocFile(); +} + + +void SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( LIB_PART* aSymbol, OUTPUTFORMATTER& aFormatter, + LIB_PART_MAP* aMap ) +{ + wxCHECK_RET( aSymbol && aSymbol->IsRoot(), "Invalid LIB_PART pointer." ); + + // LIB_ALIAS objects are deprecated but we still need to gather up the derived symbols + // and save their names for the old file format. + wxArrayString aliasNames; + + if( aMap ) + { + for( auto entry : *aMap ) + { + LIB_PART* part = entry.second; + + if( part->IsAlias() && part->GetParent().lock() == aSymbol->SharedPtr() ) + aliasNames.Add( part->GetName() ); + } + } + + LIB_FIELD& value = aSymbol->GetValueField(); + + // First line: it s a comment (component name for readers) + aFormatter.Print( 0, "#\n# %s\n#\n", TO_UTF8( value.GetText() ) ); + + // Save data + aFormatter.Print( 0, "DEF" ); + aFormatter.Print( 0, " %s", TO_UTF8( value.GetText() ) ); + + LIB_FIELD& reference = aSymbol->GetReferenceField(); + + if( !reference.GetText().IsEmpty() ) + { + aFormatter.Print( 0, " %s", TO_UTF8( reference.GetText() ) ); + } + else + { + aFormatter.Print( 0, " ~" ); + } + + aFormatter.Print( 0, " %d %d %c %c %d %c %c\n", + 0, Iu2Mils( aSymbol->GetPinNameOffset() ), + aSymbol->ShowPinNumbers() ? 'Y' : 'N', + aSymbol->ShowPinNames() ? 'Y' : 'N', + aSymbol->GetUnitCount(), aSymbol->UnitsLocked() ? 'L' : 'F', + aSymbol->IsPower() ? 'P' : 'N' ); + + timestamp_t dateModified = aSymbol->GetLastModDate(); + + if( dateModified != 0 ) + { + int sec = dateModified & 63; + int min = ( dateModified >> 6 ) & 63; + int hour = ( dateModified >> 12 ) & 31; + int day = ( dateModified >> 17 ) & 31; + int mon = ( dateModified >> 22 ) & 15; + int year = ( dateModified >> 26 ) + 1990; + + aFormatter.Print( 0, "Ti %d/%d/%d %d:%d:%d\n", year, mon, day, hour, min, sec ); + } + + std::vector fields; + aSymbol->GetFields( fields ); + + // Mandatory fields: + // may have their own save policy so there is a separate loop for them. + // Empty fields are saved, because the user may have set visibility, + // size and orientation + for( int i = 0; i < MANDATORY_FIELDS; ++i ) + saveField( fields[i], aFormatter ); + + // User defined fields: + // may have their own save policy so there is a separate loop for them. + + int fieldId = MANDATORY_FIELDS; // really wish this would go away. + + for( unsigned i = MANDATORY_FIELDS; i < fields.size(); ++i ) + { + // There is no need to save empty fields, i.e. no reason to preserve field + // names now that fields names come in dynamically through the template + // fieldnames. + if( !fields[i]->GetText().IsEmpty() ) + { + fields[i]->SetId( fieldId++ ); + saveField( fields[i], aFormatter ); + } + } + + // Save the alias list: a line starting by "ALIAS". + if( !aliasNames.IsEmpty() ) + { + aFormatter.Print( 0, "ALIAS" ); + + for( unsigned i = 0; i < aliasNames.GetCount(); i++ ) + aFormatter.Print( 0, " %s", TO_UTF8( aliasNames[i] ) ); + + aFormatter.Print( 0, "\n" ); + } + + wxArrayString footprints = aSymbol->GetFPFilters(); + + // Write the footprint filter list + if( footprints.GetCount() != 0 ) + { + aFormatter.Print( 0, "$FPLIST\n" ); + + for( unsigned i = 0; i < footprints.GetCount(); i++ ) + aFormatter.Print( 0, " %s\n", TO_UTF8( footprints[i] ) ); + + aFormatter.Print( 0, "$ENDFPLIST\n" ); + } + + // Save graphics items (including pins) + if( !aSymbol->GetDrawItems().empty() ) + { + // Sort the draw items in order to editing a file editing by hand. + aSymbol->GetDrawItems().sort(); + + aFormatter.Print( 0, "DRAW\n" ); + + for( LIB_ITEM& item : aSymbol->GetDrawItems() ) + { + switch( item.Type() ) + { + default: + case LIB_FIELD_T: /* Fields have already been saved above. */ break; + case LIB_ARC_T: saveArc( (LIB_ARC*) &item, aFormatter ); break; + case LIB_BEZIER_T: saveBezier( (LIB_BEZIER*) &item, aFormatter ); break; + case LIB_CIRCLE_T: saveCircle( ( LIB_CIRCLE* ) &item, aFormatter ); break; + case LIB_PIN_T: savePin( (LIB_PIN* ) &item, aFormatter ); break; + case LIB_POLYLINE_T: savePolyLine( ( LIB_POLYLINE* ) &item, aFormatter ); break; + case LIB_RECTANGLE_T: saveRectangle( ( LIB_RECTANGLE* ) &item, aFormatter ); break; + case LIB_TEXT_T: saveText( ( LIB_TEXT* ) &item, aFormatter ); break; + } + } + + aFormatter.Print( 0, "ENDDRAW\n" ); + } + + aFormatter.Print( 0, "ENDDEF\n" ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveArc( LIB_ARC* aArc, OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aArc && aArc->Type() == LIB_ARC_T, "Invalid LIB_ARC object." ); + + int x1 = aArc->GetFirstRadiusAngle(); + + if( x1 > 1800 ) + x1 -= 3600; + + int x2 = aArc->GetSecondRadiusAngle(); + + if( x2 > 1800 ) + x2 -= 3600; + + aFormatter.Print( 0, "A %d %d %d %d %d %d %d %d %c %d %d %d %d\n", + Iu2Mils( aArc->GetPosition().x ), Iu2Mils( aArc->GetPosition().y ), + Iu2Mils( aArc->GetRadius() ), x1, x2, aArc->GetUnit(), aArc->GetConvert(), + Iu2Mils( aArc->GetWidth() ), fill_tab[ static_cast( aArc->GetFillMode() ) ], + Iu2Mils( aArc->GetStart().x ), Iu2Mils( aArc->GetStart().y ), + Iu2Mils( aArc->GetEnd().x ), Iu2Mils( aArc->GetEnd().y ) ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveBezier( LIB_BEZIER* aBezier, + OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aBezier && aBezier->Type() == LIB_BEZIER_T, "Invalid LIB_BEZIER object." ); + + aFormatter.Print( 0, "B %u %d %d %d", (unsigned)aBezier->GetPoints().size(), + aBezier->GetUnit(), aBezier->GetConvert(), Iu2Mils( aBezier->GetWidth() ) ); + + for( const auto& pt : aBezier->GetPoints() ) + aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) ); + + aFormatter.Print( 0, " %c\n", fill_tab[static_cast( aBezier->GetFillMode() )] ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveCircle( LIB_CIRCLE* aCircle, + OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aCircle && aCircle->Type() == LIB_CIRCLE_T, "Invalid LIB_CIRCLE object." ); + + aFormatter.Print( 0, "C %d %d %d %d %d %d %c\n", + Iu2Mils( aCircle->GetPosition().x ), Iu2Mils( aCircle->GetPosition().y ), + Iu2Mils( aCircle->GetRadius() ), aCircle->GetUnit(), aCircle->GetConvert(), + Iu2Mils( aCircle->GetWidth() ), fill_tab[static_cast( aCircle->GetFillMode() )] ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveField( const LIB_FIELD* aField, OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aField && aField->Type() == LIB_FIELD_T, "Invalid LIB_FIELD object." ); + + int hjustify, vjustify; + int id = aField->GetId(); + wxString text = aField->GetText(); + + hjustify = 'C'; + + if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT ) + hjustify = 'L'; + else if( aField->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT ) + hjustify = 'R'; + + vjustify = 'C'; + + if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM ) + vjustify = 'B'; + else if( aField->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP ) + vjustify = 'T'; + + aFormatter.Print( 0, "F%d %s %d %d %d %c %c %c %c%c%c", + id, + EscapedUTF8( text ).c_str(), // wraps in quotes + Iu2Mils( aField->GetTextPos().x ), Iu2Mils( aField->GetTextPos().y ), + Iu2Mils( aField->GetTextWidth() ), + aField->GetTextAngle() == 0 ? 'H' : 'V', + aField->IsVisible() ? 'V' : 'I', + hjustify, vjustify, + aField->IsItalic() ? 'I' : 'N', + aField->IsBold() ? 'B' : 'N' ); + + /* Save field name, if necessary + * Field name is saved only if it is not the default name. + * Just because default name depends on the language and can change from + * a country to another + */ + wxString defName = TEMPLATE_FIELDNAME::GetDefaultFieldName( id ); + + if( id >= MANDATORY_FIELDS && !aField->m_name.IsEmpty() && aField->m_name != defName ) + aFormatter.Print( 0, " %s", EscapedUTF8( aField->m_name ).c_str() ); + + aFormatter.Print( 0, "\n" ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::savePin( const LIB_PIN* aPin, OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aPin && aPin->Type() == LIB_PIN_T, "Invalid LIB_PIN object." ); + + int Etype; + + switch( aPin->GetType() ) + { + default: + case ELECTRICAL_PINTYPE::PT_INPUT: Etype = 'I'; break; + case ELECTRICAL_PINTYPE::PT_OUTPUT: Etype = 'O'; break; + case ELECTRICAL_PINTYPE::PT_BIDI: Etype = 'B'; break; + case ELECTRICAL_PINTYPE::PT_TRISTATE: Etype = 'T'; break; + case ELECTRICAL_PINTYPE::PT_PASSIVE: Etype = 'P'; break; + case ELECTRICAL_PINTYPE::PT_UNSPECIFIED: Etype = 'U'; break; + case ELECTRICAL_PINTYPE::PT_POWER_IN: Etype = 'W'; break; + case ELECTRICAL_PINTYPE::PT_POWER_OUT: Etype = 'w'; break; + case ELECTRICAL_PINTYPE::PT_OPENCOLLECTOR: Etype = 'C'; break; + case ELECTRICAL_PINTYPE::PT_OPENEMITTER: Etype = 'E'; break; + case ELECTRICAL_PINTYPE::PT_NC: Etype = 'N'; break; + } + + if( !aPin->GetName().IsEmpty() ) + aFormatter.Print( 0, "X %s", TO_UTF8( aPin->GetName() ) ); + else + aFormatter.Print( 0, "X ~" ); + + aFormatter.Print( 0, " %s %d %d %d %c %d %d %d %d %c", + aPin->GetNumber().IsEmpty() ? "~" : TO_UTF8( aPin->GetNumber() ), + Iu2Mils( aPin->GetPosition().x ), Iu2Mils( aPin->GetPosition().y ), + Iu2Mils( (int) aPin->GetLength() ), (int) aPin->GetOrientation(), + Iu2Mils( aPin->GetNumberTextSize() ), Iu2Mils( aPin->GetNameTextSize() ), + aPin->GetUnit(), aPin->GetConvert(), Etype ); + + if( aPin->GetShape() != GRAPHIC_PINSHAPE::LINE || !aPin->IsVisible() ) + aFormatter.Print( 0, " " ); + + if( !aPin->IsVisible() ) + aFormatter.Print( 0, "N" ); + + switch( aPin->GetShape() ) + { + case GRAPHIC_PINSHAPE::LINE: break; + case GRAPHIC_PINSHAPE::INVERTED: aFormatter.Print( 0, "I" ); break; + case GRAPHIC_PINSHAPE::CLOCK: aFormatter.Print( 0, "C" ); break; + case GRAPHIC_PINSHAPE::INVERTED_CLOCK: aFormatter.Print( 0, "IC" ); break; + case GRAPHIC_PINSHAPE::INPUT_LOW: aFormatter.Print( 0, "L" ); break; + case GRAPHIC_PINSHAPE::CLOCK_LOW: aFormatter.Print( 0, "CL" ); break; + case GRAPHIC_PINSHAPE::OUTPUT_LOW: aFormatter.Print( 0, "V" ); break; + case GRAPHIC_PINSHAPE::FALLING_EDGE_CLOCK: aFormatter.Print( 0, "F" ); break; + case GRAPHIC_PINSHAPE::NONLOGIC: aFormatter.Print( 0, "X" ); break; + default: wxFAIL_MSG( "Invalid pin shape" ); + } + + aFormatter.Print( 0, "\n" ); + + const_cast( aPin )->ClearFlags( IS_CHANGED ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::savePolyLine( LIB_POLYLINE* aPolyLine, + OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aPolyLine && aPolyLine->Type() == LIB_POLYLINE_T, "Invalid LIB_POLYLINE object." ); + + int ccount = aPolyLine->GetCornerCount(); + + aFormatter.Print( 0, "P %d %d %d %d", ccount, aPolyLine->GetUnit(), aPolyLine->GetConvert(), + Iu2Mils( aPolyLine->GetWidth() ) ); + + for( const auto& pt : aPolyLine->GetPolyPoints() ) + { + aFormatter.Print( 0, " %d %d", Iu2Mils( pt.x ), Iu2Mils( pt.y ) ); + } + + aFormatter.Print( 0, " %c\n", fill_tab[static_cast( aPolyLine->GetFillMode() )] ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveRectangle( LIB_RECTANGLE* aRectangle, + OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aRectangle && aRectangle->Type() == LIB_RECTANGLE_T, + "Invalid LIB_RECTANGLE object." ); + + aFormatter.Print( 0, "S %d %d %d %d %d %d %d %c\n", + Iu2Mils( aRectangle->GetPosition().x ), + Iu2Mils( aRectangle->GetPosition().y ), + Iu2Mils( aRectangle->GetEnd().x ), Iu2Mils( aRectangle->GetEnd().y ), + aRectangle->GetUnit(), aRectangle->GetConvert(), + Iu2Mils( aRectangle->GetWidth() ), fill_tab[static_cast( aRectangle->GetFillMode() )] ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveText( const LIB_TEXT* aText, OUTPUTFORMATTER& aFormatter ) +{ + wxCHECK_RET( aText && aText->Type() == LIB_TEXT_T, "Invalid LIB_TEXT object." ); + + wxString text = aText->GetText(); + + if( text.Contains( wxT( " " ) ) || text.Contains( wxT( "~" ) ) || text.Contains( wxT( "\"" ) ) ) + { + // convert double quote to similar-looking two apostrophes + text.Replace( wxT( "\"" ), wxT( "''" ) ); + text = wxT( "\"" ) + text + wxT( "\"" ); + } + + aFormatter.Print( 0, "T %g %d %d %d %d %d %d %s", aText->GetTextAngle(), + Iu2Mils( aText->GetTextPos().x ), Iu2Mils( aText->GetTextPos().y ), + Iu2Mils( aText->GetTextWidth() ), !aText->IsVisible(), + aText->GetUnit(), aText->GetConvert(), TO_UTF8( text ) ); + + aFormatter.Print( 0, " %s %d", aText->IsItalic() ? "Italic" : "Normal", aText->IsBold() ); + + char hjustify = 'C'; + + if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_LEFT ) + hjustify = 'L'; + else if( aText->GetHorizJustify() == GR_TEXT_HJUSTIFY_RIGHT ) + hjustify = 'R'; + + char vjustify = 'C'; + + if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_BOTTOM ) + vjustify = 'B'; + else if( aText->GetVertJustify() == GR_TEXT_VJUSTIFY_TOP ) + vjustify = 'T'; + + aFormatter.Print( 0, " %c %c\n", hjustify, vjustify ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::saveDocFile() +{ + wxFileName fileName = m_libFileName; + + fileName.SetExt( DOC_EXT ); + FILE_OUTPUTFORMATTER formatter( fileName.GetFullPath() ); + + formatter.Print( 0, "%s\n", DOCFILE_IDENT ); + + for( LIB_PART_MAP::iterator it = m_symbols.begin(); it != m_symbols.end(); ++it ) + { + wxString description = it->second->GetDescription(); + wxString keyWords = it->second->GetKeyWords(); + wxString docFileName = it->second->GetDatasheetField().GetText(); + + if( description.IsEmpty() && keyWords.IsEmpty() && docFileName.IsEmpty() ) + continue; + + formatter.Print( 0, "#\n$CMP %s\n", TO_UTF8( it->second->GetName() ) ); + + if( !description.IsEmpty() ) + formatter.Print( 0, "D %s\n", TO_UTF8( description ) ); + + if( !keyWords.IsEmpty() ) + formatter.Print( 0, "K %s\n", TO_UTF8( keyWords ) ); + + if( !docFileName.IsEmpty() ) + formatter.Print( 0, "F %s\n", TO_UTF8( docFileName ) ); + + formatter.Print( 0, "$ENDCMP\n" ); + } + + formatter.Print( 0, "#\n#End Doc Library\n" ); +} + + +void SCH_LEGACY_PLUGIN_CACHE::DeleteSymbol( const wxString& aSymbolName ) +{ + LIB_PART_MAP::iterator it = m_symbols.find( aSymbolName ); + + if( it == m_symbols.end() ) + THROW_IO_ERROR( wxString::Format( _( "library %s does not contain a symbol named %s" ), + m_libFileName.GetFullName(), aSymbolName ) ); + + LIB_PART* part = it->second; + + if( part->IsRoot() ) + { + LIB_PART* rootPart = part; + + // Remove the root symbol and all it's children. + m_symbols.erase( it ); + + LIB_PART_MAP::iterator it1 = m_symbols.begin(); + + while( it1 != m_symbols.end() ) + { + if( it1->second->IsAlias() && it1->second->GetParent().lock() == rootPart->SharedPtr() ) + { + delete it1->second; + it1 = m_symbols.erase( it1 ); + } + else + { + it1++; + } + } + + delete rootPart; + } + else + { + // Just remove the alias. + m_symbols.erase( it ); + delete part; + } + + ++m_modHash; + m_isModified = true; +} + + void SCH_LEGACY_PLUGIN::cacheLib( const wxString& aLibraryFileName ) { if( !m_cache || !m_cache->IsFile( aLibraryFileName ) || m_cache->IsFileChanged() ) @@ -3059,6 +4223,17 @@ void SCH_LEGACY_PLUGIN::cacheLib( const wxString& aLibraryFileName ) } +bool SCH_LEGACY_PLUGIN::writeDocFile( const PROPERTIES* aProperties ) +{ + std::string propName( SCH_LEGACY_PLUGIN::PropNoDocFile ); + + if( aProperties && aProperties->find( propName ) != aProperties->end() ) + return false; + + return true; +} + + bool SCH_LEGACY_PLUGIN::isBuffering( const PROPERTIES* aProperties ) { return ( aProperties && aProperties->Exists( SCH_LEGACY_PLUGIN::PropBuffering ) ); @@ -3137,6 +4312,58 @@ LIB_PART* SCH_LEGACY_PLUGIN::LoadSymbol( const wxString& aLibraryPath, const wxS } +void SCH_LEGACY_PLUGIN::SaveSymbol( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + m_props = aProperties; + + cacheLib( aLibraryPath ); + + m_cache->AddSymbol( aSymbol ); + + if( !isBuffering( aProperties ) ) + m_cache->Save( writeDocFile( aProperties ) ); +} + + +void SCH_LEGACY_PLUGIN::DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties ) +{ + LOCALE_IO toggle; // toggles on, then off, the C locale. + m_props = aProperties; + + cacheLib( aLibraryPath ); + + m_cache->DeleteSymbol( aSymbolName ); + + if( !isBuffering( aProperties ) ) + m_cache->Save( writeDocFile( aProperties ) ); +} + + +void SCH_LEGACY_PLUGIN::CreateSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties ) +{ + if( wxFileExists( aLibraryPath ) ) + { + THROW_IO_ERROR( wxString::Format( + _( "symbol library \"%s\" already exists, cannot create a new library" ), + aLibraryPath.GetData() ) ); + } + + LOCALE_IO toggle; + + m_props = aProperties; + + delete m_cache; + m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath ); + m_cache->SetModified(); + m_cache->Save( writeDocFile( aProperties ) ); + m_cache->Load(); // update m_writable and m_mod_time +} + + bool SCH_LEGACY_PLUGIN::DeleteSymbolLib( const wxString& aLibraryPath, const PROPERTIES* aProperties ) { @@ -3163,6 +4390,25 @@ bool SCH_LEGACY_PLUGIN::DeleteSymbolLib( const wxString& aLibraryPath, } +void SCH_LEGACY_PLUGIN::SaveLibrary( const wxString& aLibraryPath, const PROPERTIES* aProperties ) +{ + if( !m_cache ) + m_cache = new SCH_LEGACY_PLUGIN_CACHE( aLibraryPath ); + + wxString oldFileName = m_cache->GetFileName(); + + if( !m_cache->IsFile( aLibraryPath ) ) + { + m_cache->SetFileName( aLibraryPath ); + } + + // This is a forced save. + m_cache->SetModified(); + m_cache->Save( writeDocFile( aProperties ) ); + m_cache->SetFileName( oldFileName ); +} + + bool SCH_LEGACY_PLUGIN::CheckHeader( const wxString& aFileName ) { // Open file and check first line @@ -3192,5 +4438,12 @@ LIB_PART* SCH_LEGACY_PLUGIN::ParsePart( LINE_READER& reader, int aMajorVersion, } +void SCH_LEGACY_PLUGIN::FormatPart( LIB_PART* part, OUTPUTFORMATTER & formatter ) +{ + SCH_LEGACY_PLUGIN_CACHE::SaveSymbol( part, formatter ); +} + + + const char* SCH_LEGACY_PLUGIN::PropBuffering = "buffering"; const char* SCH_LEGACY_PLUGIN::PropNoDocFile = "no_doc_file"; diff --git a/eeschema/sch_plugins/legacy/sch_legacy_plugin.h b/eeschema/sch_plugins/legacy/sch_legacy_plugin.h index b28f8dbabb..9445daf002 100644 --- a/eeschema/sch_plugins/legacy/sch_legacy_plugin.h +++ b/eeschema/sch_plugins/legacy/sch_legacy_plugin.h @@ -5,7 +5,7 @@ * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 CERN - * Copyright (C) 2016-2021 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2016-2019 KiCad Developers, see change_log.txt for contributors. * * @author Wayne Stambaugh * @@ -59,9 +59,6 @@ class BUS_ALIAS; * this. This parser is not that forgiving and sticks to the legacy file format document. * * As with all SCH_PLUGINs there is no UI dependencies i.e. windowing calls allowed. - * - * @warning All save code has been removed from the plugin. Legacy schematics and symbol - * librarys are now read only. */ class SCH_LEGACY_PLUGIN : public SCH_PLUGIN { @@ -107,6 +104,9 @@ public: void LoadContent( LINE_READER& aReader, SCH_SCREEN* aScreen, int version = EESCHEMA_VERSION ); + void Save( const wxString& aFileName, SCH_SHEET* aScreen, SCHEMATIC* aSchematic, + const PROPERTIES* aProperties = nullptr ) override; + void Format( SCH_SHEET* aSheet ); void Format( SELECTION* aSelection, OUTPUTFORMATTER* aFormatter ); @@ -119,8 +119,16 @@ public: const PROPERTIES* aProperties = nullptr ) override; LIB_PART* LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName, const PROPERTIES* aProperties = nullptr ) override; + void SaveSymbol( const wxString& aLibraryPath, const LIB_PART* aSymbol, + const PROPERTIES* aProperties = nullptr ) override; + void DeleteSymbol( const wxString& aLibraryPath, const wxString& aSymbolName, + const PROPERTIES* aProperties = nullptr ) override; + void CreateSymbolLib( const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; bool DeleteSymbolLib( const wxString& aLibraryPath, const PROPERTIES* aProperties = nullptr ) override; + void SaveLibrary( const wxString& aLibraryPath, + const PROPERTIES* aProperties = nullptr ) override; bool CheckHeader( const wxString& aFileName ) override; bool IsSymbolLibWritable( const wxString& aLibraryPath ) override; @@ -128,6 +136,7 @@ public: const wxString& GetError() const override { return m_error; } static LIB_PART* ParsePart( LINE_READER& aReader, int majorVersion = 0, int minorVersion = 0 ); + static void FormatPart( LIB_PART* aPart, OUTPUTFORMATTER& aFormatter ); private: void loadHierarchy( SCH_SHEET* aSheet ); @@ -144,7 +153,19 @@ private: SCH_COMPONENT* loadComponent( LINE_READER& aReader ); std::shared_ptr loadBusAlias( LINE_READER& aReader, SCH_SCREEN* aScreen ); + void saveComponent( SCH_COMPONENT* aComponent ); + void saveField( SCH_FIELD* aField ); + void saveBitmap( SCH_BITMAP* aBitmap ); + void saveSheet( SCH_SHEET* aSheet ); + void saveJunction( SCH_JUNCTION* aJunction ); + void saveNoConnect( SCH_NO_CONNECT* aNoConnect ); + void saveBusEntry( SCH_BUS_ENTRY_BASE* aBusEntry ); + void saveLine( SCH_LINE* aLine ); + void saveText( SCH_TEXT* aText ); + void saveBusAlias( std::shared_ptr aAlias ); + void cacheLib( const wxString& aLibraryFileName ); + bool writeDocFile( const PROPERTIES* aProperties ); bool isBuffering( const PROPERTIES* aProperties ); protected: