diff --git a/src/pya/unit_tests/pyaTests.cc b/src/pya/unit_tests/pyaTests.cc index c10a6d817..d527de3ab 100644 --- a/src/pya/unit_tests/pyaTests.cc +++ b/src/pya/unit_tests/pyaTests.cc @@ -106,6 +106,7 @@ PYTHONTEST (dbLayoutToNetlist, "dbLayoutToNetlist.py") PYTHONTEST (dbLayoutVsSchematic, "dbLayoutVsSchematic.py") PYTHONTEST (dbNetlistCrossReference, "dbNetlistCrossReference.py") PYTHONTEST (layLayers, "layLayers.py") +PYTHONTEST (layObjects, "layObjects.py") PYTHONTEST (layPixelBuffer, "layPixelBuffer.py") PYTHONTEST (tlTest, "tlTest.py") #if defined(HAVE_QT) && defined(HAVE_QTBINDINGS) diff --git a/src/tl/tl/tlEventsVar.h b/src/tl/tl/tlEventsVar.h index f51967c9b..4e9fb2be1 100644 --- a/src/tl/tl/tlEventsVar.h +++ b/src/tl/tl/tlEventsVar.h @@ -175,8 +175,26 @@ public: typedef typename receivers::iterator receivers_iterator; #endif + event<_TMPLARGLISTP> () + : mp_destroyed_sentinel (0) + { + // .. nothing yet .. + } + + ~event<_TMPLARGLISTP> () + { + if (mp_destroyed_sentinel) { + *mp_destroyed_sentinel = true; + } + mp_destroyed_sentinel = 0; + } + void operator() (_CALLARGLIST) { + bool was_destroyed = false; + bool *org_sentinel = mp_destroyed_sentinel; + mp_destroyed_sentinel = &was_destroyed; + // Issue the events. Because inside the call, other receivers might be added, we make a copy // first. This way added events won't be called now. receivers tmp_receivers = m_receivers; @@ -184,6 +202,10 @@ public: if (r->first.get ()) { try { r->second->call (_JOIN(r->first.get (), _CALLARGS)); + if (was_destroyed) { + // during the call something deleted us. Stop immediately. + return; + } } catch (tl::Exception &ex) { handle_event_exception (ex); } catch (std::exception &ex) { @@ -194,6 +216,8 @@ public: } } + mp_destroyed_sentinel = org_sentinel; + // Clean up expired entries afterwards (the call may have expired them) receivers_iterator w = m_receivers.begin (); for (receivers_iterator r = m_receivers.begin (); r != m_receivers.end (); ++r) { @@ -339,6 +363,7 @@ public: } private: + bool *mp_destroyed_sentinel; receivers m_receivers; }; diff --git a/testdata/python/layObjects.py b/testdata/python/layObjects.py new file mode 100644 index 000000000..ae4f5d5a3 --- /dev/null +++ b/testdata/python/layObjects.py @@ -0,0 +1,54 @@ +# KLayout Layout Viewer +# Copyright (C) 2006-2023 Matthias Koefferlein +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import pya +import unittest + +class LAYObjectsTests(unittest.TestCase): + + def test_1(self): + + class MyBrowserSource(pya.BrowserSource): + def get(self, url): + next_url = "int:" + str(int(url.split(":")[1]) + 1) + return f"This is {url}. Goto next ({next_url})" + + dialog = pya.BrowserDialog() + dialog.home = "int:0" + dialog.source = MyBrowserSource() + + dialog = pya.BrowserDialog() + dialog.home = "int:0" + dialog.source = MyBrowserSource() + + self.assertEqual(True, True) + + +# run unit tests +if __name__ == '__main__': + suite = unittest.TestSuite() + # NOTE: Use this instead of loadTestsfromTestCase to select a specific test: + # suite.addTest(BasicTest("test_26")) + suite = unittest.TestLoader().loadTestsFromTestCase(LAYObjectsTests) + + # Only runs with Application available + if "Application" in pya.__all__ and not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful(): + sys.exit(1) + + +