From 2c2fbea94f86f35604327bd4ff171d37ebc2b0c8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 20 Aug 2023 19:15:08 +0200 Subject: [PATCH 01/25] Some useful aliases for RBA as in-place operators do not really do what they are supposed to in Ruby. --- src/db/db/gsiDeclDbEdgePairs.cc | 14 ++-- src/db/db/gsiDeclDbEdges.cc | 114 ++++++++++++++++++++++--------- src/db/db/gsiDeclDbRegion.cc | 51 ++++++++++---- src/db/db/gsiDeclDbTexts.cc | 15 ++-- testdata/ruby/dbEdgePairsTest.rb | 9 ++- testdata/ruby/dbEdgesTest.rb | 25 +++++++ testdata/ruby/dbRegionTest.rb | 20 ++++++ testdata/ruby/dbTextsTest.rb | 7 +- 8 files changed, 198 insertions(+), 57 deletions(-) diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 3f4c64032..0c44c95b1 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -425,7 +425,7 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "\n" "This method has been added in version 0.26." ) + - method ("+", &db::EdgePairs::operator+, gsi::arg ("other"), + method ("+|join", &db::EdgePairs::operator+, gsi::arg ("other"), "@brief Returns the combined edge pair collection of self and the other one\n" "\n" "@return The resulting edge pair collection\n" @@ -433,8 +433,9 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "This operator adds the edge pairs of the other collection to self and returns a new combined set.\n" "\n" "This method has been introduced in version 0.24.\n" - ) + - method ("+=", &db::EdgePairs::operator+=, gsi::arg ("other"), + "The 'join' alias has been introduced in version 0.28.12." + ) + + method ("+=|join_with", &db::EdgePairs::operator+=, gsi::arg ("other"), "@brief Adds the edge pairs of the other edge pair collection to self\n" "\n" "@return The edge pair collection after modification (self)\n" @@ -442,7 +443,12 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "This operator adds the edge pairs of the other collection to self.\n" "\n" "This method has been introduced in version 0.24.\n" - ) + + "\n" + "Note that in Ruby, the '+=' operator actually does not exist, but is emulated by '+' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'join_with' instead.\n" + "\n" + "The 'join_with' alias has been introduced in version 0.28.12." + ) + method_ext ("move", &move_p, gsi::arg ("p"), "@brief Moves the edge pair collection\n" "\n" diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index 9f68150f6..2994880b9 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -769,7 +769,7 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "Crossing edges are not merged.\n" "In contrast to \\merge, this method does not modify the edge collection but returns a merged copy.\n" ) + - method ("&", (db::Edges (db::Edges::*)(const db::Edges &) const) &db::Edges::operator&, gsi::arg ("other"), + method ("&|and", (db::Edges (db::Edges::*)(const db::Edges &) const) &db::Edges::operator&, gsi::arg ("other"), "@brief Returns the boolean AND between self and the other edge collection\n" "\n" "@return The result of the boolean AND operation\n" @@ -777,17 +777,24 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "The boolean AND operation will return all parts of the edges in this collection which " "are coincident with parts of the edges in the other collection." "The result will be a merged edge collection.\n" - ) + - method ("&=", (db::Edges &(db::Edges::*)(const db::Edges &)) &db::Edges::operator&=, gsi::arg ("other"), - "@brief Performs the boolean AND between self and the other edge collection\n" + "\n" + "The 'and' alias has been introduced in version 0.28.12." + ) + + method ("&=|and_with", (db::Edges &(db::Edges::*)(const db::Edges &)) &db::Edges::operator&=, gsi::arg ("other"), + "@brief Performs the boolean AND between self and the other edge collection in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" "The boolean AND operation will return all parts of the edges in this collection which " "are coincident with parts of the edges in the other collection." "The result will be a merged edge collection.\n" - ) + - method ("&", (db::Edges (db::Edges::*)(const db::Region &) const) &db::Edges::operator&, gsi::arg ("other"), + "\n" + "Note that in Ruby, the '&=' operator actually does not exist, but is emulated by '&' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'and_with' instead.\n" + "\n" + "The 'and_with' alias has been introduced in version 0.28.12." + ) + + method ("&|and", (db::Edges (db::Edges::*)(const db::Region &) const) &db::Edges::operator&, gsi::arg ("other"), "@brief Returns the parts of the edges inside the given region\n" "\n" "@return The edges inside the given region\n" @@ -798,9 +805,10 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "edges intersect.\n" "\n" "This method has been introduced in version 0.24." - ) + - method ("&=", (db::Edges &(db::Edges::*)(const db::Region &)) &db::Edges::operator&=, gsi::arg ("other"), - "@brief Selects the parts of the edges inside the given region\n" + "The 'and' alias has been introduced in version 0.28.12." + ) + + method ("&=|and_with", (db::Edges &(db::Edges::*)(const db::Region &)) &db::Edges::operator&=, gsi::arg ("other"), + "@brief Selects the parts of the edges inside the given region in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" @@ -810,8 +818,13 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "edges intersect.\n" "\n" "This method has been introduced in version 0.24." - ) + - method ("-", (db::Edges (db::Edges::*)(const db::Edges &) const) &db::Edges::operator-, gsi::arg ("other"), + "\n" + "Note that in Ruby, the '&=' operator actually does not exist, but is emulated by '&' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'and_with' instead.\n" + "\n" + "The 'and_with' alias has been introduced in version 0.28.12." + ) + + method ("-|not", (db::Edges (db::Edges::*)(const db::Edges &) const) &db::Edges::operator-, gsi::arg ("other"), "@brief Returns the boolean NOT between self and the other edge collection\n" "\n" "@return The result of the boolean NOT operation\n" @@ -819,17 +832,24 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "The boolean NOT operation will return all parts of the edges in this collection which " "are not coincident with parts of the edges in the other collection." "The result will be a merged edge collection.\n" - ) + - method ("-=", (db::Edges &(db::Edges::*)(const db::Edges &)) &db::Edges::operator-=, gsi::arg ("other"), - "@brief Performs the boolean NOT between self and the other edge collection\n" + "\n" + "The 'not' alias has been introduced in version 0.28.12." + ) + + method ("-=|not_with", (db::Edges &(db::Edges::*)(const db::Edges &)) &db::Edges::operator-=, gsi::arg ("other"), + "@brief Performs the boolean NOT between self and the other edge collection in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" "The boolean NOT operation will return all parts of the edges in this collection which " "are not coincident with parts of the edges in the other collection." "The result will be a merged edge collection.\n" - ) + - method ("-", (db::Edges (db::Edges::*)(const db::Region &) const) &db::Edges::operator-, gsi::arg ("other"), + "\n" + "Note that in Ruby, the '-=' operator actually does not exist, but is emulated by '-' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'not_with' instead.\n" + "\n" + "The 'not_with' alias has been introduced in version 0.28.12." + ) + + method ("-|not", (db::Edges (db::Edges::*)(const db::Region &) const) &db::Edges::operator-, gsi::arg ("other"), "@brief Returns the parts of the edges outside the given region\n" "\n" "@return The edges outside the given region\n" @@ -840,9 +860,10 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "edges intersect.\n" "\n" "This method has been introduced in version 0.24." - ) + - method ("-=", (db::Edges &(db::Edges::*)(const db::Region &)) &db::Edges::operator-=, gsi::arg ("other"), - "@brief Selects the parts of the edges outside the given region\n" + "The 'not' alias has been introduced in version 0.28.12." + ) + + method ("-=|not_with", (db::Edges &(db::Edges::*)(const db::Region &)) &db::Edges::operator-=, gsi::arg ("other"), + "@brief Selects the parts of the edges outside the given region in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" @@ -851,8 +872,12 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "As a side effect, the edges are made non-intersecting by introducing cut points where\n" "edges intersect.\n" "\n" + "Note that in Ruby, the '-=' operator actually does not exist, but is emulated by '-' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'not_with' instead.\n" + "\n" "This method has been introduced in version 0.24." - ) + + "The 'not_with' alias has been introduced in version 0.28.12." + ) + method_ext ("andnot", &andnot_with_edges, gsi::arg ("other"), "@brief Returns the boolean AND and NOT between self and the other edge set\n" "\n" @@ -873,7 +898,7 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "\n" "This method has been added in version 0.28.\n" ) + - method ("^", &db::Edges::operator^, gsi::arg ("other"), + method ("^|xor", &db::Edges::operator^, gsi::arg ("other"), "@brief Returns the boolean XOR between self and the other edge collection\n" "\n" "@return The result of the boolean XOR operation\n" @@ -881,48 +906,69 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "The boolean XOR operation will return all parts of the edges in this and the other collection except " "the parts where both are coincident.\n" "The result will be a merged edge collection.\n" - ) + - method ("^=", &db::Edges::operator^=, gsi::arg ("other"), - "@brief Performs the boolean XOR between self and the other edge collection\n" + "\n" + "The 'xor' alias has been introduced in version 0.28.12." + ) + + method ("^=|xor_with", &db::Edges::operator^=, gsi::arg ("other"), + "@brief Performs the boolean XOR between self and the other edge collection in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" "The boolean XOR operation will return all parts of the edges in this and the other collection except " "the parts where both are coincident.\n" "The result will be a merged edge collection.\n" - ) + - method ("\\|", &db::Edges::operator|, gsi::arg ("other"), + "\n" + "Note that in Ruby, the '^=' operator actually does not exist, but is emulated by '^' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'xor_with' instead.\n" + "\n" + "The 'xor_with' alias has been introduced in version 0.28.12." + ) + + method ("\\||or", &db::Edges::operator|, gsi::arg ("other"), "@brief Returns the boolean OR between self and the other edge set\n" "\n" "@return The resulting edge collection\n" "\n" "The boolean OR is implemented by merging the edges of both edge sets. To simply join the edge collections " "without merging, the + operator is more efficient." - ) + - method ("\\|=", &db::Edges::operator|=, gsi::arg ("other"), - "@brief Performs the boolean OR between self and the other edge set\n" + "\n" + "The 'or' alias has been introduced in version 0.28.12." + ) + + method ("\\|=|or_with", &db::Edges::operator|=, gsi::arg ("other"), + "@brief Performs the boolean OR between self and the other edge set in-place (modifying self)\n" "\n" "@return The edge collection after modification (self)\n" "\n" "The boolean OR is implemented by merging the edges of both edge sets. To simply join the edge collections " "without merging, the + operator is more efficient." - ) + - method ("+", &db::Edges::operator+, gsi::arg ("other"), + "\n" + "Note that in Ruby, the '|=' operator actually does not exist, but is emulated by '|' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'or_with' instead.\n" + "\n" + "The 'or_with' alias has been introduced in version 0.28.12." + ) + + method ("+|join", &db::Edges::operator+, gsi::arg ("other"), "@brief Returns the combined edge set of self and the other one\n" "\n" "@return The resulting edge set\n" "\n" "This operator adds the edges of the other edge set to self and returns a new combined edge set. " "This usually creates unmerged edge sets and edges may overlap. Use \\merge if you want to ensure the result edge set is merged.\n" - ) + - method ("+=", &db::Edges::operator+=, gsi::arg ("other"), + "\n" + "The 'join' alias has been introduced in version 0.28.12." + ) + + method ("+=|join_with", &db::Edges::operator+=, gsi::arg ("other"), "@brief Adds the edges of the other edge collection to self\n" "\n" "@return The edge set after modification (self)\n" "\n" "This operator adds the edges of the other edge set to self. " "This usually creates unmerged edge sets and edges may overlap. Use \\merge if you want to ensure the result edge set is merged.\n" - ) + + "\n" + "Note that in Ruby, the '+=' operator actually does not exist, but is emulated by '+' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'join_with' instead.\n" + "\n" + "The 'join_with' alias has been introduced in version 0.28.12." + ) + method ("interacting", (db::Edges (db::Edges::*) (const db::Edges &) const) &db::Edges::selected_interacting, gsi::arg ("other"), "@brief Returns the edges of this edge collection which overlap or touch edges from the other edge collection\n" "\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index b8c2813ea..f75f5ab5a 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1647,15 +1647,18 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "This variant has been introduced in version 0.28.4." ) + method ("&=", &db::Region::operator&=, gsi::arg ("other"), - "@brief Performs the boolean AND between self and the other region\n" + "@brief Performs the boolean AND between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" "This method will compute the boolean AND (intersection) between two regions. " "The result is often but not necessarily always merged.\n" + "\n" + "Note that in Ruby, the '&=' operator actually does not exist, but is emulated by '&' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'and_with' instead." ) + method ("and_with", &db::Region::bool_and_with, gsi::arg ("other"), gsi::arg ("property_constraint", db::IgnoreProperties, "IgnoreProperties"), - "@brief Performs the boolean AND between self and the other region\n" + "@brief Performs the boolean AND between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" @@ -1687,15 +1690,18 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "This variant has been introduced in version 0.28.4." ) + method ("-=", &db::Region::operator-=, gsi::arg ("other"), - "@brief Performs the boolean NOT between self and the other region\n" + "@brief Performs the boolean NOT between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" "This method will compute the boolean NOT (intersection) between two regions. " "The result is often but not necessarily always merged.\n" + "\n" + "Note that in Ruby, the '-=' operator actually does not exist, but is emulated by '-' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'not_with' instead." ) + method ("not_with", &db::Region::bool_not_with, gsi::arg ("other"), gsi::arg ("property_constraint", db::IgnoreProperties, "IgnoreProperties"), - "@brief Performs the boolean NOT between self and the other region\n" + "@brief Performs the boolean NOT between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" @@ -1706,53 +1712,74 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This variant has been introduced in version 0.28.4." ) + - method ("^", &db::Region::operator^, gsi::arg ("other"), + method ("^|xor", &db::Region::operator^, gsi::arg ("other"), "@brief Returns the boolean XOR between self and the other region\n" "\n" "@return The result of the boolean XOR operation\n" "\n" "This method will compute the boolean XOR (intersection) between two regions. " "The result is often but not necessarily always merged.\n" + "\n" + "The 'xor' alias has been introduced in version 0.28.12." ) + - method ("^=", &db::Region::operator^=, gsi::arg ("other"), - "@brief Performs the boolean XOR between self and the other region\n" + method ("^=|xor_with", &db::Region::operator^=, gsi::arg ("other"), + "@brief Performs the boolean XOR between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" "This method will compute the boolean XOR (intersection) between two regions. " "The result is often but not necessarily always merged.\n" + "\n" + "Note that in Ruby, the '^=' operator actually does not exist, but is emulated by '^' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'xor_with' instead.\n" + "\n" + "The 'xor_with' alias has been introduced in version 0.28.12." ) + - method ("\\|", &db::Region::operator|, gsi::arg ("other"), + method ("\\||or", &db::Region::operator|, gsi::arg ("other"), "@brief Returns the boolean OR between self and the other region\n" "\n" "@return The resulting region\n" "\n" "The boolean OR is implemented by merging the polygons of both regions. To simply join the regions " "without merging, the + operator is more efficient." + "\n" + "The 'or' alias has been introduced in version 0.28.12." ) + - method ("\\|=", &db::Region::operator|=, gsi::arg ("other"), - "@brief Performs the boolean OR between self and the other region\n" + method ("\\|=|or_with", &db::Region::operator|=, gsi::arg ("other"), + "@brief Performs the boolean OR between self and the other region in-place (modifying self)\n" "\n" "@return The region after modification (self)\n" "\n" "The boolean OR is implemented by merging the polygons of both regions. To simply join the regions " "without merging, the + operator is more efficient." + "\n" + "Note that in Ruby, the '|=' operator actually does not exist, but is emulated by '|' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'or_with' instead.\n" + "\n" + "The 'or_with' alias has been introduced in version 0.28.12." ) + - method ("+", &db::Region::operator+, gsi::arg ("other"), + method ("+|join", &db::Region::operator+, gsi::arg ("other"), "@brief Returns the combined region of self and the other region\n" "\n" "@return The resulting region\n" "\n" "This operator adds the polygons of the other region to self and returns a new combined region. " "This usually creates unmerged regions and polygons may overlap. Use \\merge if you want to ensure the result region is merged.\n" + "\n" + "The 'join' alias has been introduced in version 0.28.12." ) + - method ("+=", &db::Region::operator+=, gsi::arg ("other"), + method ("+=|join_with", &db::Region::operator+=, gsi::arg ("other"), "@brief Adds the polygons of the other region to self\n" "\n" "@return The region after modification (self)\n" "\n" "This operator adds the polygons of the other region to self. " "This usually creates unmerged regions and polygons may overlap. Use \\merge if you want to ensure the result region is merged.\n" + "\n" + "Note that in Ruby, the '+=' operator actually does not exist, but is emulated by '+' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'join_with' instead.\n" + "\n" + "The 'join_with' alias has been introduced in version 0.28.12." ) + method ("covering", &db::Region::selected_enclosing, gsi::arg ("other"), gsi::arg ("min_count", size_t (1)), gsi::arg ("max_count", size_t (std::numeric_limits::max ()), "unlimited"), "@brief Returns the polygons of this region which are completely covering polygons from the other region\n" diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 7a4801bf2..c3cf07812 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -276,20 +276,27 @@ Class decl_Texts (decl_dbShapeCollection, "db", "Texts", method_ext ("data_id", &id, "@brief Returns the data ID (a unique identifier for the underlying data storage)\n" ) + - method ("+", &db::Texts::operator+, gsi::arg ("other"), + method ("+|join", &db::Texts::operator+, gsi::arg ("other"), "@brief Returns the combined text collection of self and the other one\n" "\n" "@return The resulting text collection\n" "\n" "This operator adds the texts of the other collection to self and returns a new combined set.\n" - ) + - method ("+=", &db::Texts::operator+=, gsi::arg ("other"), + "\n" + "The 'join' alias has been introduced in version 0.28.12." + ) + + method ("+=|join_with", &db::Texts::operator+=, gsi::arg ("other"), "@brief Adds the texts of the other text collection to self\n" "\n" "@return The text collection after modification (self)\n" "\n" "This operator adds the texts of the other collection to self.\n" - ) + + "\n" + "Note that in Ruby, the '+=' operator actually does not exist, but is emulated by '+' followed by an assignment. " + "This is less efficient than the in-place operation, so it is recommended to use 'join_with' instead.\n" + "\n" + "The 'join_with' alias has been introduced in version 0.28.12." + ) + method_ext ("move", &move_p, gsi::arg ("p"), "@brief Moves the text collection\n" "\n" diff --git a/testdata/ruby/dbEdgePairsTest.rb b/testdata/ruby/dbEdgePairsTest.rb index bb8af461d..a2097bfc0 100644 --- a/testdata/ruby/dbEdgePairsTest.rb +++ b/testdata/ruby/dbEdgePairsTest.rb @@ -113,8 +113,13 @@ class DBEdgePairs_TestClass < TestBase r2.insert(RBA::Edge::new(1, 1, 1, 101), RBA::Edge::new(-11, 1, -21, 51)) assert_equal(csort((r1 + r2).to_s), csort("(0,0;0,100)/(-10,0;-20,50);(0,1;0,101)/(-10,1;-20,51);(1,0;1,100)/(-11,0;-21,50);(1,1;1,101)/(-11,1;-21,51)")) - r1 += r2 - assert_equal(csort(r1.to_s), csort("(0,0;0,100)/(-10,0;-20,50);(0,1;0,101)/(-10,1;-20,51);(1,0;1,100)/(-11,0;-21,50);(1,1;1,101)/(-11,1;-21,51)")) + assert_equal(csort((r1.join(r2)).to_s), csort("(0,0;0,100)/(-10,0;-20,50);(0,1;0,101)/(-10,1;-20,51);(1,0;1,100)/(-11,0;-21,50);(1,1;1,101)/(-11,1;-21,51)")) + rr1 = r1.dup + rr1 += r2 + assert_equal(csort(rr1.to_s), csort("(0,0;0,100)/(-10,0;-20,50);(0,1;0,101)/(-10,1;-20,51);(1,0;1,100)/(-11,0;-21,50);(1,1;1,101)/(-11,1;-21,51)")) + rr1 = r1.dup + rr1.join_with(r2) + assert_equal(csort(rr1.to_s), csort("(0,0;0,100)/(-10,0;-20,50);(0,1;0,101)/(-10,1;-20,51);(1,0;1,100)/(-11,0;-21,50);(1,1;1,101)/(-11,1;-21,51)")) end diff --git a/testdata/ruby/dbEdgesTest.rb b/testdata/ruby/dbEdgesTest.rb index 68126edb3..bd4817fa9 100644 --- a/testdata/ruby/dbEdgesTest.rb +++ b/testdata/ruby/dbEdgesTest.rb @@ -315,6 +315,8 @@ class DBEdges_TestClass < TestBase r = r1 + r2 assert_equal(csort(r.to_s), csort("(0,0;100,0);(50,0;200,0)")) + r = r1.join(r2) + assert_equal(csort(r.to_s), csort("(0,0;100,0);(50,0;200,0)")) assert_equal(r.merged.to_s, "(0,0;200,0)") r.merge assert_equal(r.is_merged?, true) @@ -322,9 +324,14 @@ class DBEdges_TestClass < TestBase r = r1.dup r += r2 assert_equal(csort(r.to_s), csort("(0,0;100,0);(50,0;200,0)")) + r = r1.dup + r.join_with(r2) + assert_equal(csort(r.to_s), csort("(0,0;100,0);(50,0;200,0)")) r = r1 | r2 assert_equal(r.to_s, "(0,0;200,0)") + r = r1.or(r2) + assert_equal(r.to_s, "(0,0;200,0)") assert_equal(r.merged.to_s, "(0,0;200,0)") r.merge assert_equal(r.is_merged?, true) @@ -332,9 +339,14 @@ class DBEdges_TestClass < TestBase r = r1.dup r |= r2 assert_equal(r.to_s, "(0,0;200,0)") + r = r1.dup + r.or_with(r2) + assert_equal(r.to_s, "(0,0;200,0)") r = r1 & r2 assert_equal(r.to_s, "(50,0;100,0)") + r = r1.and(r2) + assert_equal(r.to_s, "(50,0;100,0)") assert_equal(r.is_merged?, true) r = r1.andnot(r2)[0] assert_equal(r.to_s, "(50,0;100,0)") @@ -342,9 +354,14 @@ class DBEdges_TestClass < TestBase r = r1.dup r &= r2 assert_equal(r.to_s, "(50,0;100,0)") + r = r1.dup + r.and_with(r2) + assert_equal(r.to_s, "(50,0;100,0)") r = r1 - r2 assert_equal(r.to_s, "(0,0;50,0)") + r = r1.not(r2) + assert_equal(r.to_s, "(0,0;50,0)") assert_equal(r.is_merged?, true) r = r1.andnot(r2)[1] assert_equal(r.to_s, "(0,0;50,0)") @@ -352,13 +369,21 @@ class DBEdges_TestClass < TestBase r = r1.dup r -= r2 assert_equal(r.to_s, "(0,0;50,0)") + r = r1.dup + r.not_with(r2) + assert_equal(r.to_s, "(0,0;50,0)") r = r1 ^ r2 assert_equal(csort(r.to_s), csort("(0,0;50,0);(100,0;200,0)")) + r = r1.xor(r2) + assert_equal(csort(r.to_s), csort("(0,0;50,0);(100,0;200,0)")) assert_equal(r.is_merged?, true) r = r1.dup r ^= r2 assert_equal(csort(r.to_s), csort("(0,0;50,0);(100,0;200,0)")) + r = r1.dup + r.xor_with(r2) + assert_equal(csort(r.to_s), csort("(0,0;50,0);(100,0;200,0)")) end diff --git a/testdata/ruby/dbRegionTest.rb b/testdata/ruby/dbRegionTest.rb index 71c0df2cd..173abc683 100644 --- a/testdata/ruby/dbRegionTest.rb +++ b/testdata/ruby/dbRegionTest.rb @@ -274,34 +274,54 @@ class DBRegion_TestClass < TestBase r2 = RBA::Region::new(RBA::Box::new(-10, -20, 80, 160)) assert_equal((r1 & r2).to_s, "(10,20;10,160;80,160;80,20)") + assert_equal((r1.and(r2)).to_s, "(10,20;10,160;80,160;80,20)") assert_equal(r1.andnot(r2).count, 2) assert_equal(r1.andnot(r2)[0].to_s, "(10,20;10,160;80,160;80,20)") rr = r1.dup rr &= r2 assert_equal(rr.to_s, "(10,20;10,160;80,160;80,20)") + rr = r1.dup + rr.and_with(r2) + assert_equal(rr.to_s, "(10,20;10,160;80,160;80,20)") assert_equal((r1 - r2).to_s, "(80,20;80,160;10,160;10,200;100,200;100,20)") + assert_equal((r1.not(r2)).to_s, "(80,20;80,160;10,160;10,200;100,200;100,20)") assert_equal(r1.andnot(r2)[1].to_s, "(80,20;80,160;10,160;10,200;100,200;100,20)") rr = r1.dup rr -= r2 assert_equal(rr.to_s, "(80,20;80,160;10,160;10,200;100,200;100,20)") + rr = r1.dup + rr.not_with(r2) + assert_equal(rr.to_s, "(80,20;80,160;10,160;10,200;100,200;100,20)") assert_equal((r1 ^ r2).to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20/10,20;80,20;80,160;10,160)") + assert_equal((r1.xor(r2)).to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20/10,20;80,20;80,160;10,160)") r1.min_coherence = true assert_equal(csort((r1 ^ r2).to_s), csort("(-10,-20;-10,160;10,160;10,20;80,20;80,-20);(80,20;80,160;10,160;10,200;100,200;100,20)")) rr = r1.dup rr ^= r2 assert_equal(csort(rr.to_s), csort("(-10,-20;-10,160;10,160;10,20;80,20;80,-20);(80,20;80,160;10,160;10,200;100,200;100,20)")) + rr = r1.dup + rr.xor_with(r2) + assert_equal(csort(rr.to_s), csort("(-10,-20;-10,160;10,160;10,20;80,20;80,-20);(80,20;80,160;10,160;10,200;100,200;100,20)")) assert_equal(csort((r1 + r2).to_s), csort("(10,20;10,200;100,200;100,20);(-10,-20;-10,160;80,160;80,-20)")) + assert_equal(csort((r1.join(r2)).to_s), csort("(10,20;10,200;100,200;100,20);(-10,-20;-10,160;80,160;80,-20)")) rr = r1.dup rr += r2 assert_equal(csort(rr.to_s), csort("(10,20;10,200;100,200;100,20);(-10,-20;-10,160;80,160;80,-20)")) + rr = r1.dup + rr.join_with(r2) + assert_equal(csort(rr.to_s), csort("(10,20;10,200;100,200;100,20);(-10,-20;-10,160;80,160;80,-20)")) assert_equal((r1 | r2).to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20)") + assert_equal((r1.or(r2)).to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20)") rr = r1.dup rr |= r2 assert_equal(rr.to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20)") + rr = r1.dup + rr.or_with(r2) + assert_equal(rr.to_s, "(-10,-20;-10,160;10,160;10,200;100,200;100,20;80,20;80,-20)") assert_equal((r1 + r2).sized(10).to_s, "(-20,-30;-20,170;0,170;0,210;110,210;110,10;90,10;90,-30)") rr = (r1 | r2).dup diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 6df06a71a..c313c8223 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -114,7 +114,12 @@ class DBTexts_TestClass < TestBase r1.insert(RBA::Text::new("uvm", RBA::Trans::new(RBA::Vector::new(111, 211)))) assert_equal(csort((r1 + r2).to_s), csort("('abc',r0 100,-200);('uvm',r0 110,210);('abc',r0 101,-201);('uvm',r0 111,211)")) - r1 += r2 + assert_equal(csort((r1.join(r2)).to_s), csort("('abc',r0 100,-200);('uvm',r0 110,210);('abc',r0 101,-201);('uvm',r0 111,211)")) + rr1 = r1.dup + rr1 += r2 + assert_equal(csort(r1.to_s), csort("('abc',r0 100,-200);('uvm',r0 110,210);('abc',r0 101,-201);('uvm',r0 111,211)")) + rr1 = r1.dup + rr1.join_with(r2) assert_equal(csort(r1.to_s), csort("('abc',r0 100,-200);('uvm',r0 110,210);('abc',r0 101,-201);('uvm',r0 111,211)")) end From 06321cdea7bd9775ed7c1a258cba67137cd204ad Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 24 Aug 2023 22:55:31 +0200 Subject: [PATCH 02/25] Bugfix: deep region 'transform' with plain shift was not working properly with scaled instances --- src/db/db/dbDeepRegion.cc | 4 +- src/db/unit_tests/dbDeepRegionTests.cc | 31 +++ src/lay/lay/TechMacrosPage.ui | 187 +++++++++--------- src/lay/lay/layMacroEditorDialog.cc | 2 +- src/lym/lym/lymMacroCollection.cc | 6 +- src/lym/lym/lymMacroCollection.h | 2 +- .../deep_region_transform_with_scaled.gds | Bin 0 -> 352 bytes .../deep_region_transform_with_scaled_au.gds | Bin 0 -> 352 bytes 8 files changed, 126 insertions(+), 106 deletions(-) create mode 100644 testdata/algo/deep_region_transform_with_scaled.gds create mode 100644 testdata/algo/deep_region_transform_with_scaled_au.gds diff --git a/src/db/db/dbDeepRegion.cc b/src/db/db/dbDeepRegion.cc index 1ab9d322e..6c2e2fe53 100644 --- a/src/db/db/dbDeepRegion.cc +++ b/src/db/db/dbDeepRegion.cc @@ -252,8 +252,8 @@ static void transform_deep_layer (db::DeepLayer &deep_layer, const Trans &t) // Plain move - // build cell variants for different orientations - db::OrientationReducer same_orientation; + // build cell variants for different orientations and magnifications + db::MagnificationAndOrientationReducer same_orientation; db::VariantsCollectorBase vars (&same_orientation); vars.collect (deep_layer.layout (), deep_layer.initial_cell ()); diff --git a/src/db/unit_tests/dbDeepRegionTests.cc b/src/db/unit_tests/dbDeepRegionTests.cc index 45e449621..5bdeaadd3 100644 --- a/src/db/unit_tests/dbDeepRegionTests.cc +++ b/src/db/unit_tests/dbDeepRegionTests.cc @@ -2779,6 +2779,37 @@ TEST(issue_400_with_region) db::compare_layouts (_this, ly, tl::testdata () + "/algo/deep_region_au400c.gds"); } +TEST(deep_region_transform_with_scaled) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/deep_region_transform_with_scaled.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type top_cell_index = *ly.begin_top_down (); + db::Cell &top_cell = ly.cell (top_cell_index); + + db::DeepShapeStore dss; + + for (db::Layout::layer_iterator l = ly.begin_layers (); l != ly.end_layers (); ++l) { + + unsigned int li = (*l).first; + db::Region r (db::RecursiveShapeIterator (ly, top_cell, li), dss); + r.transform (db::Trans (db::Vector (10000, 0))); + + ly.clear_layer (li); + r.insert_into (&ly, top_cell_index, li); + + } + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/deep_region_transform_with_scaled_au.gds"); +} + TEST(issue_663_separation_from_inside) { db::Layout ly; diff --git a/src/lay/lay/TechMacrosPage.ui b/src/lay/lay/TechMacrosPage.ui index aa4143759..309c02894 100644 --- a/src/lay/lay/TechMacrosPage.ui +++ b/src/lay/lay/TechMacrosPage.ui @@ -1,7 +1,8 @@ - + + TechMacrosPage - - + + 0 0 @@ -9,94 +10,86 @@ 492 - + Form - - - 9 - - + + 6 + + 9 + - - - - 5 - 0 + + + 0 0 - + %CAT_DESC% - - - - 5 - 5 + + + 0 1 - + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + Qt::Horizontal - - - - 5 - 7 + + + 0 0 - - - - 5 - 5 + + + 3 0 - + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + QTextEdit::NoWrap @@ -109,70 +102,68 @@ - - - <html><body><span style=" font-weight:600;">Note</span>: to edit, add or delete scripts, use the macro development facility in "Tools/Macro Development" or "Tools/Verification/DRC/Edit DRC Scripts". You will find a branch for this technology there.</p></body></html> + + + <html><body><span style=" font-weight:600;">Note</span>: to edit, add or delete scripts, use the macro development facility in "Tools/Macro Development". You will find a branch for this technology there.</p></body></html> - + true - - - - 5 - 5 + + + 0 0 - - <html><body><span style=" font-weight:600;">Note</span>: this page is empty because no valid base path is set.<br/>Select a base path on the "General" page to establish one.</p></body></html> + + <html><body><span style=" font-weight:600;">Note</span>: this page is empty because no valid base path is set.<br/>Select a base path on the "General" page to establish one.</p></body></html> - + Qt::AlignCenter - + true - - + + 16 200 - + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + 6 - - - + + + Create Folder Now - + - + Qt::Horizontal - + 211 20 @@ -180,12 +171,12 @@ - + - + Qt::Horizontal - + 231 20 @@ -193,12 +184,12 @@ - + - + Qt::Vertical - + 651 40 @@ -206,12 +197,12 @@ - + - + Qt::Vertical - + 671 40 @@ -219,23 +210,21 @@ - - - - - 5 - 5 + + + + 0 0 - - <html><body><span style=" font-weight:600;">Note</span>: this page is empty, because the corresponding macro folder</p><p style=" margin-top:5px; margin-bottom:5px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><i>%BASE_PATH%/%CAT%</i></p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">cannot be found or is not accessible.</p></body></html> + + <html><body><span style=" font-weight:600;">Note</span>: this page is empty, because the corresponding macro folder</p><p style=" margin-top:5px; margin-bottom:5px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><i>%BASE_PATH%/%CAT%</i></p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">cannot be found or is not accessible.</p></body></html> - + Qt::AlignCenter - + true @@ -244,15 +233,15 @@ - - - <html><body> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span>: this page is empty, because the macro location is shared by a different macro site (specifically "<i>%ALT_DESC%</i>"). Such a site cannot be made technology specific. Choose a base path that points to a different location.</p></body></html> + + + <html><body> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span>: this page is empty, because the macro location is shared by a different macro site (specifically "<i>%ALT_DESC%</i>"). Such a site cannot be made technology specific. Choose a base path that points to a different location.</p></body></html> - + Qt::AlignCenter - + true diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index bf8c9ed03..1d0a53711 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -2790,7 +2790,7 @@ BEGIN_PROTECTED std::string new_path = tl::to_string (QFileInfo (new_dir).absoluteFilePath ()); paths.push_back (std::make_pair (new_path, cat)); - lym::MacroCollection *c = mp_root->add_folder (tl::to_string (QObject::tr ("Project")) + " - " + new_path, new_path, cat, false); + lym::MacroCollection *c = mp_root->add_folder (tl::to_string (QObject::tr ("Project")) + " - " + new_path, new_path, cat, false /* writeable */, false /* do not auto-create folders */); if (!c) { throw tl::Exception (tl::to_string (QObject::tr ("The selected directory is already installed as custom location"))); } diff --git a/src/lym/lym/lymMacroCollection.cc b/src/lym/lym/lymMacroCollection.cc index 39a9c9deb..c55f71ba6 100644 --- a/src/lym/lym/lymMacroCollection.cc +++ b/src/lym/lym/lymMacroCollection.cc @@ -262,7 +262,7 @@ MacroCollection::make_readonly (bool f) } MacroCollection * -MacroCollection::add_folder (const std::string &description, const std::string &p, const std::string &cat, bool readonly, bool force_create) +MacroCollection::add_folder (const std::string &description, const std::string &p, const std::string &cat, bool readonly, bool auto_create) { if (! p.empty () && p[0] == ':') { @@ -278,7 +278,7 @@ MacroCollection::add_folder (const std::string &description, const std::string & if (! tl::file_exists (fp)) { // Try to create the folder since it does not exist yet or skip that one - if (! force_create) { + if (readonly || ! auto_create) { if (tl::verbosity () >= 20) { tl::log << tl::to_string (tr ("Folder does not exist - skipping: ")) << fp; @@ -831,7 +831,7 @@ void MacroCollection::reload (bool safe) lym::MacroCollection new_collection; for (lym::MacroCollection::child_iterator c = begin_children (); c != end_children (); ++c) { - new_collection.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly (), false /* don't force to create */); + new_collection.add_folder (c->second->description (), c->second->path (), c->second->category (), c->second->is_readonly (), false /* don't auto-create folder */); } // and synchronize current with the actual one diff --git a/src/lym/lym/lymMacroCollection.h b/src/lym/lym/lymMacroCollection.h index 850a24c76..60c779bc5 100644 --- a/src/lym/lym/lymMacroCollection.h +++ b/src/lym/lym/lymMacroCollection.h @@ -89,7 +89,7 @@ public: * If force_create is true (the default), the folder will be created if it does not * exist yet. On error, 0 is returned. */ - MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly, bool force_create = true); + MacroCollection *add_folder (const std::string &description, const std::string &path, const std::string &category, bool readonly, bool auto_create = true); /** * @brief Gets the category tag of the collection diff --git a/testdata/algo/deep_region_transform_with_scaled.gds b/testdata/algo/deep_region_transform_with_scaled.gds new file mode 100644 index 0000000000000000000000000000000000000000..7bd4b1a0fc055e9deaa6e8e3ae8d7fa992e9f6a7 GIT binary patch literal 352 zcmZQzV_;&6V31*CVt>xS!5{&|rVJX$Yz7V{HXlzX1_lvkRy)T|bMIrzKUli#&|fe+ zDuh9X6}LVvHa2GuPahWs1{Mwm1~y(M21W)pJ|+eR1{(q9|NsAg1mfo)nt_42l!1X$ z1wr#DGBEJkF)%=Am^u*6z#<4VkDURp%^YlOA^rhCbzBS#TtYBcut_lj?dFkYb(jeT z3mAB0SRH^uV8A2545USXcqzmVG&k#j-TV#c#!jF+fHVgW(1Pa(8Ya%%33nq43j+YQ CTt-v? literal 0 HcmV?d00001 diff --git a/testdata/algo/deep_region_transform_with_scaled_au.gds b/testdata/algo/deep_region_transform_with_scaled_au.gds new file mode 100644 index 0000000000000000000000000000000000000000..278bc5a93437502e8746751146f218e36f8c2971 GIT binary patch literal 352 zcmaKmy=nqM6h_a^?p}vwlqeEJx3RJ?U?ma|e-exMgIZW<-Xjm;BS;#XkXP^t@+!9J zEZ5nUAwkd!!x`?}JKsT2YEA5^+!0NMN7{7$-_c5D;}wM^lIS1}=1S8H1wzFUQ?vEwWlH z?OX&=$yrF%v)QH=4t81XF>yGst*lJyMA@D%vWzR|r}Vuc)^U5Gw%*MXIJ&;2a|{B` D<$^M+ literal 0 HcmV?d00001 From 304800e4c59e0238ead91ae814093b22b6545cc3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 25 Aug 2023 20:33:54 +0200 Subject: [PATCH 03/25] Maybe fixing issue-1465 --- src/drc/drc/built-in-macros/_drc_source.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/drc/drc/built-in-macros/_drc_source.rb b/src/drc/drc/built-in-macros/_drc_source.rb index ba1b8a457..0f2ed1aa2 100644 --- a/src/drc/drc/built-in-macros/_drc_source.rb +++ b/src/drc/drc/built-in-macros/_drc_source.rb @@ -91,9 +91,10 @@ module DRC end def finish - @tmp_layers.each do |li| - @layout.delete_layer(li) + @tmp_layers.each do |layout,li| + layout.delete_layer(li) end + @tmp_layers = [] end def set_box(method, *args) @@ -347,7 +348,7 @@ CODE if cell_filter tmp = @layout_var.insert_layer(RBA::LayerInfo::new) - @tmp_layers << tmp + @tmp_layers << [ @layout_var, tmp ] @layout_var.cells(cell_filter).each do |cell| cell.shapes(tmp).insert(cell.bbox) end @@ -606,7 +607,7 @@ CODE li = @layout.insert_layer(RBA::LayerInfo::new) li && layers.push(li) - li && @tmp_layers.push(li) + li && @tmp_layers.push([ @layout, li ]) elsif (args.size == 1 && args[0].is_a?(RBA::LayerInfo)) From e6c9872ea259ef76b9abd562176c6ec343debb48 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 25 Aug 2023 22:49:54 +0200 Subject: [PATCH 04/25] Bugfix: re-run of include-expanded DRC/LVS wasn't working Reworked include-expansion scheme for DRC/LVS such that include expansion is done by the interpreter, hence is also available for re-running the script from the marker/netlist browser. This also affects the D25 implementation. --- .../drc/built-in-macros/drc_interpreters.lym | 77 ++++++++++--------- .../lvs/built-in-macros/lvs_interpreters.lym | 77 ++++++++++--------- src/lym/lym/gsiDeclLymMacro.cc | 19 +++++ src/lym/lym/lymMacroInterpreter.cc | 24 +----- .../built-in-macros/d25_interpreters.lym | 77 ++++++++++--------- 5 files changed, 146 insertions(+), 128 deletions(-) diff --git a/src/drc/drc/built-in-macros/drc_interpreters.lym b/src/drc/drc/built-in-macros/drc_interpreters.lym index dbd8591dd..720b7e963 100644 --- a/src/drc/drc/built-in-macros/drc_interpreters.lym +++ b/src/drc/drc/built-in-macros/drc_interpreters.lym @@ -19,7 +19,7 @@ module DRC class DRCExecutable < RBA::Executable - def initialize(macro, generator, rdb_index = nil) + def initialize(macro, interpreter, generator, rdb_index = nil) @drc = DRCEngine::new @drc._rdb_index = rdb_index @@ -27,6 +27,8 @@ module DRC @macro = macro + @interpreter = interpreter + end def execute @@ -38,9 +40,11 @@ module DRC begin + encoded_file, expanded_text = @interpreter.include_expansion(@macro) + # No verbosity set in drc engine - we cannot use the engine's logger RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}") - @drc.instance_eval(@macro.text, @macro.path) + @drc.instance_eval(expanded_text, encoded_file) rescue => ex @@ -66,13 +70,40 @@ module DRC end + # A recipe implementation allowing the DRC run to be redone + class DRCRecipe < RBA::Recipe + + def initialize(interpreter) + + super("drc", "DRC recipe") + + @interpreter = interpreter + + end + + def executable(params) + + script = params["script"] + if ! script + return + end + + macro = RBA::Macro::macro_by_path(script) + macro || raise("Can't find DRC script #{script} - unable to re-run") + + DRCExecutable::new(macro, @interpreter, self.generator("script" => script), params["rdb_index"]) + + end + + end + # A DSL implementation for a DRC language (XML format) class DRCInterpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = DRCRecipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -97,7 +128,7 @@ module DRC # Implements the execute method def executable(macro) - DRCExecutable::new(macro, @recipe.generator("script" => macro.path)) + DRCExecutable::new(macro, self, @recipe.generator("script" => macro.path)) end end @@ -106,9 +137,9 @@ module DRC class DRCPlainTextInterpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = DRCRecipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -124,40 +155,14 @@ module DRC # Implements the execute method def executable(macro) - DRCExecutable::new(macro, @recipe.generator("script" => macro.path)) + DRCExecutable::new(macro, self, @recipe.generator("script" => macro.path)) end end - # A recipe implementation allowing the DRC run to be redone - class DRCRecipe < RBA::Recipe - - def initialize - super("drc", "DRC recipe") - end - - def executable(params) - - script = params["script"] - if ! script - return - end - - macro = RBA::Macro::macro_by_path(script) - macro || raise("Can't find DRC script #{script} - unable to re-run") - - DRCExecutable::new(macro, self.generator("script" => script), params["rdb_index"]) - - end - - end - - # Register the recipe - drc_recipe = DRCRecipe::new - # Register the new interpreters - DRCInterpreter::new(drc_recipe) - DRCPlainTextInterpreter::new(drc_recipe) + DRCInterpreter::new + DRCPlainTextInterpreter::new # Creates a new macro category if RBA.constants.member?(:Application) && RBA::Application::instance diff --git a/src/lvs/lvs/built-in-macros/lvs_interpreters.lym b/src/lvs/lvs/built-in-macros/lvs_interpreters.lym index c99c21e98..2aaf6f1fa 100644 --- a/src/lvs/lvs/built-in-macros/lvs_interpreters.lym +++ b/src/lvs/lvs/built-in-macros/lvs_interpreters.lym @@ -19,7 +19,7 @@ module LVS class LVSExecutable < RBA::Executable - def initialize(macro, generator, l2ndb_index = nil) + def initialize(macro, interpreter, generator, l2ndb_index = nil) @lvs = LVSEngine::new @lvs._l2ndb_index = l2ndb_index @@ -27,6 +27,8 @@ module LVS @macro = macro + @interpreter = interpreter + end def execute @@ -38,9 +40,11 @@ module LVS begin + encoded_file, expanded_text = @interpreter.include_expansion(@macro) + # No verbosity set in lvs engine - we cannot use the engine's logger RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}") - @lvs.instance_eval(@macro.text, @macro.path) + @lvs.instance_eval(expanded_text, encoded_file) rescue => ex @@ -66,13 +70,40 @@ module LVS end + # A recipe implementation allowing the LVS run to be redone + class LVSRecipe < RBA::Recipe + + def initialize(interpreter) + + super("lvs", "LVS recipe") + + @interpreter = interpreter + + end + + def executable(params) + + script = params["script"] + if ! script + return + end + + macro = RBA::Macro::macro_by_path(script) + macro || raise("Can't find LVS script #{script} - unable to re-run") + + LVSExecutable::new(macro, @interpreter, self.generator("script" => script), params["l2ndb_index"]) + + end + + end + # A DSL implementation for a LVS language (XML format) class LVSInterpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = LVSRecipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -97,7 +128,7 @@ module LVS # Implements the execute method def executable(macro) - LVSExecutable::new(macro, @recipe.generator("script" => macro.path)) + LVSExecutable::new(macro, self, @recipe.generator("script" => macro.path)) end end @@ -106,9 +137,9 @@ module LVS class LVSPlainTextInterpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = LVSRecipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -124,40 +155,14 @@ module LVS # Implements the execute method def executable(macro) - LVSExecutable::new(macro, @recipe.generator("script" => macro.path)) + LVSExecutable::new(macro, self, @recipe.generator("script" => macro.path)) end end - # A recipe implementation allowing the LVS run to be redone - class LVSRecipe < RBA::Recipe - - def initialize - super("lvs", "LVS recipe") - end - - def executable(params) - - script = params["script"] - if ! script - return - end - - macro = RBA::Macro::macro_by_path(script) - macro || raise("Can't find LVS script #{script} - unable to re-run") - - LVSExecutable::new(macro, self.generator("script" => script), params["l2ndb_index"]) - - end - - end - - # Register the recipe - lvs_recipe = LVSRecipe::new - # Register the new interpreters - LVSInterpreter::new(lvs_recipe) - LVSPlainTextInterpreter::new(lvs_recipe) + LVSInterpreter::new + LVSPlainTextInterpreter::new # Creates a new macro category if RBA.constants.member?(:Application) && RBA::Application::instance diff --git a/src/lym/lym/gsiDeclLymMacro.cc b/src/lym/lym/gsiDeclLymMacro.cc index 7d7ce3d63..28e1ac4d7 100644 --- a/src/lym/lym/gsiDeclLymMacro.cc +++ b/src/lym/lym/gsiDeclLymMacro.cc @@ -247,6 +247,18 @@ private: bool m_supports_include_expansion; }; +static std::vector +include_expansion (MacroInterpreterImpl *interp, lym::Macro *macro) +{ + std::vector res; + + auto sp = interp->include_expansion (macro); + res.push_back (sp.first); + res.push_back (sp.second); + + return res; +} + gsi::EnumIn decl_FormatEnum ("lay", "Format", gsi::enum_const ("PlainTextFormat", lym::Macro::PlainTextFormat, "@brief The macro has plain text format" @@ -298,6 +310,13 @@ Class decl_MacroInterpreter ("lay", "MacroInterpreter", gsi::method ("NoDebugger", &const_NoDebugger, "@brief Indicates no debugging for \\debugger_scheme\n" ) + + gsi::method_ext ("include_expansion", &include_expansion, gsi::arg ("macro"), + "@brief Provides include expansion as defined by the interpreter\n" + "The return value will be a two-element array with the encoded file path " + "and the include-expanded text.\n" + "\n" + "This method has been introduced in version 0.28.12." + ) + gsi::method ("register", &MacroInterpreterImpl::register_gsi, gsi::arg ("name"), "@brief Registers the macro interpreter\n" "@param name The interpreter name. This is an arbitrary string which should be unique.\n" diff --git a/src/lym/lym/lymMacroInterpreter.cc b/src/lym/lym/lymMacroInterpreter.cc index 8cb7a9f34..e080223f2 100644 --- a/src/lym/lym/lymMacroInterpreter.cc +++ b/src/lym/lym/lymMacroInterpreter.cc @@ -130,31 +130,15 @@ MacroInterpreter::execute_macro (const lym::Macro *macro) if (cls.current_name () == macro->dsl_interpreter ()) { - std::pair et = cls->include_expansion (macro); - if (et.first.empty () || et.first == macro->path ()) { - - std::unique_ptr eo (cls->executable (macro)); - if (eo.get ()) { - eo->do_execute (); - } - - } else { - - // provide a copy which takes the include-expanded version - lym::Macro tmp_macro; - tmp_macro.assign (*macro); - tmp_macro.set_text (et.second); - tmp_macro.set_file_path (et.first); - std::unique_ptr eo (cls->executable (&tmp_macro)); - if (eo.get ()) { - eo->do_execute (); - } - + std::unique_ptr eo (cls->executable (macro)); + if (eo.get ()) { + eo->do_execute (); } return; } + } throw tl::Exception (tl::to_string (tr ("No interpreter registered for DSL type '")) + macro->dsl_interpreter () + "'"); diff --git a/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym index 86d7d954d..fd8e1bca5 100644 --- a/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym +++ b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym @@ -19,13 +19,15 @@ module D25 class D25Executable < RBA::Executable - def initialize(macro, generator) + def initialize(macro, interpreter, generator) @d25 = D25Engine::new @d25._generator = generator @macro = macro + @interpreter = interpreter + end def execute @@ -37,9 +39,11 @@ module D25 begin + encoded_file, expanded_text = @interpreter.include_expansion(@macro) + # No verbosity set in d25 engine - we cannot use the engine's logger RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}") - @d25.instance_eval(@macro.text, @macro.path) + @d25.instance_eval(expanded_text, encoded_file) rescue => ex @@ -67,13 +71,40 @@ module D25 end + # A recipe implementation allowing the D25 run to be redone + class D25Recipe < RBA::Recipe + + def initialize(interpreter) + + super("d25", "D25 recipe") + + @interpreter = interpreter + + end + + def executable(params) + + script = params["script"] + if ! script + return + end + + macro = RBA::Macro::macro_by_path(script) + macro || raise("Can't find D25 script #{script} - unable to re-run") + + D25Executable::new(macro, @interpreter, self.generator("script" => script)) + + end + + end + # A DSL implementation for a D25 language (XML format) class D25Interpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = D25Recipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -98,7 +129,7 @@ module D25 # Implements the execute method def executable(macro) - D25Executable::new(macro, @recipe.generator("script" => macro.path)) + D25Executable::new(macro, self, @recipe.generator("script" => macro.path)) end end @@ -107,9 +138,9 @@ module D25 class D25PlainTextInterpreter < RBA::MacroInterpreter # Constructor - def initialize(recipe) + def initialize - @recipe = recipe + @recipe = D25Recipe::new(self) # Make the DSL use ruby syntax highlighting self.syntax_scheme = "ruby" @@ -125,40 +156,14 @@ module D25 # Implements the execute method def executable(macro) - D25Executable::new(macro, @recipe.generator("script" => macro.path)) + D25Executable::new(macro, self, @recipe.generator("script" => macro.path)) end end - # A recipe implementation allowing the D25 run to be redone - class D25Recipe < RBA::Recipe - - def initialize - super("d25", "D25 recipe") - end - - def executable(params) - - script = params["script"] - if ! script - return - end - - macro = RBA::Macro::macro_by_path(script) - macro || raise("Can't find D25 script #{script} - unable to re-run") - - D25Executable::new(macro, self.generator("script" => script)) - - end - - end - - # Register the recipe - d25_recipe = D25Recipe::new - # Register the new interpreters - D25Interpreter::new(d25_recipe) - D25PlainTextInterpreter::new(d25_recipe) + D25Interpreter::new + D25PlainTextInterpreter::new # Creates a new macro category if RBA::Application::instance From 4e83cab061b88ca620c86e27374a73b33fc4ce0e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 26 Aug 2023 15:44:54 +0200 Subject: [PATCH 05/25] Proper Python stack trace in debugger for Python 3.10 --- src/pya/pya/pya.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pya/pya/pya.cc b/src/pya/pya/pya.cc index 44f31d88a..2666e0e5f 100644 --- a/src/pya/pya/pya.cc +++ b/src/pya/pya/pya.cc @@ -109,13 +109,13 @@ public: { while (frame != NULL) { -#if PY_VERSION_HEX >= 0x030B0000 +#if PY_VERSION_HEX >= 0x030A0000 int line = PyFrame_GetLineNumber(frame); #else int line = frame->f_lineno; #endif std::string fn; -#if PY_VERSION_HEX >= 0x030B0000 +#if PY_VERSION_HEX >= 0x030A0000 if (test_type (PyFrame_GetCode(frame)->co_filename, true)) { fn = normalize_path (python2c (PyFrame_GetCode(frame)->co_filename)); #else @@ -125,7 +125,7 @@ public: } m_stack_trace.push_back (tl::BacktraceElement (fn, line)); -#if PY_VERSION_HEX >= 0x030B0000 +#if PY_VERSION_HEX >= 0x030A0000 frame = PyFrame_GetBack(frame); #else frame = frame->f_back; From 695ec041af869a5f80ef38a65b51b2d8976048e1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 27 Aug 2023 11:27:46 +0200 Subject: [PATCH 06/25] Enhancement: Allowing to select cells by error markers too (so that PCells with errors can be selected) --- src/laybasic/laybasic/layFinder.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/laybasic/laybasic/layFinder.cc b/src/laybasic/laybasic/layFinder.cc index be581c5d8..2e05f9463 100644 --- a/src/laybasic/laybasic/layFinder.cc +++ b/src/laybasic/laybasic/layFinder.cc @@ -748,10 +748,11 @@ InstFinder::find_internal (LayoutViewBase *view, unsigned int cv_index, const db m_visible_layer_indexes.push_back (l->layer_index ()); } } - } - - if (!m_visible_layers || view->guiding_shapes_visible ()) { - m_visible_layer_indexes.push_back (cv->layout ().guiding_shape_layer ()); + // add guiding shape and error layers so we can select cells by error markers or guiding shapes + if (view->guiding_shapes_visible ()) { + m_visible_layer_indexes.push_back (cv->layout ().guiding_shape_layer ()); + } + m_visible_layer_indexes.push_back (cv->layout ().error_layer ()); } m_cv_index = cv_index; From 38f08d3e909618c5d5271e2207f9fff6dcf5bf8a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 27 Aug 2023 11:28:20 +0200 Subject: [PATCH 07/25] Bugfix: avoid a segfault in the properties dialog --- src/layui/layui/layPropertiesDialog.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/layui/layui/layPropertiesDialog.cc b/src/layui/layui/layPropertiesDialog.cc index f6ae37e2b..e37f31e94 100644 --- a/src/layui/layui/layPropertiesDialog.cc +++ b/src/layui/layui/layPropertiesDialog.cc @@ -272,6 +272,7 @@ PropertiesDialog::disconnect () delete *p; } mp_properties_pages.clear (); + m_index = -1; } void @@ -604,7 +605,7 @@ PropertiesDialog::ok_pressed () { BEGIN_PROTECTED - if (! mp_properties_pages [m_index]->readonly ()) { + if (m_index >= 0 && m_index < int (mp_properties_pages.size ()) && ! mp_properties_pages [m_index]->readonly ()) { db::Transaction t (mp_manager, tl::to_string (QObject::tr ("Apply changes")), m_transaction_id); From 1391bd7219f1c9f9e59958d052011e7796dea338 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 27 Aug 2023 11:29:42 +0200 Subject: [PATCH 08/25] Enhancement: crash log now is shown in a more reliable way - but less fancy. In addition, a crash log is written 'klayout_crash.log' in the home path and the crash message is printed to error. --- src/lay/lay/laySignalHandler.cc | 47 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/lay/lay/laySignalHandler.cc b/src/lay/lay/laySignalHandler.cc index c4f5075dd..95dbe1175 100644 --- a/src/lay/lay/laySignalHandler.cc +++ b/src/lay/lay/laySignalHandler.cc @@ -27,6 +27,8 @@ #include "tlException.h" #include "tlString.h" #include "tlLog.h" +#include "tlFileUtils.h" +#include "tlStream.h" #ifdef _WIN32 # include @@ -297,16 +299,6 @@ void signal_handler (int signo, siginfo_t *si, void *) lay::Version::name () + " " + lay::Version::version () + " (" + lay::Version::subversion () + ")\n"; - std::unique_ptr msg; - - bool has_gui = s_sh_has_gui && lay::ApplicationBase::instance () && lay::ApplicationBase::instance ()->has_gui (); - - if (has_gui) { - msg.reset (new CrashMessage (0, false, tl::to_qstring (text) + QObject::tr ("\nCollecting backtrace .."))); - msg->show (); - lay::ApplicationBase::instance ()->qapp_gui ()->setOverrideCursor (Qt::WaitCursor); - } - text += std::string ("\nBacktrace:\n"); #if 0 @@ -332,14 +324,6 @@ void signal_handler (int signo, siginfo_t *si, void *) bool has_addr2line = true; for (size_t i = 0; i < nptrs; ++i) { - if (has_gui) { - lay::ApplicationBase::instance ()->qapp_gui ()->processEvents (); - if (msg->is_cancel_pressed ()) { - text += "...\n"; - break; - } - } - Dl_info info; dladdr (array [i], &info); @@ -407,9 +391,25 @@ void signal_handler (int signo, siginfo_t *si, void *) #endif - if (has_gui) { + try { - lay::ApplicationBase::instance ()->qapp_gui ()->setOverrideCursor (QCursor ()); + // write crash log + + std::string crash_log = tl::combine_path (lay::ApplicationBase::instance () ? lay::ApplicationBase::instance ()->appdata_path () : ".", "klayout_crash.log"); + + tl::OutputStream os (crash_log, tl::OutputStream::OM_Plain, true); + os << text; + + text += "\nCrash log written to " + crash_log; + + } catch (...) { + // .. ignore errors + } + + tl::error << text << tl::noendl; + + bool has_gui = s_sh_has_gui && lay::ApplicationBase::instance () && lay::ApplicationBase::instance ()->has_gui (); + if (has_gui) { // YES! I! KNOW! // In a signal handler you shall not do fancy stuff (in particular not @@ -418,8 +418,10 @@ void signal_handler (int signo, siginfo_t *si, void *) // from our stack frames) and everything is better than just core dumping. // Isn't it? - msg->set_text (tl::to_qstring (text)); - msg->set_can_resume (can_resume); + lay::ApplicationBase::instance ()->qapp_gui ()->setOverrideCursor (QCursor ()); + + std::unique_ptr msg; + msg.reset (new CrashMessage (0, can_resume, tl::to_qstring (text))); if (! msg->exec ()) { @@ -438,7 +440,6 @@ void signal_handler (int signo, siginfo_t *si, void *) } else { - tl::error << text << tl::noendl; _exit (signo); } From 512183d55c8155ea6a3eb99a39b5ca992908dc33 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 30 Aug 2023 21:48:09 +0200 Subject: [PATCH 09/25] Fixed a documentation error --- src/db/db/gsiDeclDbEdge.cc | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/db/db/gsiDeclDbEdge.cc b/src/db/db/gsiDeclDbEdge.cc index af4985b62..05052ad78 100644 --- a/src/db/db/gsiDeclDbEdge.cc +++ b/src/db/db/gsiDeclDbEdge.cc @@ -464,7 +464,7 @@ struct edge_defs "@return The scaled edge\n" ) + method ("contains?", &C::contains, gsi::arg ("p"), - "@brief Test whether a point is on an edge.\n" + "@brief Tests whether a point is on an edge.\n" "\n" "A point is on a edge if it is on (or at least closer \n" "than a grid point to) the edge.\n" @@ -474,7 +474,7 @@ struct edge_defs "@return True if the point is on the edge.\n" ) + method ("contains_excl?", &C::contains_excl, gsi::arg ("p"), - "@brief Test whether a point is on an edge excluding the endpoints.\n" + "@brief Tests whether a point is on an edge excluding the endpoints.\n" "\n" "A point is on a edge if it is on (or at least closer \n" "than a grid point to) the edge.\n" @@ -494,15 +494,18 @@ struct edge_defs "\n" "@return True if the edges are coincident.\n" ) + - method ("intersect?", &C::intersect, gsi::arg ("e"), + method ("intersects?|#intersects?", &C::intersect, gsi::arg ("e"), "@brief Intersection test. \n" "\n" "Returns true if the edges intersect. Two edges intersect if they share at least one point. \n" "If the edges coincide, they also intersect.\n" - "For degenerated edges, the intersection is mapped to\n" - "point containment tests.\n" + "If one of the edges is degenerate (both points are identical), that point is " + "required to sit exaclty on the other edge. If both edges are degenerate, their " + "points are required to be identical.\n" "\n" "@param e The edge to test.\n" + "\n" + "The 'intersects' (with an 's') synonym has been introduced in version 0.28.12.\n" ) + method_ext ("intersection_point", &intersect_point, gsi::arg ("e"), "@brief Returns the intersection point of two edges. \n" @@ -600,19 +603,21 @@ struct edge_defs "This method has been introduced in version 0.23.\n" ) + method ("crossed_by?", &C::crossed_by, gsi::arg ("e"), - "@brief Check, if an edge is cut by a line (given by an edge)\n" + "@brief Checks, if the line given by self is crossed by the edge e\n" "\n" - "This method returns true if p1 is in one semispace \n" - "while p2 is in the other or one of them is on the line\n" - "through the edge \"e\"\n" + "self if considered an infinite line. This predicate renders true " + "if the edge e is cut by this line. In other words: " + "this method returns true if e.p1 is in one semispace of self \n" + "while e.p2 is in the other or one of them is exactly on self.\n" "\n" "@param e The edge representing the line that the edge must be crossing.\n" ) + method_ext ("crossing_point", &crossing_point, gsi::arg ("e"), "@brief Returns the crossing point on two edges. \n" "\n" - "This method delivers the point where the given edge (self) crosses the line given " - "by the edge in argument \"e\". If self does not cross this line, the result is undefined. " + "This method delivers the point where the given line (self) crosses the edge given " + "by the argument \"e\". self is considered infinitely long and is required to cut " + "through the edge \"e\". If self does not cut this line, the result is undefined. " "See \\crossed_by? for a description of the crossing predicate.\n" "\n" "@param e The edge representing the line that self must be crossing.\n" From fdf3a67a9ef2cc045a81063fa950eb6e16ff7835 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 30 Aug 2023 21:49:15 +0200 Subject: [PATCH 10/25] Fixed an issue preventing to run Ruby unit tests from klayout batch mode --- src/rba/rba/rbaUtils.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/rba/rba/rbaUtils.cc b/src/rba/rba/rbaUtils.cc index 11b0fd6ea..894f06a4f 100644 --- a/src/rba/rba/rbaUtils.cc +++ b/src/rba/rba/rbaUtils.cc @@ -115,13 +115,15 @@ rba_get_backtrace_from_array (VALUE backtrace, std::vector void block_exceptions (bool f) { - RubyInterpreter::instance ()->block_exceptions (f); + if (RubyInterpreter::instance ()) { + RubyInterpreter::instance ()->block_exceptions (f); + } } bool exceptions_blocked () { - return RubyInterpreter::instance ()->exceptions_blocked (); + return RubyInterpreter::instance () ? RubyInterpreter::instance ()->exceptions_blocked () : false; } void @@ -161,8 +163,10 @@ rba_check_error () std::vector bt; rba_get_backtrace_from_array (rb_funcall (lasterr, rb_intern ("backtrace"), 0), bt, 0); - const std::string &ds = RubyInterpreter::instance ()->debugger_scope (); - bt.erase (bt.begin (), bt.begin () + RubyStackTraceProvider::scope_index (bt, ds)); + if (RubyInterpreter::instance ()) { + const std::string &ds = RubyInterpreter::instance ()->debugger_scope (); + bt.erase (bt.begin (), bt.begin () + RubyStackTraceProvider::scope_index (bt, ds)); + } // parse the backtrace to get the line number tl::BacktraceElement info; From c4e57d2bce21b6f109999aa18f56c984c92f3ef2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 1 Sep 2023 23:16:50 +0200 Subject: [PATCH 11/25] Don't clear search when switching macro editor pages --- src/lay/lay/layMacroEditorDialog.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 1d0a53711..07e54259a 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -1770,8 +1770,6 @@ MacroEditorDialog::current_tab_changed (int index) } } - // clear the search - searchEditBox->clear (); replaceFrame->setEnabled (page && page->macro () && !page->macro ()->is_readonly ()); apply_search (); From 3d0bb8516bb458b88452a92eb88b7e8bf625377e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 00:36:17 +0200 Subject: [PATCH 12/25] Non-modal notifications for macro editors too --- src/lay/lay/layMacroEditorDialog.cc | 101 +++++++++++++----------- src/lay/lay/layMacroEditorDialog.h | 1 + src/lay/lay/layMacroEditorPage.cc | 116 +++++++++++++++++++++++++++- src/lay/lay/layMacroEditorPage.h | 115 ++++++++++++++++++++++++++- 4 files changed, 282 insertions(+), 51 deletions(-) diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 07e54259a..6129038ee 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -2373,6 +2373,15 @@ BEGIN_PROTECTED END_PROTECTED } +void +MacroEditorDialog::close_requested () +{ + MacroEditorPage *page = dynamic_cast (sender ()); + if (! m_in_exec && page) { + tab_close_requested (tabWidget->indexOf (page)); + } +} + void MacroEditorDialog::tab_close_requested (int index) { @@ -2605,66 +2614,65 @@ BEGIN_PROTECTED return; } - std::set modified_files; - for (std::map ::const_iterator m = m_tab_widgets.begin (); m != m_tab_widgets.end (); ++m) { - if (m->first->is_modified ()) { - modified_files.insert (m->first->path ()); + std::map path_to_page; + for (int i = 0; i < tabWidget->count (); ++i) { + MacroEditorPage *page = dynamic_cast (tabWidget->widget (i)); + if (page) { + path_to_page.insert (std::make_pair (page->path (), page)); } } - std::vector conflicts; for (std::vector::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) { - if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) { - conflicts.push_back (*f); + + std::string fn = tl::to_string (*f); + auto w = path_to_page.find (fn); + if (w == path_to_page.end ()) { + continue; + } + + if (w->second->macro () && w->second->macro ()->is_modified ()) { + + lay::MacroEditorNotification n ("reload", tl::to_string (tr ("Macro has changed on disk, but was modified")), tl::Variant (fn)); + n.add_action ("reload", tl::to_string (tr ("Reload and discard changes"))); + w->second->add_notification (n); + + } else { + + lay::MacroEditorNotification n ("reload", tl::to_string (tr ("Macro has changed on disk")), tl::Variant (fn)); + n.add_action ("reload", tl::to_string (tr ("Reload"))); + w->second->add_notification (n); + } } + for (std::vector::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) { - if (modified_files.find (tl::to_string (*f)) != modified_files.end ()) { - conflicts.push_back (*f); + + std::string fn = tl::to_string (*f); + auto w = path_to_page.find (fn); + if (w == path_to_page.end ()) { + continue; + } + + if (w->second->macro () && w->second->macro ()->is_modified ()) { + + lay::MacroEditorNotification n ("close", tl::to_string (tr ("Macro has been removed on disk, but was modified")), tl::Variant (fn)); + n.add_action ("close", tl::to_string (tr ("Close tab and discard changes"))); + w->second->add_notification (n); + + } else { + + lay::MacroEditorNotification n ("close", tl::to_string (tr ("Macro has been removed on disk")), tl::Variant (fn)); + n.add_action ("close", tl::to_string (tr ("Close tab"))); + w->second->add_notification (n); + } } - QString msg; - - if (m_changed_files.size () + m_removed_files.size () == 1) { - msg = QObject::tr ("The following file has been changed on disk:\n\n"); - for (std::vector::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f); - } - for (std::vector::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f); - } - if (!conflicts.empty ()) { - msg += tr ("\nThis file has been modified in the editor as well.\nRefresh this file and discard changes?"); - } else { - msg += tr ("\nRefresh this file?"); - } - } else { - msg = QObject::tr ("The following files have been changed on disk:\n\n"); - for (std::vector::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f); - } - for (std::vector::const_iterator f = m_removed_files.begin (); f != m_removed_files.end (); ++f) { - msg += QString::fromUtf8 (" %1 (removed)\n").arg (*f); - } - if (!conflicts.empty ()) { - msg += tr("\nSome of these files are modified on disk and in the editor:\n\n"); - for (std::vector::const_iterator f = conflicts.begin (); f != conflicts.end (); ++f) { - msg += QString::fromUtf8 (" %1 (conflict)\n").arg (*f); - } - msg += tr ("\nRefresh these and the other files and discard all changes?"); - } else { - msg += tr ("\nRefresh those files?"); - } - } + refresh_file_watcher (); m_changed_files.clear (); m_removed_files.clear (); - if (QMessageBox::question (this, tr ("Refresh Files"), msg, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - reload_macros (); - } - END_PROTECTED } @@ -3523,6 +3531,7 @@ MacroEditorDialog::create_page (lym::Macro *macro) editor->set_font (m_font_family, m_font_size); editor->exec_model ()->set_run_mode (m_in_exec); editor->connect_macro (macro); + connect (editor.get (), SIGNAL (close_requested ()), this, SLOT (close_requested ())); connect (editor.get (), SIGNAL (help_requested (const QString &)), this, SLOT (help_requested (const QString &))); connect (editor.get (), SIGNAL (search_requested (const QString &, bool)), this, SLOT (search_requested (const QString &, bool))); connect (editor.get (), SIGNAL (edit_trace (bool)), this, SLOT (add_edit_trace (bool))); diff --git a/src/lay/lay/layMacroEditorDialog.h b/src/lay/lay/layMacroEditorDialog.h index 24bd3eccf..71c78ca02 100644 --- a/src/lay/lay/layMacroEditorDialog.h +++ b/src/lay/lay/layMacroEditorDialog.h @@ -212,6 +212,7 @@ private slots: void search_editing (); void search_finished (); void tab_close_requested (int); + void close_requested (); void close_all (); void close_all_but_this (); void close_all_left (); diff --git a/src/lay/lay/layMacroEditorPage.cc b/src/lay/lay/layMacroEditorPage.cc index 3abaa328b..bcb8c302f 100644 --- a/src/lay/lay/layMacroEditorPage.cc +++ b/src/lay/lay/layMacroEditorPage.cc @@ -46,6 +46,8 @@ #include #include #include +#include +#include namespace lay { @@ -81,6 +83,59 @@ void MacroEditorTextWidget::paintEvent (QPaintEvent *event) return TextEditWidget::paintEvent (event); } +// ---------------------------------------------------------------------------------------------- +// MacroEditorNotificationWidget implementation + +MacroEditorNotificationWidget::MacroEditorNotificationWidget (MacroEditorPage *parent, const MacroEditorNotification *notification) + : QFrame (parent), mp_parent (parent), mp_notification (notification) +{ + setBackgroundRole (QPalette::ToolTipBase); + setAutoFillBackground (true); + + QHBoxLayout *layout = new QHBoxLayout (this); + layout->setContentsMargins (4, 4, 4, 4); + + QLabel *title_label = new QLabel (this); + layout->addWidget (title_label, 1); + title_label->setText (tl::to_qstring (notification->title ())); + title_label->setForegroundRole (QPalette::ToolTipText); + title_label->setWordWrap (true); + activate_help_links (title_label); + + for (auto a = notification->actions ().begin (); a != notification->actions ().end (); ++a) { + + QPushButton *pb = new QPushButton (this); + layout->addWidget (pb); + + pb->setText (tl::to_qstring (a->second)); + m_action_buttons.insert (std::make_pair (pb, a->first)); + connect (pb, SIGNAL (clicked ()), this, SLOT (action_triggered ())); + + } + + QToolButton *close_button = new QToolButton (); + close_button->setIcon (QIcon (":clear_edit_16px.png")); + close_button->setAutoRaise (true); + layout->addWidget (close_button); + + connect (close_button, SIGNAL (clicked ()), this, SLOT (close_triggered ())); +} + +void +MacroEditorNotificationWidget::action_triggered () +{ + auto a = m_action_buttons.find (sender ()); + if (a != m_action_buttons.end ()) { + mp_parent->notification_action (*mp_notification, a->second); + } +} + +void +MacroEditorNotificationWidget::close_triggered () +{ + mp_parent->remove_notification (*mp_notification); +} + // ---------------------------------------------------------------------------------------------- // MacroEditorHighlighters implementation @@ -491,15 +546,17 @@ void MacroEditorSidePanel::paintEvent (QPaintEvent *) MacroEditorPage::MacroEditorPage (QWidget * /*parent*/, MacroEditorHighlighters *highlighters) : mp_macro (0), mp_highlighters (highlighters), mp_highlighter (0), m_error_line (-1), m_ntab (8), m_nindent (2), m_ignore_cursor_changed_event (false) { - QVBoxLayout *layout = new QVBoxLayout (this); - + mp_layout = new QVBoxLayout (this); + mp_layout->setContentsMargins (0, 0, 0, 0); + mp_readonly_label = new QLabel (this); mp_readonly_label->setText (QObject::tr ("Macro is read-only and cannot be edited")); mp_readonly_label->hide (); - layout->addWidget (mp_readonly_label); + mp_layout->addWidget (mp_readonly_label); QHBoxLayout *hlayout = new QHBoxLayout (); - layout->addLayout (hlayout); + hlayout->setContentsMargins (4, 4, 4, 4); + mp_layout->addLayout (hlayout); mp_exec_model = new MacroEditorExecutionModel (this); mp_text = new MacroEditorTextWidget (this); @@ -1021,6 +1078,8 @@ void MacroEditorPage::connect_macro (lym::Macro *macro) if (mp_macro) { + m_path = mp_macro->path (); + connect (mp_macro, SIGNAL (changed ()), this, SLOT (update ())); lym::Macro::Interpreter lang = macro->interpreter (); @@ -1924,5 +1983,54 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event) return false; } +void +MacroEditorPage::add_notification (const MacroEditorNotification ¬ification) +{ + if (m_notification_widgets.find (¬ification) == m_notification_widgets.end ()) { + m_notifications.push_back (notification); + QWidget *w = new MacroEditorNotificationWidget (this, &m_notifications.back ()); + m_notification_widgets.insert (std::make_pair (&m_notifications.back (), w)); + mp_layout->insertWidget (0, w); + } +} + +void +MacroEditorPage::remove_notification (const MacroEditorNotification ¬ification) +{ + auto nw = m_notification_widgets.find (¬ification); + if (nw != m_notification_widgets.end ()) { + + nw->second->deleteLater (); + m_notification_widgets.erase (nw); + + for (auto n = m_notifications.begin (); n != m_notifications.end (); ++n) { + if (*n == notification) { + m_notifications.erase (n); + break; + } + } + + } +} + +void +MacroEditorPage::notification_action (const MacroEditorNotification ¬ification, const std::string &action) +{ + if (action == "close") { + + remove_notification (notification); + + emit close_requested (); + + } else if (action == "reload") { + + remove_notification (notification); + + mp_macro->load (); + mp_macro->reset_modified (); + + } +} + } diff --git a/src/lay/lay/layMacroEditorPage.h b/src/lay/lay/layMacroEditorPage.h index c298a32b4..3e4e1fbf6 100644 --- a/src/lay/lay/layMacroEditorPage.h +++ b/src/lay/lay/layMacroEditorPage.h @@ -27,6 +27,7 @@ #include "lymMacro.h" #include "layGenericSyntaxHighlighter.h" +#include "tlVariant.h" #include #include @@ -47,10 +48,13 @@ class QSyntaxHighlighter; class QTimer; class QWindow; class QListWidget; +class QVBoxLayout; namespace lay { +class MacroEditorPage; + /** * @brief A collection of highlighters */ @@ -209,7 +213,92 @@ private: bool m_debugging_on; }; -class MacroEditorPage +/** + * @brief Descriptor for a notification inside the macro editor + * + * Notifications are popups added at the top of the view to indicate need for reloading for example. + * Notifications have a name, a title, optional actions (id, title) and a parameter (e.g. file path to reload). + * Actions are mapped to QPushButtons. + */ +class MacroEditorNotification +{ +public: + MacroEditorNotification (const std::string &name, const std::string &title, const tl::Variant ¶meter = tl::Variant ()) + : m_name (name), m_title (title), m_parameter (parameter) + { + // .. nothing yet .. + } + + void add_action (const std::string &name, const std::string &title) + { + m_actions.push_back (std::make_pair (name, title)); + } + + const std::vector > &actions () const + { + return m_actions; + } + + const std::string &name () const + { + return m_name; + } + + const std::string &title () const + { + return m_title; + } + + const tl::Variant ¶meter () const + { + return m_parameter; + } + + bool operator<(const MacroEditorNotification &other) const + { + if (m_name != other.name ()) { + return m_name < other.name (); + } + return m_parameter < other.parameter (); + } + + bool operator==(const MacroEditorNotification &other) const + { + if (m_name != other.name ()) { + return false; + } + return m_parameter == other.parameter (); + } + +private: + std::string m_name; + std::string m_title; + tl::Variant m_parameter; + std::vector > m_actions; +}; + +/** + * @brief A widget representing a notification + */ +class MacroEditorNotificationWidget + : public QFrame +{ +Q_OBJECT + +public: + MacroEditorNotificationWidget (MacroEditorPage *parent, const MacroEditorNotification *notification); + +private slots: + void action_triggered (); + void close_triggered (); + +private: + MacroEditorPage *mp_parent; + const MacroEditorNotification *mp_notification; + std::map m_action_buttons; +}; + +class MacroEditorPage : public QWidget { Q_OBJECT @@ -224,6 +313,11 @@ public: return mp_macro; } + const std::string path () const + { + return m_path; + } + bool is_modified () const { return m_is_modified; @@ -269,10 +363,14 @@ public: void set_editor_focus (); + void add_notification (const MacroEditorNotification ¬ificaton); + void remove_notification (const MacroEditorNotification ¬ificaton); + signals: void help_requested (const QString &s); void search_requested (const QString &s, bool backward); void edit_trace (bool); + void close_requested (); public slots: void commit (); @@ -288,10 +386,22 @@ protected slots: void hide_completer (); private: + friend class MacroEditorNotificationWidget; + + struct CompareNotificationPointers + { + bool operator() (const MacroEditorNotification *a, const MacroEditorNotification *b) const + { + return *a < *b; + } + }; + lym::Macro *mp_macro; + std::string m_path; MacroEditorExecutionModel *mp_exec_model; MacroEditorTextWidget *mp_text; MacroEditorSidePanel *mp_side_panel; + QVBoxLayout *mp_layout; QLabel *mp_readonly_label; bool m_is_modified; MacroEditorHighlighters *mp_highlighters; @@ -305,6 +415,8 @@ private: QTimer *mp_completer_timer; QWidget *mp_completer_popup; QListWidget *mp_completer_list; + std::list m_notifications; + std::map m_notification_widgets; void update_extra_selections (); bool return_pressed (); @@ -316,6 +428,7 @@ private: QTextCursor get_completer_cursor (int &pos0, int &pos); bool select_match_here (); void replace_in_selection (const QString &replace, bool first); + void notification_action (const MacroEditorNotification ¬ification, const std::string &action); bool eventFilter (QObject *watched, QEvent *event); }; From 3ddd2046c42ae4df90daa425df3ed40ff61b4b32 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 00:49:47 +0200 Subject: [PATCH 13/25] Ignoring __pycache__ directories in macro editor This feature can be configured by providing a colon-separated list of directory names to ignore in the $KLAYOUT_IGNORE_MACRO_DIRS env var. --- src/lym/lym/lymMacroCollection.cc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lym/lym/lymMacroCollection.cc b/src/lym/lym/lymMacroCollection.cc index c55f71ba6..6712d6549 100644 --- a/src/lym/lym/lymMacroCollection.cc +++ b/src/lym/lym/lymMacroCollection.cc @@ -38,6 +38,7 @@ #include "tlProgress.h" #include "tlFileUtils.h" #include "tlResources.h" +#include "tlEnv.h" #include "rba.h" #include "pya.h" @@ -363,6 +364,22 @@ namespace { } #endif +// Some directories are ignored in addition to dotfiles +static bool dir_is_ignored (const std::string &dn) +{ + static std::set ignored; + static bool initialized = false; + + if (! initialized) { + // a colon-separated list of directory names + std::string ign = tl::get_env ("KLAYOUT_IGNORE_MACRO_DIRS", "__pycache__"); + auto ignv = tl::split (ign, ":"); + ignored.insert (ignv.begin (), ignv.end ()); + } + + return ignored.find (dn) != ignored.end (); +} + void MacroCollection::scan () { std::string p = path (); @@ -431,6 +448,10 @@ void MacroCollection::scan () continue; } + if (dir_is_ignored (*f)) { + continue; + } + try { MacroCollection *&mc = m_folders.insert (std::make_pair (*f, (MacroCollection *) 0)).first->second; From bd7f0f395593462191c8fcc66d5f7e0e5dc4e1be Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 01:15:15 +0200 Subject: [PATCH 14/25] Macro editor: provide a 'Tabs' context menu for the tab bar which allows selecting a tab from a list --- src/lay/lay/layMacroEditorDialog.cc | 39 +++++++++++++++++++++++++++++ src/lay/lay/layMacroEditorDialog.h | 3 +++ 2 files changed, 42 insertions(+) diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index 6129038ee..d3e7c6259 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -429,6 +429,17 @@ MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection connect (action, SIGNAL (triggered ()), this, SLOT (close_all_right ())); tabWidget->addAction (action); + action = new QAction (); + action->setSeparator (true); + tabWidget->addAction (action); + + mp_tabs_menu = new QMenu (); + + action = new QAction (tr ("Tabs")); + action->setMenu (mp_tabs_menu); + connect (mp_tabs_menu, SIGNAL (aboutToShow ()), this, SLOT (tabs_menu_about_to_show ())); + tabWidget->addAction (action); + dbgOn->setEnabled (true); runButton->setEnabled (true); runThisButton->setEnabled (true); @@ -672,6 +683,34 @@ MacroEditorDialog::instance () return s_macro_editor_instance; } +void +MacroEditorDialog::tab_menu_selected () +{ + QAction *action = dynamic_cast (sender ()); + if (action) { + tabWidget->setCurrentIndex (action->data ().toInt ()); + } +} + +void +MacroEditorDialog::tabs_menu_about_to_show () +{ + mp_tabs_menu->clear (); + + for (int i = 0; i < tabWidget->count (); ++i) { + MacroEditorPage *page = dynamic_cast (tabWidget->widget (i)); + if (page) { + QAction *action = new QAction (tl::to_qstring (page->path ())); + action->setData (i); + connect (action, SIGNAL (triggered ()), this, SLOT (tab_menu_selected ())); + if (page->macro () == mp_run_macro) { + action->setIcon (QIcon (":/run_16px.png")); + } + mp_tabs_menu->addAction (action); + } + } +} + void MacroEditorDialog::select_category (const std::string &cat) { diff --git a/src/lay/lay/layMacroEditorDialog.h b/src/lay/lay/layMacroEditorDialog.h index 71c78ca02..c17f9e291 100644 --- a/src/lay/lay/layMacroEditorDialog.h +++ b/src/lay/lay/layMacroEditorDialog.h @@ -233,6 +233,8 @@ private slots: void del_watches (); void clear_watches (); void set_debugging_on (bool on); + void tabs_menu_about_to_show (); + void tab_menu_selected (); // edit trace navigation void forward (); @@ -359,6 +361,7 @@ private: std::vector m_changed_files, m_removed_files; tl::DeferredMethod dm_refresh_file_watcher; tl::DeferredMethod dm_update_ui_to_run_mode; + QMenu *mp_tabs_menu; }; } From 470c2acf5a74c0197fb09cde526f3bc7161f02ef Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 19:17:19 +0200 Subject: [PATCH 15/25] Qt4 compatibility --- src/lay/lay/layMacroEditorDialog.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index d3e7c6259..db56a738e 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -429,13 +429,13 @@ MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection connect (action, SIGNAL (triggered ()), this, SLOT (close_all_right ())); tabWidget->addAction (action); - action = new QAction (); + action = new QAction (this); action->setSeparator (true); tabWidget->addAction (action); mp_tabs_menu = new QMenu (); - action = new QAction (tr ("Tabs")); + action = new QAction (tr ("Tabs"), this); action->setMenu (mp_tabs_menu); connect (mp_tabs_menu, SIGNAL (aboutToShow ()), this, SLOT (tabs_menu_about_to_show ())); tabWidget->addAction (action); @@ -700,7 +700,7 @@ MacroEditorDialog::tabs_menu_about_to_show () for (int i = 0; i < tabWidget->count (); ++i) { MacroEditorPage *page = dynamic_cast (tabWidget->widget (i)); if (page) { - QAction *action = new QAction (tl::to_qstring (page->path ())); + QAction *action = new QAction (tl::to_qstring (page->path ()), mp_tabs_menu); action->setData (i); connect (action, SIGNAL (triggered ()), this, SLOT (tab_menu_selected ())); if (page->macro () == mp_run_macro) { From 862f8a5c65f869cbff97e0d9a802d8ccecb3e7a5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 19:18:43 +0200 Subject: [PATCH 16/25] Restored a method which was renamed by mistake --- src/db/db/gsiDeclDbEdge.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/gsiDeclDbEdge.cc b/src/db/db/gsiDeclDbEdge.cc index 05052ad78..8d1f0d94b 100644 --- a/src/db/db/gsiDeclDbEdge.cc +++ b/src/db/db/gsiDeclDbEdge.cc @@ -494,7 +494,7 @@ struct edge_defs "\n" "@return True if the edges are coincident.\n" ) + - method ("intersects?|#intersects?", &C::intersect, gsi::arg ("e"), + method ("intersects?|#intersect?", &C::intersect, gsi::arg ("e"), "@brief Intersection test. \n" "\n" "Returns true if the edges intersect. Two edges intersect if they share at least one point. \n" From 6cc796e4be2429abab3a113351d7924a9d90341c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 19:29:57 +0200 Subject: [PATCH 17/25] Fixed issue #1460 - CellRename dialog is larger now initially --- src/layui/layui/RenameCellDialog.ui | 59 +++++++++++++++-------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/layui/layui/RenameCellDialog.ui b/src/layui/layui/RenameCellDialog.ui index b0ffe9a02..2ba2c40ec 100644 --- a/src/layui/layui/RenameCellDialog.ui +++ b/src/layui/layui/RenameCellDialog.ui @@ -1,37 +1,38 @@ - + + RenameCellDialog - - + + 0 0 - 286 - 124 + 540 + 133 - + Rename Cell - - + + 9 - + 6 - - - + + + New cell name - + - + Qt::Vertical - + 268 16 @@ -39,15 +40,15 @@ - - + + - + - + Qt::Vertical - + 268 31 @@ -55,13 +56,13 @@ - - - + + + Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -79,11 +80,11 @@ RenameCellDialog accept() - + 248 254 - + 157 274 @@ -95,11 +96,11 @@ RenameCellDialog reject() - + 316 260 - + 286 274 From 81e61412452a1044fbb0c4e800915c9b4e445c18 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 19:37:43 +0200 Subject: [PATCH 18/25] Fixed issue #1464 - Layout#get_info and Layout#set_info don't segfault on wrong layer index --- src/db/db/gsiDeclDbLayout.cc | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index c4555ee99..550e8b5de 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1037,6 +1037,22 @@ static void dtransform_cplx (db::Layout *layout, const db::DCplxTrans &trans) layout->transform (dbu_trans.inverted () * trans * dbu_trans); } +static db::LayerProperties get_properties (const db::Layout *layout, unsigned int index) +{ + if (layout->is_valid_layer (index)) { + return layout->get_properties (index); + } else { + return db::LayerProperties (); + } +} + +static void set_properties (db::Layout *layout, unsigned int index, const db::LayerProperties &props) +{ + if (layout->is_valid_layer (index)) { + layout->set_properties (index, props); + } +} + Class decl_Layout ("db", "Layout", gsi::constructor ("new", &layout_ctor_with_manager, gsi::arg ("manager"), "@brief Creates a layout object attached to a manager\n" @@ -1667,11 +1683,12 @@ Class decl_Layout ("db", "Layout", "\n" "See \\insert_special_layer for a description of special layers." ) + - gsi::method ("set_info", &db::Layout::set_properties, gsi::arg ("index"), gsi::arg ("props"), + gsi::method_ext ("set_info", &set_properties, gsi::arg ("index"), gsi::arg ("props"), "@brief Sets the info structure for a specified layer\n" ) + - gsi::method ("get_info", &db::Layout::get_properties, gsi::arg ("index"), + gsi::method_ext ("get_info", &get_properties, gsi::arg ("index"), "@brief Gets the info structure for a specified layer\n" + "If the layer index is not a valid layer index, an empty LayerProperties object will be returned." ) + gsi::method ("cells", &db::Layout::cells, "@brief Returns the number of cells\n" From 4a475a7e213c9d55eb77d24df60c8f408a6579f9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 Sep 2023 23:47:40 +0200 Subject: [PATCH 19/25] Bugfix: moving a text with a non-centered vertical alignment now uses the right location --- src/edt/edt/edtService.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index 8afdf95e3..d2bb74075 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -471,6 +471,7 @@ Service::selection_bbox () // build the transformation variants cache // TODO: this is done multiple times - once for each service! TransformationVariants tv (view ()); + const db::DCplxTrans &vp = view ()->viewport ().trans (); lay::TextInfo text_info (view ()); @@ -491,7 +492,7 @@ Service::selection_bbox () if (r->shape ().is_text ()) { db::Text text; r->shape ().text (text); - box += *t * text_info.bbox (ctx_trans * text, *t); + box += *t * text_info.bbox (ctx_trans * text, vp * *t); } else { box += *t * (ctx_trans * r->shape ().bbox ()); } From 10f3380023d2089fede7c910875f553422c578c1 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 3 Sep 2023 16:14:44 +0200 Subject: [PATCH 20/25] Enhancement: 'copy interactive' will maintain selection --- src/laybasic/laybasic/layLayoutViewBase.cc | 4 +-- src/laybasic/laybasic/layLayoutViewBase.h | 2 +- src/laybasic/laybasic/layMove.cc | 32 +++++++++++++++------- src/laybasic/laybasic/layMove.h | 2 +- src/layui/layui/layLayoutViewFunctions.cc | 3 +- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 0a3a226db..27ffa530b 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -5308,7 +5308,7 @@ LayoutViewBase::paste () } void -LayoutViewBase::paste_interactive () +LayoutViewBase::paste_interactive (bool transient_mode) { clear_selection (); @@ -5320,7 +5320,7 @@ LayoutViewBase::paste_interactive () // operations. trans->close (); - if (mp_move_service->begin_move (trans.release (), false)) { + if (mp_move_service->begin_move (trans.release (), transient_mode)) { switch_mode (-1); // move mode } } diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index c86d7c771..5faf144a8 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -254,7 +254,7 @@ public: /** * @brief Pastes from clipboard and initiates a move */ - void paste_interactive (); + void paste_interactive (bool transient_mode = false); /** * @brief Copies to clipboard diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index c80dbb0e0..fb8dd5e9b 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -240,7 +240,7 @@ MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool } bool -MoveService::begin_move (db::Transaction *transaction, bool selected_after_move) +MoveService::begin_move (db::Transaction *transaction, bool transient_selection) { if (m_dragging) { return false; @@ -248,16 +248,28 @@ MoveService::begin_move (db::Transaction *transaction, bool selected_after_move) std::unique_ptr trans_holder (transaction); - bool drag_transient = ! selected_after_move; - if (! mp_editables->has_selection ()) { - // try to use the transient selection for the real one - mp_editables->transient_to_selection (); - drag_transient = true; - } + bool drag_transient = false; + + if (! transaction) { + + // unless in "continue with move" use case try to establish a selection + + if (! mp_editables->has_selection ()) { + // try to use the transient selection for the real one + mp_editables->transient_to_selection (); + drag_transient = true; + } + + if (! mp_editables->has_selection ()) { + // still nothing selected + return false; + } + + } else { + + // inherit transient selection mode from previous operation + drag_transient = transient_selection; - if (! mp_editables->has_selection ()) { - // still nothing selected - return false; } db::DBox bbox = mp_editables->selection_bbox (); diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 1aa8bd52c..a51884d6c 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -42,7 +42,7 @@ public: ~MoveService (); virtual bool configure (const std::string &name, const std::string &value); - bool begin_move (db::Transaction *transaction = 0, bool selected_after_move = true); + bool begin_move (db::Transaction *transaction = 0, bool transient_selection = false); private: virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index e2c917a89..ac988ae0e 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -1177,11 +1177,12 @@ LayoutViewFunctions::do_cm_duplicate (bool interactive) db::Clipboard::instance ().swap (saved_clipboard); try { + bool transient_mode = ! view ()->has_selection (); view ()->copy_view_objects (); view ()->clear_selection (); view ()->cancel (); if (interactive) { - view ()->paste_interactive (); + view ()->paste_interactive (transient_mode); } else { view ()->paste (); } From b2ab89214d81961e9284387b6e11588441aa9371 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 3 Sep 2023 18:05:34 +0200 Subject: [PATCH 21/25] Better integration of partial mode - for example 'interactive move' now also acts on partial selection --- src/edt/edt/edtPartialService.cc | 158 ++++++++++++++++++++++++++++++- src/edt/edt/edtPartialService.h | 20 ++++ 2 files changed, 176 insertions(+), 2 deletions(-) diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index e035b4c3a..3779cce15 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1146,8 +1146,6 @@ PartialService::move_ac () const void PartialService::deactivated () { - // clear selection when this mode is left - partial_select (db::DBox (), lay::Editable::Reset); clear_partial_transient_selection (); } @@ -2169,6 +2167,110 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, return false; } +bool +PartialService::begin_move (MoveMode mode, const db::DPoint &p, lay::angle_constraint_type ac) +{ + if (has_selection () && mode == lay::Editable::Selected) { + + m_alt_ac = ac; + + m_dragging = true; + m_keep_selection = true; + + if (is_single_point_selection ()) { + // for a single selected point we use the original point as the start location which + // allows bringing it to grid + m_current = m_start = single_selected_point (); + } else if (is_single_edge_selection ()) { + // for an edge selection use the point projected to edge as the start location which + // allows bringing it to grid + m_current = m_start = projected_to_edge (single_selected_edge (), p); + } else { + m_current = m_start = p; + } + + m_alt_ac = lay::AC_Global; + + return true; + + } else { + return false; + } +} + +void +PartialService::move (const db::DPoint &p, lay::angle_constraint_type ac) +{ + m_alt_ac = ac; + + set_cursor (lay::Cursor::size_all); + + // drag the vertex or edge/segment + if (is_single_point_selection () || is_single_edge_selection ()) { + + lay::PointSnapToObjectResult snap_details; + + // for a single selected point or edge, m_start is the original position and we snap the target - + // thus, we can bring the point on grid or to an object's edge or vertex + snap_details = snap2 (p); + if (snap_details.object_snap == lay::PointSnapToObjectResult::NoObject) { + m_current = m_start + snap (p - m_start); + } else { + m_current = snap_details.snapped_point; + mouse_cursor_from_snap_details (snap_details); + } + + } else { + + // snap movement to angle and grid without object + m_current = m_start + snap (p - m_start); + clear_mouse_cursors (); + + } + + selection_to_view (); + + m_alt_ac = lay::AC_Global; +} + +void +PartialService::end_move (const db::DPoint & /*p*/, lay::angle_constraint_type ac) +{ + m_alt_ac = ac; + + if (m_current != m_start) { + + // stop dragging + ui ()->ungrab_mouse (this); + + if (manager ()) { + manager ()->transaction (tl::to_string (tr ("Partial move"))); + } + + // heuristically, if there is just one edge selected: do not confine to the movement + // angle constraint - the edge usually is confined enough + db::DTrans move_trans = db::DTrans (m_current - m_start); + + transform_selection (move_trans); + + if (manager ()) { + manager ()->commit (); + } + + } + + if (! m_keep_selection) { + m_selection.clear (); + } + + m_dragging = false; + selection_to_view (); + + clear_mouse_cursors (); + + m_alt_ac = lay::AC_Global; +} + bool PartialService::has_selection () { @@ -2181,6 +2283,58 @@ PartialService::selection_size () return m_selection.size (); } +db::DBox +PartialService::selection_bbox () +{ + // build the transformation variants cache + // TODO: this is done multiple times - once for each service! + TransformationVariants tv (view ()); + const db::DCplxTrans &vp = view ()->viewport ().trans (); + + lay::TextInfo text_info (view ()); + + db::DBox box; + for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { + + const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); + const db::Layout &layout = cv->layout (); + + db::CplxTrans ctx_trans = db::CplxTrans (layout.dbu ()) * cv.context_trans () * r->first.trans (); + + db::box_convert bc (layout); + if (! r->first.is_cell_inst ()) { + + const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); + if (tv_list != 0) { + for (std::vector::const_iterator t = tv_list->begin (); t != tv_list->end (); ++t) { + if (r->first.shape ().is_text ()) { + db::Text text; + r->first.shape ().text (text); + box += *t * text_info.bbox (ctx_trans * text, vp * *t); + } else { + for (auto e = r->second.begin (); e != r->second.end (); ++e) { + box += *t * (ctx_trans * e->bbox ()); + } + } + } + } + + } else { + + const std::vector *tv_list = tv.per_cv (r->first.cv_index ()); + if (tv_list != 0) { + for (std::vector::const_iterator t = tv_list->begin (); t != tv_list->end (); ++t) { + box += *t * (ctx_trans * r->first.back ().bbox (bc)); + } + } + + } + + } + + return box; +} + bool PartialService::has_transient_selection () { diff --git a/src/edt/edt/edtPartialService.h b/src/edt/edt/edtPartialService.h index 749eddce5..cdc07b61d 100644 --- a/src/edt/edt/edtPartialService.h +++ b/src/edt/edt/edtPartialService.h @@ -266,6 +266,26 @@ public: */ virtual bool has_transient_selection (); + /** + * @brief Gets the selection bounding box + */ + virtual db::DBox selection_bbox (); + + /** + * @brief Start a "move" operation + */ + virtual bool begin_move (MoveMode sel, const db::DPoint &p, lay::angle_constraint_type ac); + + /** + * @brief Continue a "move" operation + */ + virtual void move (const db::DPoint &p, lay::angle_constraint_type ac); + + /** + * @brief Terminate a "move" operation + */ + virtual void end_move (const db::DPoint &p, lay::angle_constraint_type ac); + /** * @brief Implement the "select" method at least to clear the selection */ From 3a6fecc71d2906344f9099382c76cacdc5332095 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 3 Sep 2023 18:09:39 +0200 Subject: [PATCH 22/25] Bugfix: cleanup after deleting cells in partial mode - no new top cells appear. --- src/edt/edt/edtPartialService.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index 3779cce15..368ddaa39 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -2345,6 +2345,8 @@ PartialService::has_transient_selection () void PartialService::del () { + std::set needs_cleanup; + // stop dragging ui ()->ungrab_mouse (this); @@ -2426,6 +2428,9 @@ PartialService::del () if (r->first.is_cell_inst ()) { const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); if (cv.is_valid ()) { + if (cv->layout ().cell (r->first.back ().inst_ptr.cell_index ()).is_proxy ()) { + needs_cleanup.insert (& cv->layout ()); + } cv->layout ().cell (r->first.cell_index ()).erase (r->first.back ().inst_ptr); } } @@ -2439,6 +2444,11 @@ PartialService::del () m_selection.clear (); m_dragging = false; selection_to_view (); + + // clean up the layouts that need to do so. + for (std::set::const_iterator l = needs_cleanup.begin (); l != needs_cleanup.end (); ++l) { + (*l)->cleanup (); + } } lay::InstanceMarker * From 12f9ad33f43457cb8ce08e21257155d02de6b5b5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 5 Sep 2023 00:35:49 +0200 Subject: [PATCH 23/25] Some enhancements to package manager - So not update macros while installing: avoids transient error messages - Offer to autorun macros also after package update (so far only on package new installation) --- src/lay/lay/layMacroController.cc | 50 ++++++++++++++++++++----------- src/lay/lay/layMacroController.h | 5 ++-- src/lay/lay/laySaltController.cc | 29 +++++++++++++----- src/layui/layui/layBusy.cc | 6 ++++ src/tl/tl/tlFileSystemWatcher.cc | 17 +++++++++++ src/tl/tl/tlFileSystemWatcher.h | 5 ++++ 6 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/lay/lay/layMacroController.cc b/src/lay/lay/layMacroController.cc index a4cce73de..e3f3dbb76 100644 --- a/src/lay/lay/layMacroController.cc +++ b/src/lay/lay/layMacroController.cc @@ -385,9 +385,9 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) } else { // determine the paths currently in use - std::map prev_folders_by_path; + std::map prev_folders_by_path; for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { - prev_folders_by_path.insert (std::make_pair (p->path, p.operator-> ())); + prev_folders_by_path.insert (std::make_pair (p->path, *p)); } // gets the external paths (tech, packages) into m_external_paths @@ -433,23 +433,39 @@ MacroController::sync_implicit_macros (bool ask_before_autorun) for (std::vector::const_iterator p = m_external_paths.begin (); p != m_external_paths.end (); ++p) { - if (prev_folders_by_path.find (p->path) != prev_folders_by_path.end ()) { - continue; - } + auto pf = prev_folders_by_path.find (p->path); + if (pf != prev_folders_by_path.end ()) { - if (tl::verbosity () >= 20) { - tl::info << "Adding macro folder " << p->path << ", category '" << p->cat << "' for '" << p->description << "'"; - } + if (pf->second.version != p->version) { - // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in - // a non-tech context. - // In that case, the add_folder method will return 0. + if (tl::verbosity () >= 20) { + tl::info << "New version (" << p->version << " vs. " << pf->second.version << ") of macro folder " << p->path << ", category '" << p->cat << "' for '" << p->description << "'"; + } + + lym::MacroCollection *mc = lym::MacroCollection::root ().folder_by_name (p->path); + if (mc) { + new_folders.push_back (mc); + } + + } + + } else { + + if (tl::verbosity () >= 20) { + tl::info << "Adding macro folder " << p->path << ", category '" << p->cat << "' for '" << p->description << "'"; + } + + // Add the folder. Note: it may happen that a macro folder for the tech specific macros already exists in + // a non-tech context. + // In that case, the add_folder method will return 0. + + // TODO: is it wise to make this writeable? + lym::MacroCollection *mc = lym::MacroCollection::root ().add_folder (p->description, p->path, p->cat, p->readonly); + if (mc) { + mc->set_virtual_mode (p->type); + new_folders.push_back (mc); + } - // TODO: is it wise to make this writeable? - lym::MacroCollection *mc = lym::MacroCollection::root ().add_folder (p->description, p->path, p->cat, p->readonly); - if (mc) { - mc->set_virtual_mode (p->type); - new_folders.push_back (mc); } } @@ -565,7 +581,7 @@ MacroController::sync_macro_sources () if (*f != macro_categories () [c].name) { description += " - " + tl::to_string (tr ("%1 branch").arg (tl::to_qstring (*f))); } - external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].name, lym::MacroCollection::SaltFolder, g->is_readonly ())); + external_paths.push_back (ExternalPathDescriptor (tl::to_string (macro_dir.path ()), description, macro_categories () [c].name, lym::MacroCollection::SaltFolder, g->is_readonly (), g->version ())); } diff --git a/src/lay/lay/layMacroController.h b/src/lay/lay/layMacroController.h index e7a8c3a84..26e4674e7 100644 --- a/src/lay/lay/layMacroController.h +++ b/src/lay/lay/layMacroController.h @@ -204,8 +204,8 @@ private: */ struct ExternalPathDescriptor { - ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lym::MacroCollection::FolderType _type, bool _readonly) - : path (_path), description (_description), cat (_cat), type (_type), readonly (_readonly) + ExternalPathDescriptor (const std::string &_path, const std::string &_description, const std::string &_cat, lym::MacroCollection::FolderType _type, bool _readonly, const std::string &_version = std::string ()) + : path (_path), description (_description), cat (_cat), type (_type), version (_version), readonly (_readonly) { // .. nothing yet .. } @@ -214,6 +214,7 @@ private: std::string description; std::string cat; lym::MacroCollection::FolderType type; + std::string version; bool readonly; }; diff --git a/src/lay/lay/laySaltController.cc b/src/lay/lay/laySaltController.cc index 8dc078c4f..7c99e30db 100644 --- a/src/lay/lay/laySaltController.cc +++ b/src/lay/lay/laySaltController.cc @@ -26,6 +26,7 @@ #include "layConfig.h" #include "layMainWindow.h" #include "layQtTools.h" +#include "layBusy.h" #include "tlLog.h" #include @@ -138,11 +139,12 @@ SaltController::show_editor () lay::restore_dialog_state (mp_salt_dialog, s); } - // while running the dialog, don't watch file events - that would interfere with - // the changes applied by the dialog itself. - m_file_watcher->enable (false); - mp_salt_dialog->exec (); - m_file_watcher->enable (true); + { + // while running the dialog, don't watch file events - that would interfere with + // the changes applied by the dialog itself. + lay::BusySection busy_section; // disable file watcher + mp_salt_dialog->exec (); + } mp_plugin_root->config_set (cfg_salt_manager_window_state, lay::save_dialog_state (mp_salt_dialog)); @@ -154,13 +156,13 @@ SaltController::show_editor () void SaltController::sync_file_watcher () { + lay::BusySection busy_section; // disable file watcher + if (m_file_watcher) { m_file_watcher->clear (); - m_file_watcher->enable (false); for (lay::Salt::flat_iterator g = m_salt.begin_flat (); g != m_salt.end_flat (); ++g) { m_file_watcher->add_file ((*g)->path ()); } - m_file_watcher->enable (true); } } @@ -216,7 +218,18 @@ SaltController::install_packages (const std::vector &packages, bool manager.compute_packages (m_salt, salt_mine); } - return manager.execute (0, m_salt); + bool result = false; + + { + // while running the dialog, don't watch file events - that would interfere with + // the changes applied by the dialog itself. + lay::BusySection busy_section; // disable file watcher + result = manager.execute (0, m_salt); + } + + sync_file_watcher (); + + return result; } void diff --git a/src/layui/layui/layBusy.cc b/src/layui/layui/layBusy.cc index f03c8b5c2..097151f4c 100644 --- a/src/layui/layui/layBusy.cc +++ b/src/layui/layui/layBusy.cc @@ -24,6 +24,7 @@ #include "layBusy.h" #include "tlThreads.h" +#include "tlFileSystemWatcher.h" namespace lay { @@ -61,6 +62,9 @@ BusySection::BusySection () m_previous_mode = mp_busy_mode->is_busy (); mp_busy_mode->enter_busy_mode (true); } + + // disable file system watchers during busy periods + tl::FileSystemWatcher::global_enable (false); } BusySection::~BusySection () @@ -70,6 +74,8 @@ BusySection::~BusySection () mp_busy_mode->enter_busy_mode (m_previous_mode); } mp_busy_mode = 0; + + tl::FileSystemWatcher::global_enable (true); } bool diff --git a/src/tl/tl/tlFileSystemWatcher.cc b/src/tl/tl/tlFileSystemWatcher.cc index 57a099a62..59219547d 100644 --- a/src/tl/tl/tlFileSystemWatcher.cc +++ b/src/tl/tl/tlFileSystemWatcher.cc @@ -31,6 +31,9 @@ namespace tl { +// The global enable counter (<0: disable) +static int s_global_enable = 0; + // The maximum allowed processing time in seconds const double processing_time = 0.02; @@ -47,6 +50,16 @@ FileSystemWatcher::FileSystemWatcher (QObject *parent) m_batch_size = 1000; } +void +FileSystemWatcher::global_enable (bool en) +{ + if (en) { + ++s_global_enable; + } else { + --s_global_enable; + } +} + void FileSystemWatcher::enable (bool en) { @@ -120,6 +133,10 @@ FileSystemWatcher::remove_file (const std::string &path) void FileSystemWatcher::timeout () { + if (s_global_enable < 0) { + return; + } + tl::Clock start = tl::Clock::current (); if (m_iter == m_files.end ()) { diff --git a/src/tl/tl/tlFileSystemWatcher.h b/src/tl/tl/tlFileSystemWatcher.h index 22e2a448b..f6190f208 100644 --- a/src/tl/tl/tlFileSystemWatcher.h +++ b/src/tl/tl/tlFileSystemWatcher.h @@ -57,6 +57,11 @@ public: */ FileSystemWatcher (QObject *parent = 0); + /** + * @brief Global enable/disable + */ + static void global_enable (bool en); + /** * @brief Enables or disables the file watcher */ From 4d00a198621d347fc5692c02b85c42b4a43b8dd5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 5 Sep 2023 00:56:17 +0200 Subject: [PATCH 24/25] Small enhancements for macro editor dialog geometry --- src/lay/lay/MacroEditorDialog.ui | 24 +++++++++++++++++++++--- src/lay/lay/layMacroEditorPage.cc | 7 +++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/lay/lay/MacroEditorDialog.ui b/src/lay/lay/MacroEditorDialog.ui index fa02c8421..5058a5440 100644 --- a/src/lay/lay/MacroEditorDialog.ui +++ b/src/lay/lay/MacroEditorDialog.ui @@ -463,6 +463,10 @@ ... + + + :/breakpoint_16px.png:/breakpoint_16px.png + F9 @@ -614,13 +618,20 @@ 40 - 20 + 16 - + + + + 0 + 0 + + + @@ -721,7 +732,14 @@ 0 - + + + + 0 + 0 + + + diff --git a/src/lay/lay/layMacroEditorPage.cc b/src/lay/lay/layMacroEditorPage.cc index bcb8c302f..b0980c315 100644 --- a/src/lay/lay/layMacroEditorPage.cc +++ b/src/lay/lay/layMacroEditorPage.cc @@ -549,13 +549,16 @@ MacroEditorPage::MacroEditorPage (QWidget * /*parent*/, MacroEditorHighlighters mp_layout = new QVBoxLayout (this); mp_layout->setContentsMargins (0, 0, 0, 0); + QVBoxLayout *vlayout = new QVBoxLayout (this); + vlayout->setContentsMargins (4, 4, 4, 4); + mp_layout->addLayout (vlayout); + mp_readonly_label = new QLabel (this); mp_readonly_label->setText (QObject::tr ("Macro is read-only and cannot be edited")); mp_readonly_label->hide (); - mp_layout->addWidget (mp_readonly_label); + vlayout->addWidget (mp_readonly_label); QHBoxLayout *hlayout = new QHBoxLayout (); - hlayout->setContentsMargins (4, 4, 4, 4); mp_layout->addLayout (hlayout); mp_exec_model = new MacroEditorExecutionModel (this); From c6d7b3e7fca4820842a9117041433961e50de0e9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 6 Sep 2023 21:30:02 +0200 Subject: [PATCH 25/25] Fixed a issue with the search box on the macro editor: when switching to the replace text box, focus was passed to the text editor instead --- src/lay/lay/layMacroEditorDialog.cc | 12 +----------- src/lay/lay/layMacroEditorDialog.h | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/lay/lay/layMacroEditorDialog.cc b/src/lay/lay/layMacroEditorDialog.cc index db56a738e..193de09cd 100644 --- a/src/lay/lay/layMacroEditorDialog.cc +++ b/src/lay/lay/layMacroEditorDialog.cc @@ -254,7 +254,6 @@ MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection m_first_show (true), m_debugging_on (true), mp_run_macro (0), md_update_console_text (this, &MacroEditorDialog::update_console_text), - md_search_edited (this, &MacroEditorDialog::do_search_edited), m_in_event_handler (false), m_os (OS_none), m_new_line (true), @@ -2099,14 +2098,6 @@ MacroEditorDialog::search_editing () } } -void -MacroEditorDialog::search_edited () -{ - // since we want to move the focus to the text field, we have to do this in the deferred method - // (this method is called from an event handler and setFocus does not have an effect then) - md_search_edited (); -} - void MacroEditorDialog::search_finished () { @@ -2120,7 +2111,7 @@ MacroEditorDialog::search_finished () } void -MacroEditorDialog::do_search_edited () +MacroEditorDialog::search_edited () { MacroEditorPage *page = dynamic_cast (tabWidget->currentWidget ()); if (! page) { @@ -2132,7 +2123,6 @@ MacroEditorDialog::do_search_edited () if (! page->has_multi_block_selection ()) { page->find_next (); } - set_editor_focus (); } void diff --git a/src/lay/lay/layMacroEditorDialog.h b/src/lay/lay/layMacroEditorDialog.h index c17f9e291..7dad91b4d 100644 --- a/src/lay/lay/layMacroEditorDialog.h +++ b/src/lay/lay/layMacroEditorDialog.h @@ -314,7 +314,6 @@ private: lym::Macro *mp_run_macro; std::vector m_macro_templates; tl::DeferredMethod md_update_console_text; - tl::DeferredMethod md_search_edited; TextEditWidget *mp_console_text; std::map m_tab_widgets; int m_history_index;