Merge pull request #1466 from KLayout/wip2

Wip2
This commit is contained in:
Matthias Köfferlein 2023-09-08 00:41:52 +02:00 committed by GitHub
commit be1be467d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1253 additions and 485 deletions

View File

@ -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 ());

View File

@ -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?|#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"
"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"

View File

@ -425,7 +425,7 @@ Class<db::EdgePairs> 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<db::EdgePairs> 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<db::EdgePairs> 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"

View File

@ -769,7 +769,7 @@ Class<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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<db::Edges> 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"

View File

@ -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<db::Layout> 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<db::Layout> 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"

View File

@ -1647,15 +1647,18 @@ Class<db::Region> 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<db::Region> 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<db::Region> 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<size_t>::max ()), "unlimited"),
"@brief Returns the polygons of this region which are completely covering polygons from the other region\n"

View File

@ -276,20 +276,27 @@ Class<db::Texts> 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"

View File

@ -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;

View File

@ -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))

View File

@ -19,7 +19,7 @@ module DRC
class DRCExecutable &lt; 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 &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{@macro.path}")
@drc.instance_eval(@macro.text, @macro.path)
@drc.instance_eval(expanded_text, encoded_file)
rescue =&gt; ex
@ -66,13 +70,40 @@ module DRC
end
# A recipe implementation allowing the DRC run to be redone
class DRCRecipe &lt; 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 &lt; 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 &lt; 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 &lt; 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) &amp;&amp; RBA::Application::instance

View File

@ -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<db::CellInst> bc (layout);
if (! r->first.is_cell_inst ()) {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ());
if (tv_list != 0) {
for (std::vector<db::DCplxTrans>::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<db::DCplxTrans> *tv_list = tv.per_cv (r->first.cv_index ());
if (tv_list != 0) {
for (std::vector<db::DCplxTrans>::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 ()
{
@ -2191,6 +2345,8 @@ PartialService::has_transient_selection ()
void
PartialService::del ()
{
std::set<db::Layout *> needs_cleanup;
// stop dragging
ui ()->ungrab_mouse (this);
@ -2272,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);
}
}
@ -2285,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<db::Layout *>::const_iterator l = needs_cleanup.begin (); l != needs_cleanup.end (); ++l) {
(*l)->cleanup ();
}
}
lay::InstanceMarker *

View File

@ -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
*/

View File

@ -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 ());
}

View File

@ -463,6 +463,10 @@
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../icons/icons.qrc">
<normaloff>:/breakpoint_16px.png</normaloff>:/breakpoint_16px.png</iconset>
</property>
<property name="shortcut">
<string>F9</string>
</property>
@ -614,13 +618,20 @@
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="lay::DecoratedLineEdit" name="searchEditBox"/>
<widget class="lay::DecoratedLineEdit" name="searchEditBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="findNextButton">
@ -721,7 +732,14 @@
<number>0</number>
</property>
<item>
<widget class="lay::DecoratedLineEdit" name="replaceText"/>
<widget class="lay::DecoratedLineEdit" name="replaceText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="replaceNextButton">

View File

@ -1,7 +1,8 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TechMacrosPage</class>
<widget class="QWidget" name="TechMacrosPage" >
<property name="geometry" >
<widget class="QWidget" name="TechMacrosPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
@ -9,94 +10,86 @@
<height>492</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>9</number>
</property>
<item>
<widget class="QLabel" name="title_label" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>0</vsizetype>
<widget class="QLabel" name="title_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<property name="text">
<string>%CAT_DESC%</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="macro_frame" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<widget class="QFrame" name="macro_frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeView" name="folder_tree" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>7</vsizetype>
<widget class="QTreeView" name="folder_tree">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QFrame" name="frame" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin" stdset="0">
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="macro_text" >
<property name="lineWrapMode" >
<widget class="QTextBrowser" name="macro_text">
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
</widget>
@ -109,70 +102,68 @@
</widget>
</item>
<item>
<widget class="QLabel" name="note_label" >
<property name="text" >
<string>&lt;html>&lt;body>&lt;span style=" font-weight:600;">Note&lt;/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.&lt;/p>&lt;/body>&lt;/html></string>
<widget class="QLabel" name="note_label">
<property name="text">
<string>&lt;html&gt;&lt;body&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: to edit, add or delete scripts, use the macro development facility in &quot;Tools/Macro Development&quot;. You will find a branch for this technology there.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap" >
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="empty_label1" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<widget class="QLabel" name="empty_label1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>&lt;html>&lt;body>&lt;span style=" font-weight:600;">Note&lt;/span>: this page is empty because no valid base path is set.&lt;br/>Select a base path on the "General" page to establish one.&lt;/p>&lt;/body>&lt;/html></string>
<property name="text">
<string>&lt;html&gt;&lt;body&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: this page is empty because no valid base path is set.&lt;br/&gt;Select a base path on the &quot;General&quot; page to establish one.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment" >
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap" >
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="empty_label2_frame" >
<property name="minimumSize" >
<widget class="QFrame" name="empty_label2_frame">
<property name="minimumSize">
<size>
<width>16</width>
<height>200</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<layout class="QGridLayout">
<property name="margin" stdset="0">
<number>0</number>
</property>
<property name="spacing" >
<property name="spacing">
<number>6</number>
</property>
<item row="2" column="1" >
<widget class="QPushButton" name="create_folder_button" >
<property name="text" >
<item row="2" column="1">
<widget class="QPushButton" name="create_folder_button">
<property name="text">
<string>Create Folder Now</string>
</property>
</widget>
</item>
<item row="2" column="2" >
<item row="2" column="2">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>211</width>
<height>20</height>
@ -180,12 +171,12 @@
</property>
</spacer>
</item>
<item row="2" column="0" >
<item row="2" column="0">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>231</width>
<height>20</height>
@ -193,12 +184,12 @@
</property>
</spacer>
</item>
<item row="3" column="0" colspan="3" >
<item row="3" column="0" colspan="3">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>651</width>
<height>40</height>
@ -206,12 +197,12 @@
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3" >
<item row="0" column="0" colspan="3">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>671</width>
<height>40</height>
@ -219,23 +210,21 @@
</property>
</spacer>
</item>
<item row="1" column="0" colspan="3" >
<widget class="QLabel" name="empty_label2" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>5</vsizetype>
<item row="1" column="0" colspan="3">
<widget class="QLabel" name="empty_label2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>&lt;html>&lt;body>&lt;span style=" font-weight:600;">Note&lt;/span>: this page is empty, because the corresponding macro folder&lt;/p>&lt;p style=" margin-top:5px; margin-bottom:5px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;i>%BASE_PATH%/%CAT%&lt;/i>&lt;/p>&lt;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.&lt;/p>&lt;/body>&lt;/html></string>
<property name="text">
<string>&lt;html&gt;&lt;body&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: this page is empty, because the corresponding macro folder&lt;/p&gt;&lt;p style=&quot; margin-top:5px; margin-bottom:5px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;i&gt;%BASE_PATH%/%CAT%&lt;/i&gt;&lt;/p&gt;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;cannot be found or is not accessible.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment" >
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap" >
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
@ -244,15 +233,15 @@
</widget>
</item>
<item>
<widget class="QLabel" name="empty_label3" >
<property name="text" >
<string>&lt;html>&lt;body>
&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;span style=" font-weight:600;">Note&lt;/span>: this page is empty, because the macro location is shared by a different macro site (specifically "&lt;i>%ALT_DESC%&lt;/i>"). Such a site cannot be made technology specific. Choose a base path that points to a different location.&lt;/p>&lt;/body>&lt;/html></string>
<widget class="QLabel" name="empty_label3">
<property name="text">
<string>&lt;html&gt;&lt;body&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note&lt;/span&gt;: this page is empty, because the macro location is shared by a different macro site (specifically &quot;&lt;i&gt;%ALT_DESC%&lt;/i&gt;&quot;). Such a site cannot be made technology specific. Choose a base path that points to a different location.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment" >
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap" >
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>

View File

@ -385,9 +385,9 @@ MacroController::sync_implicit_macros (bool ask_before_autorun)
} else {
// determine the paths currently in use
std::map<std::string, const ExternalPathDescriptor *> prev_folders_by_path;
std::map<std::string, ExternalPathDescriptor> prev_folders_by_path;
for (std::vector<ExternalPathDescriptor>::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<ExternalPathDescriptor>::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 ()));
}

View File

@ -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;
};

View File

@ -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),
@ -429,6 +428,17 @@ MacroEditorDialog::MacroEditorDialog (lay::Dispatcher *pr, lym::MacroCollection
connect (action, SIGNAL (triggered ()), this, SLOT (close_all_right ()));
tabWidget->addAction (action);
action = new QAction (this);
action->setSeparator (true);
tabWidget->addAction (action);
mp_tabs_menu = new QMenu ();
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);
dbgOn->setEnabled (true);
runButton->setEnabled (true);
runThisButton->setEnabled (true);
@ -672,6 +682,34 @@ MacroEditorDialog::instance ()
return s_macro_editor_instance;
}
void
MacroEditorDialog::tab_menu_selected ()
{
QAction *action = dynamic_cast<QAction *> (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<MacroEditorPage *> (tabWidget->widget (i));
if (page) {
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) {
action->setIcon (QIcon (":/run_16px.png"));
}
mp_tabs_menu->addAction (action);
}
}
}
void
MacroEditorDialog::select_category (const std::string &cat)
{
@ -1770,8 +1808,6 @@ MacroEditorDialog::current_tab_changed (int index)
}
}
// clear the search
searchEditBox->clear ();
replaceFrame->setEnabled (page && page->macro () && !page->macro ()->is_readonly ());
apply_search ();
@ -2062,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 ()
{
@ -2083,7 +2111,7 @@ MacroEditorDialog::search_finished ()
}
void
MacroEditorDialog::do_search_edited ()
MacroEditorDialog::search_edited ()
{
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->currentWidget ());
if (! page) {
@ -2095,7 +2123,6 @@ MacroEditorDialog::do_search_edited ()
if (! page->has_multi_block_selection ()) {
page->find_next ();
}
set_editor_focus ();
}
void
@ -2375,6 +2402,15 @@ BEGIN_PROTECTED
END_PROTECTED
}
void
MacroEditorDialog::close_requested ()
{
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (sender ());
if (! m_in_exec && page) {
tab_close_requested (tabWidget->indexOf (page));
}
}
void
MacroEditorDialog::tab_close_requested (int index)
{
@ -2607,66 +2643,65 @@ BEGIN_PROTECTED
return;
}
std::set<std::string> modified_files;
for (std::map <lym::Macro *, MacroEditorPage *>::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<std::string, MacroEditorPage *> path_to_page;
for (int i = 0; i < tabWidget->count (); ++i) {
MacroEditorPage *page = dynamic_cast<MacroEditorPage *> (tabWidget->widget (i));
if (page) {
path_to_page.insert (std::make_pair (page->path (), page));
}
}
std::vector<QString> conflicts;
for (std::vector<QString>::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<QString>::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<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f);
}
for (std::vector<QString>::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<QString>::const_iterator f = m_changed_files.begin (); f != m_changed_files.end (); ++f) {
msg += QString::fromUtf8 (" %1 (modified)\n").arg (*f);
}
for (std::vector<QString>::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<QString>::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
}
@ -2790,7 +2825,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")));
}
@ -3525,6 +3560,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)));

View File

@ -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 ();
@ -232,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 ();
@ -311,7 +314,6 @@ private:
lym::Macro *mp_run_macro;
std::vector<lym::Macro *> m_macro_templates;
tl::DeferredMethod<MacroEditorDialog> md_update_console_text;
tl::DeferredMethod<MacroEditorDialog> md_search_edited;
TextEditWidget *mp_console_text;
std::map <lym::Macro *, MacroEditorPage *> m_tab_widgets;
int m_history_index;
@ -358,6 +360,7 @@ private:
std::vector<QString> m_changed_files, m_removed_files;
tl::DeferredMethod<MacroEditorDialog> dm_refresh_file_watcher;
tl::DeferredMethod<MacroEditorDialog> dm_update_ui_to_run_mode;
QMenu *mp_tabs_menu;
};
}

View File

@ -46,6 +46,8 @@
#include <QTimer>
#include <QListWidget>
#include <QApplication>
#include <QToolButton>
#include <QPushButton>
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,20 @@ 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);
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 ();
layout->addWidget (mp_readonly_label);
vlayout->addWidget (mp_readonly_label);
QHBoxLayout *hlayout = new QHBoxLayout ();
layout->addLayout (hlayout);
mp_layout->addLayout (hlayout);
mp_exec_model = new MacroEditorExecutionModel (this);
mp_text = new MacroEditorTextWidget (this);
@ -1021,6 +1081,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 +1986,54 @@ MacroEditorPage::eventFilter (QObject *watched, QEvent *event)
return false;
}
void
MacroEditorPage::add_notification (const MacroEditorNotification &notification)
{
if (m_notification_widgets.find (&notification) == 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 &notification)
{
auto nw = m_notification_widgets.find (&notification);
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 &notification, 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 ();
}
}
}

View File

@ -27,6 +27,7 @@
#include "lymMacro.h"
#include "layGenericSyntaxHighlighter.h"
#include "tlVariant.h"
#include <QDialog>
#include <QPixmap>
@ -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 &parameter = 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<std::pair<std::string, std::string> > &actions () const
{
return m_actions;
}
const std::string &name () const
{
return m_name;
}
const std::string &title () const
{
return m_title;
}
const tl::Variant &parameter () 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<std::pair<std::string, std::string> > 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<QObject *, std::string> 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 &notificaton);
void remove_notification (const MacroEditorNotification &notificaton);
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<MacroEditorNotification> m_notifications;
std::map<const MacroEditorNotification *, QWidget *, CompareNotificationPointers> 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 &notification, const std::string &action);
bool eventFilter (QObject *watched, QEvent *event);
};

View File

@ -26,6 +26,7 @@
#include "layConfig.h"
#include "layMainWindow.h"
#include "layQtTools.h"
#include "layBusy.h"
#include "tlLog.h"
#include <QDir>
@ -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<std::string> &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

View File

@ -27,6 +27,8 @@
#include "tlException.h"
#include "tlString.h"
#include "tlLog.h"
#include "tlFileUtils.h"
#include "tlStream.h"
#ifdef _WIN32
# include <windows.h>
@ -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<CrashMessage> 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<CrashMessage> 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);
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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

View File

@ -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<db::Transaction> 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 ();

View File

@ -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);

View File

@ -1,37 +1,38 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RenameCellDialog</class>
<widget class="QDialog" name="RenameCellDialog" >
<property name="geometry" >
<widget class="QDialog" name="RenameCellDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>124</height>
<width>540</width>
<height>133</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string>Rename Cell</string>
</property>
<layout class="QGridLayout" >
<property name="margin" >
<layout class="QGridLayout">
<property name="margin" stdset="0">
<number>9</number>
</property>
<property name="spacing" >
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0" >
<widget class="QLabel" name="label" >
<property name="text" >
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>New cell name </string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2" >
<item row="0" column="0" colspan="2">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>268</width>
<height>16</height>
@ -39,15 +40,15 @@
</property>
</spacer>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="name_le" />
<item row="1" column="1">
<widget class="QLineEdit" name="name_le"/>
</item>
<item row="2" column="0" colspan="2" >
<item row="2" column="0" colspan="2">
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>268</width>
<height>31</height>
@ -55,13 +56,13 @@
</property>
</spacer>
</item>
<item row="3" column="0" colspan="2" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@ -79,11 +80,11 @@
<receiver>RenameCellDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
@ -95,11 +96,11 @@
<receiver>RenameCellDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>

View File

@ -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

View File

@ -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 ();
}

View File

@ -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);

View File

@ -19,7 +19,7 @@ module LVS
class LVSExecutable &lt; 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 &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{@macro.path}")
@lvs.instance_eval(@macro.text, @macro.path)
@lvs.instance_eval(expanded_text, encoded_file)
rescue =&gt; ex
@ -66,13 +70,40 @@ module LVS
end
# A recipe implementation allowing the LVS run to be redone
class LVSRecipe &lt; 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 &lt; 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 &lt; 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 &lt; 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) &amp;&amp; RBA::Application::instance

View File

@ -247,6 +247,18 @@ private:
bool m_supports_include_expansion;
};
static std::vector<std::string>
include_expansion (MacroInterpreterImpl *interp, lym::Macro *macro)
{
std::vector<std::string> res;
auto sp = interp->include_expansion (macro);
res.push_back (sp.first);
res.push_back (sp.second);
return res;
}
gsi::EnumIn<lym::Macro, lym::Macro::Format> decl_FormatEnum ("lay", "Format",
gsi::enum_const ("PlainTextFormat", lym::Macro::PlainTextFormat,
"@brief The macro has plain text format"
@ -298,6 +310,13 @@ Class<MacroInterpreterImpl> 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"

View File

@ -38,6 +38,7 @@
#include "tlProgress.h"
#include "tlFileUtils.h"
#include "tlResources.h"
#include "tlEnv.h"
#include "rba.h"
#include "pya.h"
@ -262,7 +263,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 +279,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;
@ -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 <std::string> 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;
@ -831,7 +852,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

View File

@ -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

View File

@ -130,31 +130,15 @@ MacroInterpreter::execute_macro (const lym::Macro *macro)
if (cls.current_name () == macro->dsl_interpreter ()) {
std::pair<std::string, std::string> et = cls->include_expansion (macro);
if (et.first.empty () || et.first == macro->path ()) {
std::unique_ptr<tl::Executable> 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<tl::Executable> eo (cls->executable (&tmp_macro));
if (eo.get ()) {
eo->do_execute ();
}
std::unique_ptr<tl::Executable> 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 () + "'");

View File

@ -19,13 +19,15 @@ module D25
class D25Executable &lt; 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 &gt;= 10 &amp;&amp; RBA::Logger::info("Running #{@macro.path}")
@d25.instance_eval(@macro.text, @macro.path)
@d25.instance_eval(expanded_text, encoded_file)
rescue =&gt; ex
@ -67,13 +71,40 @@ module D25
end
# A recipe implementation allowing the D25 run to be redone
class D25Recipe &lt; 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 &lt; 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 &lt; 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 &lt; 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

View File

@ -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<std::string> (PyFrame_GetCode(frame)->co_filename, true)) {
fn = normalize_path (python2c<std::string> (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;

View File

@ -115,13 +115,15 @@ rba_get_backtrace_from_array (VALUE backtrace, std::vector<tl::BacktraceElement>
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 <tl::BacktraceElement> 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;

View File

@ -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 ()) {

View File

@ -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
*/

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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