const wxString WORKSHEET_LAYOUT::MakeFullFileName( const wxString& aShortFileName,
                                                   const wxString& aProjectPath )
{
    wxString    fullFileName = ExpandEnvVarSubstitutions( aShortFileName );

    if( fullFileName.IsEmpty() )
        return fullFileName;

    wxFileName fn = fullFileName;

    if( fn.IsAbsolute() )
        return fullFileName;

    // the path is not absolute: search it in project path, and then in
    // kicad valid paths
    if( !aProjectPath.IsEmpty() )
    {
        fn.MakeAbsolute( aProjectPath );

        if( wxFileExists( fn.GetFullPath() ) )
            return fn.GetFullPath();
    }

    fn = fullFileName;
    wxString name = Kiface().KifaceSearch().FindValidPath( fn.GetFullName() );

    if( !name.IsEmpty() )
        fullFileName = name;

    return fullFileName;
}
void S3D_PLUGIN_MANAGER::checkPluginPath( const wxString& aPath,
    std::list< wxString >& aSearchList )
{
    // check the existence of a path and add it to the path search list
    if( aPath.empty() )
        return;

    #ifdef DEBUG
    wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] checking for 3D plugins in '%s'\n",
        aPath.ToUTF8() );
    #endif

    wxFileName path;

    if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
        path.Assign( ExpandEnvVarSubstitutions( aPath ), "" );
    else
        path.Assign( aPath, "" );

    path.Normalize();

    if( !wxFileName::DirExists( path.GetFullPath() ) )
        return;

    // determine if the directory is already in the list
    wxString wxpath = path.GetFullPath();
    std::list< wxString >::iterator bl = aSearchList.begin();
    std::list< wxString >::iterator el = aSearchList.end();

    while( bl != el )
    {
        if( 0 == (*bl).Cmp( wxpath ) )
            return;

        ++bl;
    }

    aSearchList.push_back( wxpath );

    return;
}
bool S3D_FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
{
    if( aConfigDir.empty() )
        return false;

    wxFileName cfgdir;

    if( aConfigDir.StartsWith( "${" ) || aConfigDir.StartsWith( "$(" ) )
        cfgdir.Assign( ExpandEnvVarSubstitutions( aConfigDir ), "" );
    else
        cfgdir.Assign( aConfigDir, "" );

    cfgdir.Normalize();

    if( false == cfgdir.DirExists() )
        return false;

    m_ConfigDir = cfgdir.GetPath();
    createPathList();

    return true;
}
void S3D_PLUGIN_MANAGER::checkPluginName( const wxString& aPath,
    std::list< wxString >& aPluginList )
{
    // check the existence of a plugin name and add it to the list

    if( aPath.empty() || !wxFileName::FileExists( aPath ) )
        return;

    wxFileName path;

    if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
        path.Assign( ExpandEnvVarSubstitutions( aPath ) );
    else
        path.Assign( aPath );

    path.Normalize();

    // determine if the path is already in the list
    wxString wxpath = path.GetFullPath();
    std::list< wxString >::iterator bl = aPluginList.begin();
    std::list< wxString >::iterator el = aPluginList.end();

    while( bl != el )
    {
        if( 0 == (*bl).Cmp( wxpath ) )
            return;

        ++bl;
    }

    aPluginList.push_back( wxpath );

    #ifdef DEBUG
    wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] found 3D plugin '%s'\n",
        wxpath.ToUTF8() );
    #endif

    return;
}
bool S3D_FILENAME_RESOLVER::SetProjectDir( const wxString& aProjDir, bool* flgChanged )
{
    if( aProjDir.empty() )
        return false;

    wxFileName projdir;

    if( aProjDir.StartsWith( "${" ) || aProjDir.StartsWith( "$(" ) )
        projdir.Assign( ExpandEnvVarSubstitutions( aProjDir ), "" );
    else
        projdir.Assign( aProjDir, "" );

    projdir.Normalize();

    if( false == projdir.DirExists() )
        return false;

    m_curProjDir = projdir.GetPath();

    if( flgChanged )
        *flgChanged = false;

    if( m_Paths.empty() )
    {
        S3D_ALIAS al;
        al.m_alias = "${KIPRJMOD}";
        al.m_pathvar = "${KIPRJMOD}";
        al.m_pathexp = m_curProjDir;
        m_Paths.push_back( al );

        if( flgChanged )
            *flgChanged = true;

    }
    else
    {
        if( m_Paths.front().m_pathexp.Cmp( m_curProjDir ) )
        {
            m_Paths.front().m_pathexp = m_curProjDir;

            if( flgChanged )
                *flgChanged = true;

        }
        else
        {
            return true;
        }
    }

#ifdef DEBUG
    do {
        std::ostringstream ostr;
        ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
        ostr << " * [INFO] changed project dir to ";
        ostr << m_Paths.front().m_pathexp.ToUTF8();
        wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
    } while( 0 );
#endif

    return true;
}
bool S3D_FILENAME_RESOLVER::addPath( const S3D_ALIAS& aPath )
{
    if( aPath.m_alias.empty() || aPath.m_pathvar.empty() )
        return false;

    wxCriticalSectionLocker lock( lock3D_resolver );

    S3D_ALIAS tpath = aPath;

    #ifdef _WIN32
    while( tpath.m_pathvar.EndsWith( wxT( "\\" ) ) )
        tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 );
    #else
    while( tpath.m_pathvar.EndsWith( wxT( "/" ) ) &&  tpath.m_pathvar.length() > 1 )
        tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 );
    #endif

    wxFileName path;

    if( tpath.m_pathvar.StartsWith( "${" ) || tpath.m_pathvar.StartsWith( "$(" ) )
        path.Assign( ExpandEnvVarSubstitutions( tpath.m_pathvar ), "" );
    else
        path.Assign( tpath.m_pathvar, "" );

    path.Normalize();

    if( !path.DirExists() )
    {
        // suppress the message if the missing pathvar is the
        // legacy KISYS3DMOD variable
        if( aPath.m_pathvar.compare( wxT( "${KISYS3DMOD}" ) ) )
        {
            wxString msg = _( "The given path does not exist" );
            msg.append( wxT( "\n" ) );
            msg.append( tpath.m_pathvar );
            wxMessageBox( msg, _( "3D model search path" ) );
        }

        tpath.m_pathexp.clear();
    }
    else
    {
        tpath.m_pathexp = path.GetFullPath();

        #ifdef _WIN32
        while( tpath.m_pathexp.EndsWith( wxT( "\\" ) ) )
        tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 );
        #else
        while( tpath.m_pathexp.EndsWith( wxT( "/" ) ) &&  tpath.m_pathexp.length() > 1 )
            tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 );
        #endif
    }

    wxString pname = path.GetPath();
    std::list< S3D_ALIAS >::iterator sPL = m_Paths.begin();
    std::list< S3D_ALIAS >::iterator ePL = m_Paths.end();

    while( sPL != ePL )
    {
        if( !tpath.m_alias.Cmp( sPL->m_alias ) )
        {
            wxString msg = _( "Alias: " );
            msg.append( tpath.m_alias );
            msg.append( wxT( "\n" ) );
            msg.append( _( "This path: " ) );
            msg.append( tpath.m_pathvar );
            msg.append( wxT( "\n" ) );
            msg.append( _( "Existing path: " ) );
            msg.append( sPL->m_pathvar );
            wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );

            return false;
        }

        ++sPL;
    }

    m_Paths.push_back( tpath );
    return true;
}
wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
{
    wxCriticalSectionLocker lock( lock3D_resolver );

    if( aFileName.empty() )
        return wxEmptyString;

    if( m_Paths.empty() )
        createPathList();

    // first attempt to use the name as specified:
    wxString tname = aFileName;

    #ifdef _WIN32
    // translate from KiCad's internal UNIX-like path to MSWin paths
    tname.Replace( wxT( "/" ), wxT( "\\" ) );
    #endif

    // Note: variable expansion must be performed using a threadsafe
    // wrapper for the getenv() system call. If we allow the
    // wxFileName::Normalize() routine to perform expansion then
    // we will have a race condition since wxWidgets does not assure
    // a threadsafe wrapper for getenv().
    if( tname.StartsWith( "${" ) || tname.StartsWith( "$(" ) )
        tname = ExpandEnvVarSubstitutions( tname );

    wxFileName tmpFN( tname );

    // in the case of absolute filenames we don't store a map item
    if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
        && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
    {
        tmpFN.Normalize();

        if( tmpFN.FileExists() )
            return tmpFN.GetFullPath();

        return wxEmptyString;
    }

    // this case covers full paths, leading expanded vars, and paths
    // relative to the current working directory (which is not necessarily
    // the current project directory)
    if( tmpFN.FileExists() )
    {
        tmpFN.Normalize();
        tname = tmpFN.GetFullPath();

        // special case: if a path begins with ${ENV_VAR} but is not in the
        // resolver's path list then add it.
        if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
            checkEnvVarPath( aFileName );

        return tname;
    }

    // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the
    // file either does not exist or the ENV_VAR is not defined
    if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
    {
        if( !( m_errflags & ERRFLG_ENVPATH ) )
        {
            m_errflags |= ERRFLG_ENVPATH;
            wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogMessage( errmsg );
        }

        return wxEmptyString;
    }

    // at this point aFileName is:
    // a. an aliased shortened name or
    // b. cannot be determined

    std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
    std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();

    // check the path relative to the current project directory;
    // note: this is not necessarily the same as the current working
    // directory, which has already been checked. This case accounts
    // for partial paths which do not contain ${KIPRJMOD}.
    // This check is performed before checking the path relative to
    // ${KISYS3DMOD} so that users can potentially override a model
    // within ${KISYS3DMOD}
    if( !sPL->m_pathexp.empty() && !tname.StartsWith( ":" ) )
    {
        tmpFN.Assign( sPL->m_pathexp, "" );
        wxString fullPath = tmpFN.GetPathWithSep() + tname;

        if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) )
            fullPath = ExpandEnvVarSubstitutions( fullPath );

        if( wxFileName::FileExists( fullPath ) )
        {
            tmpFN.Assign( fullPath );
            tmpFN.Normalize();
            tname = tmpFN.GetFullPath();
            return tname;
        }

    }

    // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
    if( !tname.StartsWith( ":" ) )
    {
        wxFileName fpath;
        wxString fullPath( "${KISYS3DMOD}" );
        fullPath.Append( fpath.GetPathSeparator() );
        fullPath.Append( tname );
        fullPath = ExpandEnvVarSubstitutions( fullPath );
        fpath.Assign( fullPath );

        if( fpath.Normalize() && fpath.FileExists() )
        {
            tname = fpath.GetFullPath();
            return tname;
        }

    }

    // ${ENV_VAR} paths have already been checked; skip them
    while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" )
        || sPL->m_alias.StartsWith( "$(" ) ) )
        ++sPL;

    // at this point the filename must contain an alias or else it is invalid
    wxString alias;         // the alias portion of the short filename
    wxString relpath;       // the path relative to the alias

    if( !SplitAlias( tname, alias, relpath ) )
    {
        if( !( m_errflags & ERRFLG_RELPATH ) )
        {
            // this can happen if the file was intended to be relative to
            // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect.
            m_errflags |= ERRFLG_RELPATH;
            wxString errmsg = "[3D File Resolver] No such path";
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogTrace( MASK_3D_RESOLVER, errmsg );
        }

        return wxEmptyString;
    }

    while( sPL != ePL )
    {
        if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() )
        {
            wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
            wxString fullPath = fpath.GetPathWithSep() + relpath;

            if( fullPath.StartsWith( "${") || fullPath.StartsWith( "$(" ) )
                fullPath = ExpandEnvVarSubstitutions( fullPath );

            if( wxFileName::FileExists( fullPath ) )
            {
                wxFileName tmp( fullPath );

                if( tmp.Normalize() )
                    tname = tmp.GetFullPath();

                return tname;
            }
        }

        ++sPL;
    }

    if( !( m_errflags & ERRFLG_ALIAS ) )
    {
        m_errflags |= ERRFLG_ALIAS;
        wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
        errmsg.append( "\n" );
        errmsg.append( tname.substr( 1 ) );
        wxLogTrace( MASK_3D_RESOLVER, errmsg );
    }

    return wxEmptyString;
}
bool S3D_FILENAME_RESOLVER::createPathList( void )
{
    if( !m_Paths.empty() )
        return true;

    wxString kmod;

    // add an entry for the default search path; at this point
    // we cannot set a sensible default so we use an empty string.
    // the user may change this later with a call to SetProjectDir()

    S3D_ALIAS lpath;
    lpath.m_alias = "${KIPRJMOD}";
    lpath.m_pathvar = "${KIPRJMOD}";
    lpath.m_pathexp = m_curProjDir;
    m_Paths.push_back( lpath );
    wxFileName fndummy;
    wxUniChar psep = fndummy.GetPathSeparator();
    std::list< wxString > epaths;

    if( GetKicadPaths( epaths ) )
    {
        for( auto i : epaths )
        {
            wxString pathVal = ExpandEnvVarSubstitutions( i );

            if( pathVal.empty() )
            {
                lpath.m_pathexp.clear();
            }
            else
            {
                fndummy.Assign( pathVal, "" );
                fndummy.Normalize();
                lpath.m_pathexp = fndummy.GetFullPath();
            }

            lpath.m_alias =  i;
            lpath.m_pathvar = i;

            if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
                lpath.m_pathexp.erase( --lpath.m_pathexp.end() );

            m_Paths.push_back( lpath );
        }
    }

    if( !m_ConfigDir.empty() )
        readPathList();

    if( m_Paths.empty() )
        return false;

#ifdef DEBUG
    wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
    std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
    std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();

    while( sPL != ePL )
    {
        wxLogTrace( MASK_3D_RESOLVER, "   + %s : '%s'\n", (*sPL).m_alias.GetData(),
            (*sPL).m_pathexp.GetData() );
        ++sPL;
    }
#endif

    return true;
}