From 5783623c501de92d4ad846038798ef1a3defaf5a Mon Sep 17 00:00:00 2001
From: Wayne Stambaugh <stambaughw@gmail.com>
Date: Sun, 16 Mar 2025 12:39:06 -0400
Subject: [PATCH] Add code to test if schematic file belongs to another
 project.

---
 eeschema/sch_screen.cpp               |  42 +++++++++
 eeschema/sch_screen.h                 |  21 ++++-
 qa/tests/eeschema/CMakeLists.txt      |   3 +-
 qa/tests/eeschema/test_sch_screen.cpp | 123 ++++++++++++++++++++++++++
 4 files changed, 187 insertions(+), 2 deletions(-)
 create mode 100644 qa/tests/eeschema/test_sch_screen.cpp

diff --git a/eeschema/sch_screen.cpp b/eeschema/sch_screen.cpp
index c1fda63329..be777478e1 100644
--- a/eeschema/sch_screen.cpp
+++ b/eeschema/sch_screen.cpp
@@ -1594,6 +1594,48 @@ std::set<wxString> SCH_SCREEN::GetSheetNames() const
 }
 
 
+bool SCH_SCREEN::HasInstanceDataFromOtherProjects() const
+{
+    wxCHECK( Schematic(), false );
+
+    SCH_SHEET_LIST hierarchy = Schematic()->Hierarchy();
+
+    for( const SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) )
+    {
+        const SCH_SYMBOL* symbol = static_cast<const SCH_SYMBOL*>( item );
+
+        const std::vector<SCH_SYMBOL_INSTANCE> symbolInstances = symbol->GetInstances();
+
+        for( const SCH_SYMBOL_INSTANCE& instance : symbolInstances )
+        {
+            if( !hierarchy.HasPath( instance.m_Path ) )
+                return true;
+        }
+    }
+
+    return false;
+}
+
+
+bool SCH_SCREEN::InProjectPath() const
+{
+    wxCHECK( Schematic() && !m_fileName.IsEmpty(), false );
+
+    wxFileName thisScreenFn( m_fileName );
+    wxFileName thisProjectFn( Schematic()->Prj().GetProjectFullName() );
+
+    wxCHECK( thisProjectFn.IsAbsolute(), false );
+
+    if( thisScreenFn.GetDirCount() < thisProjectFn.GetDirCount() )
+        return false;
+
+    while( thisProjectFn.GetDirCount() != thisScreenFn.GetDirCount() )
+        thisScreenFn.RemoveLastDir();
+
+    return thisScreenFn.GetPath() == thisProjectFn.GetPath();
+}
+
+
 #if defined(DEBUG)
 void SCH_SCREEN::Show( int nestLevel, std::ostream& os ) const
 {
diff --git a/eeschema/sch_screen.h b/eeschema/sch_screen.h
index 9281d868fb..ec14f778a0 100644
--- a/eeschema/sch_screen.h
+++ b/eeschema/sch_screen.h
@@ -65,6 +65,7 @@ class SCH_EDIT_FRAME;
 class SCH_SHEET_LIST;
 class SCH_IO_KICAD_SEXPR_PARSER;
 class SCH_IO_KICAD_SEXPR;
+class TEST_SCH_SCREEN_FIXTURE;
 
 enum SCH_LINE_TEST_T
 {
@@ -548,6 +549,14 @@ public:
 
     bool HasSymbolFieldNamesWithWhiteSpace() const;
 
+    /**
+     * Check if the schematic file is in the current project path.
+     *
+     * @retval true if the schematic file resides in the current project path or a sub-folder.
+     * @retval false if the schematic file does not reside within the current project path.
+     */
+    bool InProjectPath() const;
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
@@ -592,11 +601,22 @@ public:
      */
     std::set<wxString> GetSheetNames() const;
 
+    /**
+     * Check symbols for instance data from other projects.
+     *
+     * @retval true if the schematic contains symbols and/or sheets with instances from a
+     *         project other than the current project.
+     * @retval false if the schematic does not  contain symbols and/or sheets with instances
+     *         from a project other than the current project.
+     */
+    bool HasInstanceDataFromOtherProjects() const;
+
 private:
     friend SCH_EDIT_FRAME;     // Only to populate m_symbolInstances.
     friend SCH_IO_KICAD_SEXPR_PARSER;   // Only to load instance information from schematic file.
     friend SCH_IO_KICAD_SEXPR;   // Only to save the loaded instance information to schematic file.
     friend SCH_IO_ALTIUM;
+    friend TEST_SCH_SCREEN_FIXTURE;
 
     bool doIsJunction( const VECTOR2I& aPosition, bool aBreakCrossings,
                        bool* aHasExplicitJunctionDot, bool* aHasBusEntry ) const;
@@ -618,7 +638,6 @@ private:
      */
     size_t getLibSymbolNameMatches( const SCH_SYMBOL& aSymbol, std::vector<wxString>& aMatches );
 
-
     /**
      * Compare two #BUS_ALIAS objects by name.  For sorting in the set.
      */
diff --git a/qa/tests/eeschema/CMakeLists.txt b/qa/tests/eeschema/CMakeLists.txt
index fa4c3e145a..4128fd680f 100644
--- a/qa/tests/eeschema/CMakeLists.txt
+++ b/qa/tests/eeschema/CMakeLists.txt
@@ -76,6 +76,7 @@ set( QA_EESCHEMA_SRCS
     test_sch_pin.cpp
     test_sch_rtree.cpp
     test_sch_reference_list.cpp
+    test_sch_screen.cpp
     test_sch_sheet.cpp
     test_sch_sheet_path.cpp
     test_sch_sheet_list.cpp
@@ -125,4 +126,4 @@ target_compile_definitions( qa_eeschema
 
 kicad_add_boost_test( qa_eeschema qa_eeschema )
 
-setup_qa_env( qa_eeschema )
\ No newline at end of file
+setup_qa_env( qa_eeschema )
diff --git a/qa/tests/eeschema/test_sch_screen.cpp b/qa/tests/eeschema/test_sch_screen.cpp
new file mode 100644
index 0000000000..087e26a05f
--- /dev/null
+++ b/qa/tests/eeschema/test_sch_screen.cpp
@@ -0,0 +1,123 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file
+ * Test suite for SCH_SCREEN
+ */
+
+#include <qa_utils/wx_utils/unit_test_utils.h>
+#include "eeschema_test_utils.h"
+
+#include <schematic.h>
+#include <wildcards_and_files_ext.h>
+
+// Code under test
+#include <sch_screen.h>
+
+#include <qa_utils/uuid_test_utils.h>
+#include <qa_utils/wx_utils/wx_assert.h>
+
+class TEST_SCH_SCREEN_FIXTURE : public KI_TEST::SCHEMATIC_TEST_FIXTURE
+{
+protected:
+    wxFileName GetSchematicPath( const wxString& aRelativePath ) override;
+};
+
+
+wxFileName TEST_SCH_SCREEN_FIXTURE::GetSchematicPath( const wxString& aRelativePath )
+{
+    wxFileName fn( KI_TEST::GetEeschemaTestDataDir() );
+
+    wxString path = fn.GetFullPath();
+    path += aRelativePath + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
+
+    return wxFileName( path );
+}
+
+
+/**
+ * Declare the test suite
+ */
+BOOST_FIXTURE_TEST_SUITE( SchScreen, TEST_SCH_SCREEN_FIXTURE )
+
+
+/**
+ * Test SCH_SCREEN::InProjectPath().
+ */
+BOOST_AUTO_TEST_CASE( TestInProjectPath )
+{
+    LoadSchematic( "schematic_object_tests/not_shared_by_multiple_projects/"
+                   "not_shared_by_multiple_projects" );
+
+    SCH_SCREEN testScreen( &m_schematic );
+    wxFileName testFn( m_schematic.RootScreen()->GetFileName() );
+
+    // File is in same folder as project.
+    testFn.SetName( "test" );
+    testScreen.SetFileName( testFn.GetFullPath() );
+    BOOST_CHECK( testScreen.InProjectPath() );
+
+    // File is in a sub-folder inside project.
+    testFn.AppendDir( "sch" );
+    testScreen.SetFileName( testFn.GetFullPath() );
+    BOOST_CHECK( testScreen.InProjectPath() );
+
+    // File is one folder below poject folder.
+    testFn.RemoveLastDir();
+    testFn.RemoveLastDir();
+    testScreen.SetFileName( testFn.GetFullPath() );
+    BOOST_CHECK( !testScreen.InProjectPath() );
+
+    // File is in a completely different path with the same folder depth.
+    testFn.SetPath( "/home/foo/kicad" );
+
+    wxFileName projectFn( m_schematic.Prj().GetProjectFullName() );
+
+    // Just in case someone has a build path with no subfolders.
+    BOOST_CHECK( testFn.GetDirCount() < projectFn.GetDirCount() );
+
+    int subDirCount = 1;
+
+    while( projectFn.GetDirCount() != testFn.GetDirCount() )
+    {
+        testFn.AppendDir( wxString::Format( wxS( "subdir%d" ), subDirCount ) );
+        subDirCount += 1;
+    }
+
+    testScreen.SetFileName( testFn.GetFullPath() );
+    BOOST_CHECK( !testScreen.InProjectPath() );
+}
+
+
+/**
+ * Test SCH_SCREEN::HasInstanceDataFromOtherProjects().
+ */
+BOOST_AUTO_TEST_CASE( TestSharedByMultipleProjects )
+{
+    LoadSchematic( "schematic_object_tests/not_shared_by_multiple_projects/"
+                   "not_shared_by_multiple_projects" );
+
+    const SCH_SCREEN* rootScreen = m_schematic.RootScreen();
+    BOOST_CHECK( !rootScreen->HasInstanceDataFromOtherProjects() );
+    BOOST_CHECK( rootScreen->InProjectPath() );
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()