kicad-source/eeschema/dialogs/dialog_spice_model.cpp
Wayne Stambaugh 652b969193 GTK+ file dialog wildcard improvements.
The GTK+ file dialog is case sensitive however it does support regular
expressions.  Most of the file dialog wildcards are lower case so only
files with the lower case extensions will show up in the file dialog.
This code adds a method to convert file extensions of any case to the
appropriate (sch -> [sS][cC][hH]) regular expression on GTK+ builds so
all file extension case combinations will show up in the file dialog.

A note to developers: make sure to add a file extension when setting
when setting the default file argument.  If you do don't set an
extension, the GTK+ file dialog will happily append the regular
expression as the file extension which is surely not what you want.

There are still a few known places (mostly gerbview) where there are
some complex wildcard code that has not been converted.

Fixes lp:1720542

https://bugs.launchpad.net/kicad/+bug/1720542
2017-11-11 19:32:26 -05:00

793 lines
24 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016-2017 CERN
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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
*/
#include "wildcards_and_files_ext.h"
#include "dialog_spice_model.h"
#include <netlist_exporters/netlist_exporter_pspice.h>
#include <sim/spice_value.h>
#include <confirm.h>
#include <project.h>
#include <wx/tokenzr.h>
// Helper function to shorten conditions
static bool empty( const wxTextCtrl* aCtrl )
{
return aCtrl->GetValue().IsEmpty();
}
// Function to sort PWL values list
static int wxCALLBACK comparePwlValues( wxIntPtr aItem1, wxIntPtr aItem2, wxIntPtr WXUNUSED( aSortData ) )
{
float* t1 = reinterpret_cast<float*>( &aItem1 );
float* t2 = reinterpret_cast<float*>( &aItem2 );
if( *t1 > *t2 )
return 1;
if( *t1 < *t2 )
return -1;
return 0;
}
DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_COMPONENT& aComponent, SCH_FIELDS& aFields )
: DIALOG_SPICE_MODEL_BASE( aParent ), m_component( aComponent ), m_fields( aFields ),
m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
{
m_pasValue->SetValidator( m_spiceValidator );
m_modelType->SetValidator( m_notEmptyValidator );
m_modelName->SetValidator( m_notEmptyValidator );
m_genDc->SetValidator( m_spiceEmptyValidator );
m_genAcMag->SetValidator( m_spiceEmptyValidator );
m_genAcPhase->SetValidator( m_spiceEmptyValidator );
m_pulseInit->SetValidator( m_spiceEmptyValidator );
m_pulseNominal->SetValidator( m_spiceEmptyValidator );
m_pulseDelay->SetValidator( m_spiceEmptyValidator );
m_pulseRise->SetValidator( m_spiceEmptyValidator );
m_pulseFall->SetValidator( m_spiceEmptyValidator );
m_pulseWidth->SetValidator( m_spiceEmptyValidator );
m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
m_sinOffset->SetValidator( m_spiceEmptyValidator );
m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
m_sinFreq->SetValidator( m_spiceEmptyValidator );
m_sinDelay->SetValidator( m_spiceEmptyValidator );
m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
m_expInit->SetValidator( m_spiceEmptyValidator );
m_expPulsed->SetValidator( m_spiceEmptyValidator );
m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
m_expRiseConst->SetValidator( m_spiceEmptyValidator );
m_expFallDelay->SetValidator( m_spiceEmptyValidator );
m_expFallConst->SetValidator( m_spiceEmptyValidator );
m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
m_sdbSizerOK->SetDefault();
}
bool DIALOG_SPICE_MODEL::TransferDataFromWindow()
{
if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
return false;
wxWindow* page = m_notebook->GetCurrentPage();
// Passive
if( page == m_passive )
{
if( !m_passive->Validate() )
return false;
switch( m_pasType->GetSelection() )
{
case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
default:
wxASSERT_MSG( false, "Unhandled passive type" );
return false;
break;
}
m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
}
// Model
else if( page == m_model )
{
if( !m_model->Validate() )
return false;
switch( m_modelType->GetSelection() )
{
case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_SUBCKT; break;
case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_BJT; break;
case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_MOSFET; break;
case 3: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_DIODE; break;
default:
wxASSERT_MSG( false, "Unhandled semiconductor type" );
return false;
break;
}
m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
if( !empty( m_modelLibrary ) )
m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
}
// Power source
else if( page == m_power )
{
wxString model;
if( !generatePowerSource( model ) )
return false;
m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
m_fieldsTmp[SF_MODEL] = model;
}
else
{
wxASSERT_MSG( false, "Unhandled model type" );
return false;
}
m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
// Apply the settings
for( int i = 0; i < SF_END; ++i )
{
if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
{
getField( i ).SetText( m_fieldsTmp[i] );
}
else
{
// Erase empty fields (having empty fields causes a warning in the properties dialog)
const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) i );
auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
return f.GetName() == spiceField;
} );
if( fieldIt != m_fields.end() )
m_fields.erase( fieldIt );
}
}
return true;
}
bool DIALOG_SPICE_MODEL::TransferDataToWindow()
{
const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
// Fill out the working buffer
for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
{
const wxString& spiceField = spiceFields[idx];
auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
return f.GetName() == spiceField;
} );
// Do not modify the existing value, just add missing fields with default values
if( fieldIt != m_fields.end() && !fieldIt->GetText().IsEmpty() )
m_fieldsTmp[idx] = fieldIt->GetText();
else
m_fieldsTmp[idx] = NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( (SPICE_FIELD) idx, &m_component,
NET_ADJUST_INCLUDE_PATHS | NET_ADJUST_PASSIVE_VALS );
}
// Analyze the component fields to fill out the dialog
char primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
switch( primitive )
{
case SP_RESISTOR:
case SP_CAPACITOR:
case SP_INDUCTOR:
m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
: primitive == SP_CAPACITOR ? 1
: primitive == SP_INDUCTOR ? 2
: -1 );
m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
break;
case SP_SUBCKT:
case SP_DIODE:
case SP_BJT:
case SP_MOSFET:
m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
m_modelType->SetSelection( primitive == SP_SUBCKT ? 0
: primitive == SP_BJT ? 1
: primitive == SP_MOSFET ? 2
: primitive == SP_DIODE ? 3
: -1 );
m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
if( !empty( m_modelLibrary ) )
{
const wxString& libFile = m_modelLibrary->GetValue();
m_fieldsTmp[SF_LIB_FILE] = libFile;
loadLibrary( libFile );
}
break;
case SP_VSOURCE:
case SP_ISOURCE:
if( !parsePowerSource( m_fieldsTmp[SF_MODEL] ) )
return false;
m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
break;
default:
//wxASSERT_MSG( false, "Unhandled Spice primitive type" );
break;
}
m_disabled->SetValue( !NETLIST_EXPORTER_PSPICE::StringToBool( m_fieldsTmp[SF_ENABLED] ) );
// Check if node sequence is different than the default one
if( m_fieldsTmp[SF_NODE_SEQUENCE]
!= NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( SF_NODE_SEQUENCE, &m_component, 0 ) )
{
m_nodeSeqCheck->SetValue( true );
m_nodeSeqVal->SetValue( m_fieldsTmp[SF_NODE_SEQUENCE] );
}
return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
}
bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
{
if( aModel.IsEmpty() )
return false;
wxStringTokenizer tokenizer( aModel, " ()" );
wxString tkn = tokenizer.GetNextToken().Lower();
while( tokenizer.HasMoreTokens() )
{
// Variables used for generic values processing (filling out wxTextCtrls in sequence)
bool genericProcessing = false;
unsigned int genericReqParamsCount = 0;
std::vector<wxTextCtrl*> genericControls;
if( tkn == "dc" )
{
// There might be an optional "dc" or "trans" directive, skip it
if( tkn == "dc" || tkn == "trans" )
tkn = tokenizer.GetNextToken().Lower();
// DC value
try
{
m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
}
catch( ... )
{
return false;
}
}
else if( tkn == "ac" )
{
// AC magnitude
try
{
tkn = tokenizer.GetNextToken().Lower();
m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
}
catch( ... )
{
return false;
}
// AC phase (optional)
try
{
tkn = tokenizer.GetNextToken().Lower();
m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
}
catch( ... )
{
continue; // perhaps another directive
}
}
else if( tkn == "pulse" )
{
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
genericProcessing = true;
genericReqParamsCount = 2;
genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
}
else if( tkn == "sin" )
{
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
genericProcessing = true;
genericReqParamsCount = 2;
genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
}
else if( tkn == "exp" )
{
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
genericProcessing = true;
genericReqParamsCount = 2;
genericControls = { m_expInit, m_expPulsed,
m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
}
else if( tkn == "pwl" )
{
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
try
{
while( tokenizer.HasMoreTokens() )
{
tkn = tokenizer.GetNextToken();
SPICE_VALUE time( tkn );
tkn = tokenizer.GetNextToken();
SPICE_VALUE value( tkn );
addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
}
}
catch( ... )
{
return false;
}
}
else
{
// Unhandled power source type
wxASSERT_MSG( false, "Unhandled power source type" );
return false;
}
if( genericProcessing )
{
try
{
for( unsigned int i = 0; i < genericControls.size(); ++i )
{
// If there are no more tokens, let's check if we got at least required fields
if( !tokenizer.HasMoreTokens() )
return ( i >= genericReqParamsCount );
tkn = tokenizer.GetNextToken().Lower();
genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
}
}
catch( ... )
{
return false;
}
}
// Get the next token now, so if any of the branches catches an expection, try to
// process it in another branch
tkn = tokenizer.GetNextToken().Lower();
}
return true;
}
bool DIALOG_SPICE_MODEL::generatePowerSource( wxString& aTarget ) const
{
wxString acdc, trans;
wxWindow* page = m_powerNotebook->GetCurrentPage();
bool useTrans = true; // shall we use the transient command part?
// Variables for generic processing
bool genericProcessing = false;
unsigned int genericReqParamsCount = 0;
std::vector<wxTextCtrl*> genericControls;
/// DC / AC section
// If SPICE_VALUE can be properly constructed, then it is a valid value
try
{
if( !empty( m_genDc ) )
acdc += wxString::Format( "dc %s ", SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
}
catch( ... )
{
DisplayError( NULL, wxT( "Invalid DC value" ) );
return false;
}
try
{
if( !empty( m_genAcMag ) )
{
acdc += wxString::Format( "ac %s ", SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
if( !empty( m_genAcPhase ) )
acdc += wxString::Format( "%s ", SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
}
}
catch( ... )
{
DisplayError( NULL, wxT( "Invalid AC magnitude or phase" ) );
return false;
}
/// Transient section
if( page == m_pwrPulse )
{
if( !m_pwrPulse->Validate() )
return false;
genericProcessing = true;
trans += "pulse";
genericReqParamsCount = 2;
genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
}
else if( page == m_pwrSin )
{
if( !m_pwrSin->Validate() )
return false;
genericProcessing = true;
trans += "sin";
genericReqParamsCount = 2;
genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
}
else if( page == m_pwrExp )
{
if( !m_pwrExp->Validate() )
return false;
genericProcessing = true;
trans += "exp";
genericReqParamsCount = 2;
genericControls = { m_expInit, m_expPulsed,
m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
}
else if( page == m_pwrPwl )
{
if( m_pwlValList->GetItemCount() > 0 )
{
trans += "pwl(";
for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
{
trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
m_pwlValList->GetItemText( i, m_pwlValueCol ) );
}
trans.Trim();
trans += ")";
}
}
if( genericProcessing )
{
bool finished = false;
unsigned int paramCounter = 0;
trans += "(";
for( auto textCtrl : genericControls )
{
if( empty( textCtrl ) )
{
finished = true;
if( paramCounter < genericReqParamsCount )
{
if( paramCounter == 0 )
{
// It is fine, no parameters were entered
useTrans = false;
break;
}
DisplayError( NULL,
wxString::Format( wxT( "You need to specify at least the "
"first %d parameters for the transient source" ),
genericReqParamsCount ) );
return false;
}
}
else if( finished )
{
DisplayError( NULL, wxT( "You cannot leave interleaved blank "
"spaces for the transient source" ) );
return false;
}
trans += wxString::Format( "%s ", textCtrl->GetValue() );
++paramCounter;
}
trans.Trim();
trans += ")";
}
aTarget = acdc;
if( useTrans )
aTarget += trans;
aTarget.Trim( false );
aTarget.Trim( true );
return true;
}
void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
{
wxString curModel = m_modelName->GetValue();
m_models.clear();
wxFileName filePath( aFilePath );
bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
// Look for the file in the project path
if( !filePath.Exists() )
{
filePath.SetPath( Prj().GetProjectPath() + filePath.GetPath() );
if( !filePath.Exists() )
return;
}
// Display the library contents
m_libraryContents->LoadFile( filePath.GetFullPath() );
// Process the file, looking for components
for( int line_nr = 0; line_nr < m_libraryContents->GetNumberOfLines(); ++line_nr )
{
wxStringTokenizer tokenizer( m_libraryContents->GetLineText( line_nr ) );
while( tokenizer.HasMoreTokens() )
{
wxString token = tokenizer.GetNextToken().Lower();
// some subckts contain .model clauses inside,
// skip them as they are a part of the subckt, not another model
if( token == ".model" && !in_subckt )
{
wxString name = tokenizer.GetNextToken();
if( name.IsEmpty() )
break;
token = tokenizer.GetNextToken();
MODEL::TYPE type = MODEL::parseModelType( token );
if( type != MODEL::UNKNOWN )
m_models.emplace( name, MODEL( line_nr, type ) );
}
else if( token == ".subckt" )
{
wxASSERT( !in_subckt );
in_subckt = true;
wxString name = tokenizer.GetNextToken();
if( name.IsEmpty() )
break;
m_models.emplace( name, MODEL( line_nr, MODEL::SUBCKT ) );
}
else if( token == ".ends" )
{
wxASSERT( in_subckt );
in_subckt = false;
}
}
}
// Refresh the model name combobox values
m_modelName->Clear();
for( const auto& model : m_models )
m_modelName->Append( model.first );
// Restore the previous value or if there is none - pick the first one from the loaded library
if( !curModel.IsEmpty() )
m_modelName->SetValue( curModel );
else if( m_modelName->GetCount() > 0 )
m_modelName->SetSelection( 0 );
}
SCH_FIELD& DIALOG_SPICE_MODEL::getField( int aFieldType )
{
const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
return f.GetName() == spiceField;
} );
// Found one, so return it
if( fieldIt != m_fields.end() )
return *fieldIt;
// Create a new field with requested name
m_fields.emplace_back( wxPoint(), m_fields.size(), &m_component, spiceField );
return m_fields.back();
}
bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
{
// TODO execute validators
if( aTime.IsEmpty() || aValue.IsEmpty() )
return false;
long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
// There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
double timeD;
float timeF;
m_pwlTime->GetValue().ToDouble( &timeD );
timeF = timeD;
// Store the time value, so the entries can be sorted
m_pwlValList->SetItemData( idx, *reinterpret_cast<long*>( &timeF ) );
// Sort items by timestamp
m_pwlValList->SortItems( comparePwlValues, -1 );
return true;
}
void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
{
wxString searchPath = wxFileName( m_modelLibrary->GetValue() ).GetPath();
if( searchPath.IsEmpty() )
searchPath = Prj().GetProjectPath();
wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard;
wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
wxFD_OPEN | wxFD_FILE_MUST_EXIST );
if( openDlg.ShowModal() == wxID_CANCEL )
return;
wxFileName libPath( openDlg.GetPath() );
// Try to convert the path to relative to project
if( libPath.MakeRelativeTo( Prj().GetProjectPath() ) && !libPath.GetFullPath().StartsWith( ".." ) )
m_modelLibrary->SetValue( libPath.GetFullPath() );
else
m_modelLibrary->SetValue( openDlg.GetPath() );
loadLibrary( openDlg.GetPath() );
m_modelName->Popup();
}
void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
{
// autoselect the model type
auto it = m_models.find( m_modelName->GetValue() );
if( it != m_models.end() )
{
m_modelType->SetSelection( (int) it->second.model );
// scroll to the bottom, so the model definition is shown in the first line
m_libraryContents->ShowPosition(
m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
}
else
{
m_modelType->SetSelection( wxNOT_FOUND );
m_libraryContents->ShowPosition( 0 );
}
}
void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
{
addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
}
void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
{
long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
m_pwlValList->DeleteItem( idx );
}
DIALOG_SPICE_MODEL::MODEL::TYPE DIALOG_SPICE_MODEL::MODEL::parseModelType( const wxString& aValue )
{
if( aValue.IsEmpty() )
return UNKNOWN;
const wxString val( aValue.Lower() );
if( val.StartsWith( "npn" ) || val.StartsWith( "pnp" ) )
return BJT;
else if( val.StartsWith( "nmos" ) || val.StartsWith( "pmos" ) )
return MOSFET;
else if( val[0] == 'd' )
return DIODE;
return UNKNOWN;
}