15 changed files with 902 additions and 844 deletions
-
9CHANGELOG.txt
-
2eeschema/CMakeLists.txt
-
281eeschema/class_libentry.cpp
-
125eeschema/class_libentry.h
-
110eeschema/class_libentry_fields.cpp
-
72eeschema/class_libentry_fields.h
-
512eeschema/class_library.cpp
-
123eeschema/class_library.h
-
185eeschema/classes_body_items.cpp
-
69eeschema/classes_body_items.h
-
152eeschema/dialog_edit_component_in_schematic.cpp
-
13eeschema/eelibs_read_libraryfiles.cpp
-
53eeschema/getpart.cpp
-
2eeschema/makefile.include
-
38eeschema/savelib.cpp
@ -0,0 +1,281 @@ |
|||
/**********************************************************/ |
|||
/* lib_entry.cpp */ |
|||
/**********************************************************/ |
|||
|
|||
#include "fctsys.h"
|
|||
#include "gr_basic.h"
|
|||
|
|||
#include "common.h"
|
|||
#include "program.h"
|
|||
#include "libcmp.h"
|
|||
#include "general.h"
|
|||
|
|||
#include "protos.h"
|
|||
|
|||
|
|||
/*********************/ |
|||
/* class LibCmpEntry */ |
|||
/*********************/ |
|||
|
|||
/* Basic class for librarty oomponent description
|
|||
* Not directly used |
|||
* Used to create the 2 derived classes : |
|||
* - EDA_LibCmpAliasStruct |
|||
* - EDA_LibComponentStruct |
|||
*/ |
|||
|
|||
/********************************************************************/ |
|||
LibCmpEntry::LibCmpEntry( LibrEntryType CmpType, const wxChar* CmpName ) : |
|||
EDA_BaseStruct( LIBCOMPONENT_STRUCT_TYPE ) |
|||
/********************************************************************/ |
|||
{ |
|||
Type = CmpType; |
|||
m_Name.m_FieldId = VALUE; |
|||
if( CmpName ) |
|||
m_Name.m_Text = CmpName; |
|||
} |
|||
|
|||
|
|||
/******************************/ |
|||
LibCmpEntry::~LibCmpEntry() |
|||
/******************************/ |
|||
{ |
|||
} |
|||
|
|||
|
|||
/*******************************/ |
|||
/* class EDA_LibCmpAliasStruct */ |
|||
/*******************************/ |
|||
|
|||
/* Class to define an alias of a component
|
|||
* An alias uses the component defintion (graphic, pins...) |
|||
* but has its own name and documentation. |
|||
* Therefore, when the component is modified, alias of this component are modified. |
|||
* This is a simple method to create components with differs very few |
|||
* (like 74LS00, 74HC00 ... and many op amps ) |
|||
*/ |
|||
|
|||
EDA_LibCmpAliasStruct:: EDA_LibCmpAliasStruct( const wxChar* CmpName, |
|||
const wxChar* CmpRootName ) : |
|||
LibCmpEntry( ALIAS, CmpName ) |
|||
{ |
|||
if( CmpRootName == NULL ) |
|||
m_RootName.Empty(); |
|||
else |
|||
m_RootName = CmpRootName; |
|||
} |
|||
|
|||
|
|||
EDA_LibCmpAliasStruct::~EDA_LibCmpAliasStruct() |
|||
{ |
|||
} |
|||
|
|||
|
|||
/********************************/ |
|||
/* class EDA_LibComponentStruct */ |
|||
/********************************/ |
|||
|
|||
/* This is a standard component (in library)
|
|||
*/ |
|||
EDA_LibComponentStruct:: EDA_LibComponentStruct( const wxChar* CmpName ) : |
|||
LibCmpEntry( ROOT, CmpName ) |
|||
{ |
|||
m_Drawings = NULL; |
|||
m_LastDate = 0; |
|||
m_UnitCount = 1; |
|||
m_TextInside = 40; |
|||
m_Options = ENTRY_NORMAL; |
|||
m_UnitSelectionLocked = FALSE; |
|||
m_DrawPinNum = m_DrawPinName = 1; |
|||
|
|||
Fields = NULL; |
|||
m_Prefix.m_FieldId = REFERENCE; |
|||
} |
|||
|
|||
|
|||
/******************************************************/ |
|||
EDA_LibComponentStruct::~EDA_LibComponentStruct() |
|||
/******************************************************/ |
|||
{ |
|||
LibEDA_BaseStruct* DrawItem, * NextDrawItem; |
|||
LibDrawField* TempField, * field; |
|||
|
|||
field = Fields; Fields = NULL; |
|||
while( field ) |
|||
{ |
|||
TempField = field; |
|||
field = field->Next(); |
|||
SAFE_DELETE( TempField ); |
|||
} |
|||
|
|||
/* suppression des elements dependants */ |
|||
DrawItem = m_Drawings; m_Drawings = NULL; |
|||
while( DrawItem ) |
|||
{ |
|||
NextDrawItem = DrawItem->Next(); |
|||
SAFE_DELETE( DrawItem ); |
|||
DrawItem = NextDrawItem; |
|||
} |
|||
} |
|||
|
|||
|
|||
/**********************************************************************/ |
|||
EDA_Rect EDA_LibComponentStruct::GetBoundaryBox( int Unit, int Convert ) |
|||
/**********************************************************************/ |
|||
|
|||
/* Return the componenty boundary box ( in user coordinates )
|
|||
* The unit Unit, and the shape Convert are considered. |
|||
* If Unit == 0, Unit is not used |
|||
* if Convert == 0 Convert is non used |
|||
**/ |
|||
{ |
|||
int xmin, xmax, ymin, ymax, x1, y1; |
|||
int* pt, ii; |
|||
LibEDA_BaseStruct* DrawEntry; |
|||
EDA_Rect BoundaryBox; |
|||
|
|||
DrawEntry = m_Drawings; |
|||
if( DrawEntry ) |
|||
{ |
|||
xmin = ymin = 0x7FFFFFFF; |
|||
xmax = ymax = 0x80000000; |
|||
} |
|||
else |
|||
{ |
|||
xmin = ymin = -50; |
|||
xmax = ymax = 50; // Min size in 1/1000 inch
|
|||
} |
|||
|
|||
for( ; DrawEntry != NULL; DrawEntry = DrawEntry->Next() ) |
|||
{ |
|||
if( DrawEntry->m_Unit > 0 ) // The item is non common to units
|
|||
if( (m_UnitCount > 1 ) && (Unit > 0) && (Unit != DrawEntry->m_Unit) ) |
|||
continue; |
|||
if( DrawEntry->m_Convert > 0 ) //The item is not common to alls convert
|
|||
if( (Convert > 0) && (Convert != DrawEntry->m_Convert) ) |
|||
continue; |
|||
|
|||
switch( DrawEntry->Type() ) |
|||
{ |
|||
case COMPONENT_ARC_DRAW_TYPE: |
|||
{ |
|||
// Arc is reduced to a line from m_Start to m_End.
|
|||
// TO DO better.
|
|||
LibDrawArc* Arc = (LibDrawArc*) DrawEntry; |
|||
x1 = Arc->m_ArcStart.x; |
|||
y1 = Arc->m_ArcStart.y; |
|||
xmin = MIN( xmin, x1 ); |
|||
ymin = MIN( ymin, y1 ); |
|||
xmax = MAX( xmax, x1 ); |
|||
ymax = MAX( ymax, y1 ); |
|||
x1 = Arc->m_ArcEnd.x; |
|||
y1 = Arc->m_ArcEnd.y; |
|||
xmin = MIN( xmin, x1 ); |
|||
ymin = MIN( ymin, y1 ); |
|||
xmax = MAX( xmax, x1 ); |
|||
ymax = MAX( ymax, y1 ); |
|||
} |
|||
break; |
|||
|
|||
case COMPONENT_CIRCLE_DRAW_TYPE: |
|||
{ |
|||
LibDrawCircle* Circle = (LibDrawCircle*) DrawEntry; |
|||
x1 = Circle->m_Pos.x - Circle->m_Rayon; |
|||
y1 = Circle->m_Pos.y - Circle->m_Rayon; |
|||
xmin = MIN( xmin, x1 ); |
|||
ymin = MIN( ymin, y1 ); |
|||
x1 = Circle->m_Pos.x + Circle->m_Rayon; |
|||
y1 = Circle->m_Pos.y + Circle->m_Rayon; |
|||
xmax = MAX( xmax, x1 ); |
|||
ymax = MAX( ymax, y1 ); |
|||
} |
|||
break; |
|||
|
|||
case COMPONENT_RECT_DRAW_TYPE: |
|||
{ |
|||
LibDrawSquare* Square = (LibDrawSquare*) DrawEntry; |
|||
xmin = MIN( xmin, Square->m_Pos.x ); |
|||
xmin = MIN( xmin, Square->m_End.x ); |
|||
xmax = MAX( xmax, Square->m_Pos.x ); |
|||
xmax = MAX( xmax, Square->m_End.x ); |
|||
ymin = MIN( ymin, Square->m_Pos.y ); |
|||
ymin = MIN( ymin, Square->m_End.y ); |
|||
ymax = MAX( ymax, Square->m_Pos.y ); |
|||
ymax = MAX( ymax, Square->m_End.y ); |
|||
} |
|||
break; |
|||
|
|||
case COMPONENT_PIN_DRAW_TYPE: |
|||
{ |
|||
LibDrawPin* Pin = (LibDrawPin*) DrawEntry; |
|||
x1 = Pin->m_Pos.x; |
|||
y1 = Pin->m_Pos.y; |
|||
xmin = MIN( xmin, x1 ); |
|||
xmax = MAX( xmax, x1 ); |
|||
ymin = MIN( ymin, y1 ); |
|||
ymax = MAX( ymax, y1 ); |
|||
#if 0 \
|
|||
// 0 pour englober le point origine de la pin, 1 pour englober toute la pin
|
|||
switch( Pin->Orient ) |
|||
{ |
|||
case PIN_UP: |
|||
y1 += Pin->Len; break; |
|||
|
|||
case PIN_DOWN: |
|||
y1 -= Pin->Len; break; |
|||
|
|||
case PIN_LEFT: |
|||
x1 -= Pin->Len; break; |
|||
|
|||
case PIN_RIGHT: |
|||
x1 += Pin->Len; break; |
|||
} |
|||
|
|||
xmin = MIN( xmin, x1 ); |
|||
xmax = MAX( xmax, x1 ); |
|||
ymin = MIN( ymin, y1 ); |
|||
ymax = MAX( ymax, y1 ); |
|||
#endif
|
|||
} |
|||
break; |
|||
|
|||
case COMPONENT_GRAPHIC_TEXT_DRAW_TYPE: |
|||
break; |
|||
|
|||
case COMPONENT_POLYLINE_DRAW_TYPE: |
|||
{ |
|||
LibDrawPolyline* polyline = (LibDrawPolyline*) DrawEntry; |
|||
pt = polyline->m_PolyList; |
|||
for( ii = 0; ii < polyline->m_CornersCount; ii++ ) |
|||
{ |
|||
if( xmin > *pt ) |
|||
xmin = *pt; |
|||
if( xmax < *pt ) |
|||
xmax = *pt; |
|||
pt++; |
|||
if( ymin > *pt ) |
|||
ymin = *pt; |
|||
if( ymax < *pt ) |
|||
ymax = *pt; |
|||
pt++; |
|||
} |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
; |
|||
} |
|||
} |
|||
|
|||
// Update the BoundaryBox. Remenber the fact the screen Y axis is the reverse */
|
|||
ymax = -ymax; ymin = -ymin; // Y is is screen axis sense
|
|||
// Ensure w and H > 0 (wxRect assume it)
|
|||
if( xmax < xmin ) |
|||
EXCHG( xmax, xmin ); |
|||
if( ymax < ymin ) |
|||
EXCHG( ymax, ymin ); |
|||
BoundaryBox.SetX( xmin ); BoundaryBox.SetWidth( xmax - xmin ); |
|||
BoundaryBox.SetY( ymin ); BoundaryBox.SetHeight( ymax - ymin ); |
|||
|
|||
return BoundaryBox; |
|||
} |
@ -0,0 +1,125 @@ |
|||
/****************************************************************/ |
|||
/* Headers fo lib component (or libentry) definitions */ |
|||
/****************************************************************/ |
|||
|
|||
#ifndef CLASS_LIBENTRY_H |
|||
#define CLASS_LIBENTRY_H |
|||
|
|||
#include "classes_body_items.h" |
|||
#include "class_libentry_fields.h" |
|||
|
|||
/* Types for components in libraries |
|||
* components can be a true component or an alias of a true component. |
|||
*/ |
|||
enum LibrEntryType { |
|||
ROOT, /* This is a true component standard EDA_LibComponentStruct */ |
|||
ALIAS /* This is an alias of a true component */ |
|||
}; |
|||
|
|||
/* values for member .m_Options */ |
|||
enum LibrEntryOptions { |
|||
ENTRY_NORMAL, // Libentry is a standard component (real or alias) |
|||
ENTRY_POWER // Libentry is a power symbol |
|||
}; |
|||
|
|||
|
|||
/* basic class to describe components in libraries (true component or alias), non used directly */ |
|||
class LibCmpEntry : public EDA_BaseStruct |
|||
{ |
|||
public: |
|||
LibrEntryType Type; /* Type = ROOT; |
|||
* = ALIAS pour struct LibraryAliasType */ |
|||
LibDrawField m_Name; // name (74LS00 ..) in lib ( = VALUE ) |
|||
wxString m_Doc; /* documentation for info */ |
|||
wxString m_KeyWord; /* keyword list (used to select a group of components by keyword) */ |
|||
wxString m_DocFile; /* Associed doc filename */ |
|||
LibrEntryOptions m_Options; // special features (i.e. Entry is a POWER) |
|||
|
|||
public: |
|||
LibCmpEntry( LibrEntryType CmpType, const wxChar* CmpName ); |
|||
virtual ~LibCmpEntry(); |
|||
virtual wxString GetClass() const |
|||
{ |
|||
return wxT( "LibCmpEntry" ); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Function SaveDoc |
|||
* writes the doc info out to a FILE in "*.dcm" format. |
|||
* @param aFile The FILE to write to. |
|||
* @return bool - true if success writing else false. |
|||
*/ |
|||
bool SaveDoc( FILE* aFile ); |
|||
}; |
|||
|
|||
|
|||
/*********************************************/ |
|||
/* class to handle an usual component in lib */ |
|||
/*********************************************/ |
|||
class EDA_LibComponentStruct : public LibCmpEntry |
|||
{ |
|||
public: |
|||
LibDrawField m_Prefix; /* Prefix ( U, IC ... ) = REFERENCE */ |
|||
wxArrayString m_AliasList; /* ALIAS list for the component */ |
|||
wxArrayString m_FootprintList; /* list of suitable footprint names for the component (wildcard names accepted)*/ |
|||
int m_UnitCount; /* Units (or sections) per package */ |
|||
bool m_UnitSelectionLocked; // True if units are differents and their selection is locked |
|||
// (i.e. if part A cannot be automatically changed in part B |
|||
int m_TextInside; /* if 0: pin name drawn on the pin itself |
|||
* if > 0 pin name drawn inside the component, |
|||
* with a distance of m_TextInside in mils */ |
|||
bool m_DrawPinNum; |
|||
bool m_DrawPinName; |
|||
LibDrawField* Fields; /* Auxiliairy Field list (id = 2 a 11) */ |
|||
LibEDA_BaseStruct* m_Drawings; /* How to draw this part */ |
|||
long m_LastDate; // Last change Date |
|||
|
|||
public: |
|||
virtual wxString GetClass() const |
|||
{ |
|||
return wxT( "EDA_LibComponentStruct" ); |
|||
} |
|||
|
|||
|
|||
EDA_LibComponentStruct( const wxChar* CmpName ); |
|||
EDA_Rect GetBoundaryBox( int Unit, int Convert ); /* return Box around the part. */ |
|||
|
|||
~EDA_LibComponentStruct(); |
|||
void SortDrawItems(); |
|||
|
|||
/** |
|||
* Function Save |
|||
* writes the data structures for this object out to a FILE in "*.lib" format. |
|||
* @param aFile The FILE to write to. |
|||
* @return bool - true if success writing else false. |
|||
*/ |
|||
bool Save( FILE* aFile ); |
|||
|
|||
/** Function SetFields |
|||
* initialize fields from a vector of fields |
|||
* @param aFields a std::vector <LibDrawField> to import. |
|||
*/ |
|||
void SetFields( const std::vector <LibDrawField> aFields ); |
|||
}; |
|||
|
|||
|
|||
/**************************************************************************/ |
|||
/* class to handle an alias of an usual component in lib (root component) */ |
|||
/**************************************************************************/ |
|||
class EDA_LibCmpAliasStruct : public LibCmpEntry |
|||
{ |
|||
public: |
|||
wxString m_RootName; /* Root component Part name */ |
|||
|
|||
public: |
|||
EDA_LibCmpAliasStruct( const wxChar* CmpName, const wxChar* CmpRootName ); |
|||
~EDA_LibCmpAliasStruct(); |
|||
virtual wxString GetClass() const |
|||
{ |
|||
return wxT( "EDA_LibCmpAliasStruct" ); |
|||
} |
|||
}; |
|||
|
|||
|
|||
#endif // CLASS_LIBENTRY_H |
@ -0,0 +1,110 @@ |
|||
/**********************************************************/ |
|||
/* libclass.cpp */ |
|||
/**********************************************************/ |
|||
|
|||
#include "fctsys.h"
|
|||
#include "gr_basic.h"
|
|||
|
|||
#include "common.h"
|
|||
#include "program.h"
|
|||
#include "libcmp.h"
|
|||
#include "general.h"
|
|||
|
|||
|
|||
/***************************/ |
|||
/* class LibraryFieldEntry */ |
|||
/***************************/ |
|||
|
|||
/* a Field is a string linked to a component.
|
|||
* Unlike a pure graphic text, fields can be used in netlist generation |
|||
* and other things. |
|||
* |
|||
* 4 fields have a special meaning: |
|||
* REFERENCE |
|||
* VALUE |
|||
* FOOTPRINT NAME |
|||
* DOCUMENTATION LINK (reserved but not used in kicad) |
|||
*/ |
|||
LibDrawField::LibDrawField( int idfield ) : LibEDA_BaseStruct( COMPONENT_FIELD_DRAW_TYPE ) |
|||
{ |
|||
m_FieldId = idfield; /* 0 a 11, 0 = REFERENCE, 1 = VALUE*/ |
|||
if( m_FieldId < 0 ) |
|||
m_FieldId = 0; |
|||
if( m_FieldId >= NUMBER_OF_FIELDS ) |
|||
m_FieldId = NUMBER_OF_FIELDS - 1; |
|||
m_Size.x = m_Size.y = DEFAULT_SIZE_TEXT; |
|||
} |
|||
|
|||
|
|||
LibDrawField::~LibDrawField() |
|||
{ |
|||
} |
|||
|
|||
|
|||
// Creation et Duplication d'un field
|
|||
LibDrawField* LibDrawField::GenCopy() |
|||
{ |
|||
LibDrawField* newfield = new LibDrawField( m_FieldId ); |
|||
|
|||
Copy( newfield ); |
|||
|
|||
return newfield; |
|||
} |
|||
|
|||
|
|||
/** Function Copy
|
|||
* copy parameters of this to Target. Pointers are not copied |
|||
* @param Target = the LibDrawField to set with "this" values |
|||
*/ |
|||
void LibDrawField::Copy( LibDrawField* Target ) const |
|||
{ |
|||
Target->m_Pos = m_Pos; |
|||
Target->m_Size = m_Size; |
|||
Target->m_Width = m_Width; |
|||
Target->m_Orient = m_Orient; |
|||
Target->m_Attributs = m_Attributs; |
|||
Target->m_Text = m_Text; |
|||
Target->m_Name = m_Name; |
|||
Target->m_HJustify = m_HJustify; |
|||
Target->m_VJustify = m_VJustify; |
|||
Target->m_Italic = m_Italic; |
|||
} |
|||
|
|||
|
|||
/**************************************************/ |
|||
bool LibDrawField::Save( FILE* ExportFile ) const |
|||
/**************************************************/ |
|||
{ |
|||
int hjustify, vjustify; |
|||
wxString text = m_Text; |
|||
|
|||
hjustify = 'C'; |
|||
if( m_HJustify == GR_TEXT_HJUSTIFY_LEFT ) |
|||
hjustify = 'L'; |
|||
else if( m_HJustify == GR_TEXT_HJUSTIFY_RIGHT ) |
|||
hjustify = 'R'; |
|||
vjustify = 'C'; |
|||
if( m_VJustify == GR_TEXT_VJUSTIFY_BOTTOM ) |
|||
vjustify = 'B'; |
|||
else if( m_VJustify == GR_TEXT_VJUSTIFY_TOP ) |
|||
vjustify = 'T'; |
|||
if( text.IsEmpty() ) |
|||
text = wxT( "~" ); |
|||
fprintf( ExportFile, "F%d \"%s\" %d %d %d %c %c %c %c%c%c", |
|||
m_FieldId, CONV_TO_UTF8( text ), |
|||
m_Pos.x, m_Pos.y, |
|||
m_Size.x, |
|||
m_Orient == 0 ? 'H' : 'V', |
|||
(m_Attributs & TEXT_NO_VISIBLE ) ? 'I' : 'V', |
|||
hjustify, vjustify, |
|||
m_Italic ? 'I' : 'N', |
|||
m_Width > 1 ? 'B' : 'N'); |
|||
|
|||
// Save field name, if necessary
|
|||
if( m_FieldId >= FIELD1 && !m_Name.IsEmpty() ) |
|||
fprintf( ExportFile, " \"%s\"", CONV_TO_UTF8( m_Name ) ); |
|||
|
|||
fprintf( ExportFile, "\n" ); |
|||
return true; |
|||
} |
|||
|
@ -0,0 +1,72 @@ |
|||
/**************************************************************/ |
|||
/* Lib component definitions (libentry) definition of fields */ |
|||
/**************************************************************/ |
|||
|
|||
#ifndef CLASS_LIBENTRY_FIELDS_H |
|||
#define CLASS_LIBENTRY_FIELDS_H |
|||
|
|||
|
|||
/* Fields , same as component fields. |
|||
* can be defined in libraries (mandatory for ref and value, ca be useful for footprints) |
|||
* 2 Fields are always defined : |
|||
* Prefix (U, IC..) with gives the reference in schematic) |
|||
* Name (74LS00..) used to find the component in libraries, and give the default value in schematic |
|||
*/ |
|||
|
|||
class LibDrawField : public LibEDA_BaseStruct |
|||
, public EDA_TextStruct |
|||
{ |
|||
public: |
|||
int m_FieldId; /* 0 a 11 |
|||
* 0 = Reference; 1 = Value |
|||
* 2 = Default footprint, 3 = subsheet (not used, reserved) |
|||
* 4 .. 11 other fields |
|||
*/ |
|||
wxString m_Name; /* Field Name (not the field text itself, that is .m_Text) */ |
|||
|
|||
public: |
|||
|
|||
LibDrawField* Next() const { return (LibDrawField*) Pnext; } |
|||
LibDrawField* Back() const { return (LibDrawField*) Pback; } |
|||
|
|||
|
|||
LibDrawField( int idfield = 2 ); |
|||
~LibDrawField(); |
|||
virtual wxString GetClass() const |
|||
{ |
|||
return wxT( "LibDrawField" ); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Function Save |
|||
* writes the data structures for this object out to a FILE in "*.brd" format. |
|||
* @param aFile The FILE to write to. |
|||
* @return bool - true if success writing else false. |
|||
*/ |
|||
virtual bool Save( FILE* aFile ) const; |
|||
|
|||
|
|||
LibDrawField* GenCopy(); |
|||
|
|||
/** Function Copy |
|||
* copy parameters of this to Target. Pointers are not copied |
|||
* @param Target = the LibDrawField to set with "this" values |
|||
*/ |
|||
void Copy( LibDrawField* Target ) const; |
|||
|
|||
void SetFields( const std::vector <LibDrawField> aFields ); |
|||
|
|||
void Draw( WinEDA_DrawPanel * aPanel, wxDC * aDC, const wxPoint &aOffset, int aColor, |
|||
int aDrawMode, void* aData, int aTransformMatrix[2][2] ); |
|||
|
|||
/** |
|||
* Function HitTest |
|||
* tests if the given wxPoint is within the bounds of this object. |
|||
* @param refPos A wxPoint to test, in Field coordinate system |
|||
* @return bool - true if a hit, else false |
|||
*/ |
|||
bool HitTest( const wxPoint& refPos ); |
|||
}; |
|||
|
|||
#endif // CLASS_LIBENTRY_FIELDS_H |
Write
Preview
Loading…
Cancel
Save
Reference in new issue