klayout/src/edt/edtPartialService.cc

2782 lines
85 KiB
C++

/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <QMessageBox>
#include "dbVector.h"
#include "layLayoutView.h"
#include "laySnap.h"
#include "layFinder.h"
#include "tlProgress.h"
#include "edtPartialService.h"
#include "edtService.h"
#include "edtConfig.h"
#include "edtDialogs.h"
#include "edtEditorOptionsPages.h"
#include <cmath>
namespace tl
{
class Progress;
}
namespace edt
{
// max. number of tries in single-click selection before giving up
static int point_sel_tests = 10000; // TODO: is this a reasonable value?
// -------------------------------------------------------------
/**
* @brief A move constraint
*
* A move constraint describes the degrees of freedom for a single point.
* Such a constraint can be: fixed (no freedom), unconstrained (point can move
* both in x and y direction) and freedom along an axis.
*/
class Constraint
{
public:
typedef enum { Free, Fixed, OneDim } constraint_mode;
/**
* @brief Construct an unconstrained constraint
*/
Constraint ()
: m_mode (Free)
{ }
/**
* @brief Construct an "fixed" constraint
*/
Constraint (int)
: m_mode (Fixed)
{ }
/**
* @brief Construct an "1-dimensional" constraint
*
* @param axis Gives the direction in which the point can move, if 0 same as fixed constraint
*/
Constraint (db::Vector axis)
: m_axis (axis)
{
if (axis == db::Vector ()) {
m_mode = Fixed;
} else {
m_mode = OneDim;
}
}
/**
* @brief Type accessor
*/
constraint_mode mode () const
{
return m_mode;
}
/**
* @brief Add constraints
*
* Merging of constraints means to allow movement additionally in the same directions
* than given by the second constraint.
*/
Constraint &operator+= (const Constraint &b)
{
if (m_mode == Fixed || b.mode () == Free) {
*this = b;
} else if (m_mode == Free || b.mode () == Fixed) {
// nothing to do.
} else {
// must both be OneDim here.
if (db::vprod_sign (m_axis, b.m_axis) != 0) {
m_mode = Free;
}
}
return *this;
}
/**
* @brief Unite constraints
*
* Additionally impose a constraint on this movement.
*/
Constraint &operator*= (const Constraint &b)
{
if (m_mode == Free || b.mode () == Fixed) {
*this = b;
} else if (m_mode == Fixed || b.mode () == Free) {
// nothing to do.
} else {
// must both be OneDim here.
if (db::vprod_sign (m_axis, b.m_axis) != 0) {
m_mode = Fixed;
}
}
return *this;
}
/**
* @brief Move a point by the given vector, given the imposed constraints
*
* The movement is performed "as far as possible", i.e. projecting the axis to the
* move vector, not vice versa.
*
* @param p The point to move
* @param v The vector by which to move (desired direction)
* @return first: true, if the move was successful, second: the resulting point
*/
std::pair<bool, db::Point> move (const db::Point &p, const db::DVector &v)
{
if (v == db::DVector ()) {
return std::make_pair (true, p);
} else if (m_mode == Free) {
return std::make_pair (true, p + db::Vector (v));
} else if (m_mode == Fixed) {
return std::make_pair (true, p);
} else {
double proj = db::sprod (db::DVector (m_axis), v);
#if 0
// HINT: disallowing movements when constraint axis and edge form a small angle
// leads to strange results when moving rounded corner segments as a whole for example.
// Therefore the creation of artefacts in this case is not recommended
// There is other code that takes care of the case that edges get very long
// beyond a certain threshold.
// do not allow movement for small angles to avoid creation of nasty spikes
if (fabs (proj) < m_axis.double_length () * v.double_length () * 0.1) {
return std::make_pair (false, p);
} else {
// check for overflow and return false if that happens
db::DPoint dp = db::DPoint (p) + db::DVector (m_axis) * (v.sq_double_length () / proj);
if (dp.x () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.x () >= double (std::numeric_limits<db::Point::coord_type>::max ()) ||
dp.y () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.y () >= double (std::numeric_limits<db::Point::coord_type>::max ())) {
return std::make_pair (false, p);
} else {
return std::make_pair (true, db::Point::from_double (dp));
}
}
#else
// check for overflow and return false if that happens
db::DPoint dp = db::DPoint (p) + db::DVector (m_axis) * (v.sq_double_length () / proj);
if (dp.x () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.x () >= double (std::numeric_limits<db::Point::coord_type>::max ()) ||
dp.y () <= double (std::numeric_limits<db::Point::coord_type>::min ()) || dp.y () >= double (std::numeric_limits<db::Point::coord_type>::max ())) {
return std::make_pair (false, p);
} else {
return std::make_pair (true, db::Point (dp));
}
#endif
}
}
/**
* @brief Transform by a given transformation
*/
template <class T>
Constraint &transform (const T &t)
{
m_axis.transform (t);
return *this;
}
/**
* @brief Return the transformed version
*/
template <class T>
Constraint transformed (const T &t) const
{
Constraint c (*this);
return c.transform (t);
}
private:
constraint_mode m_mode;
db::Vector m_axis;
};
// -------------------------------------------------------------
// Some utilities
static bool
insert_point_path (const db::Path &p, const std::set<EdgeWithIndex> &sel, db::Point &ins, db::Path &new_path)
{
new_path.width (p.width ());
new_path.round (p.round ());
new_path.extensions (p.bgn_ext (), p.end_ext ());
std::vector<db::Point> ctr;
ctr.reserve (p.points () + 1);
bool found = false;
unsigned int n = 0;
for (db::Path::iterator pt = p.begin (); pt != p.end (); ++n) {
db::Point p1 = *pt;
++pt;
if (pt != p.end ()) {
db::Point p2 = *pt;
ctr.push_back (p1);
if (! found && sel.find (EdgeWithIndex (db::Edge (p1, p2), n, n + 1, 0)) != sel.end ()) {
// project the point onto the edge
std::pair <bool, db::Point> projected = db::Edge (p1, p2).projected (ins);
if (projected.first) {
ins = projected.second;
ctr.push_back (ins);
found = true;
}
}
} else {
ctr.push_back (p1);
}
}
if (found) {
new_path.assign (ctr.begin (), ctr.end ());
}
return found;
}
static void
assign_path_compressed (db::Path &p, std::vector <db::Point> &ctr)
{
// compress contour (remove redundant points)
// and assign to path
std::vector<db::Point>::iterator wp = ctr.begin ();
std::vector<db::Point>::iterator wp0 = wp;
std::vector<db::Point>::const_iterator rp = ctr.begin ();
db::Point pm1 = *rp;
if (wp != ctr.end ()) {
++wp;
++rp;
while (rp != ctr.end ()) {
db::Point p0 = *rp;
++rp;
if (rp != ctr.end ()) {
db::Point pp1 = *rp;
if (! (db::vprod_sign (pp1 - p0, p0 - pm1) == 0 && db::sprod_sign (pp1 - p0, p0 - pm1) >= 0)) {
*wp++ = p0;
pm1 = p0;
}
} else {
*wp++ = p0;
}
}
}
p.assign (wp0, wp);
}
static db::Path
del_points_path (const db::Path &p, const std::set<EdgeWithIndex> &sel)
{
db::Path new_path;
new_path.width (p.width ());
new_path.round (p.round ());
new_path.extensions (p.bgn_ext (), p.end_ext ());
std::vector<db::Point> ctr;
ctr.reserve (p.points ());
unsigned int n = 0;
for (db::Path::iterator pt = p.begin (); pt != p.end (); ++pt, ++n) {
db::Point p1 = *pt;
if (sel.find (EdgeWithIndex (db::Edge (p1, p1), n, n, 0)) == sel.end ()) {
ctr.push_back (p1);
}
}
assign_path_compressed (new_path, ctr);
return new_path;
}
static void
modify_path (db::Path &p, const std::map <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, bool compress = false)
{
std::vector<db::Point> ctr;
ctr.reserve (p.points ());
std::map <PointWithIndex, db::Point>::const_iterator np;
std::map <EdgeWithIndex, db::Edge>::const_iterator ne;
unsigned int n = 0;
for (db::Path::iterator pt = p.begin (); pt != p.end (); ++n) {
db::Point p1 = *pt;
db::Point p1org = p1;
np = new_points.find (PointWithIndex (p1, n, 0));
if (np != new_points.end ()) {
p1 = np->second;
}
++pt;
if (pt != p.end ()) {
db::Point p2 = *pt;
db::Point p2org = p2;
np = new_points.find (PointWithIndex (p2, n + 1, 0));
if (np != new_points.end ()) {
p2 = np->second;
}
ne = new_edges.find (EdgeWithIndex (db::Edge (p1org, p2org), n, n + 1, 0));
ctr.push_back (p1);
if (ne != new_edges.end () && ne->second.p1 () != p1) {
ctr.push_back (ne->second.p1 ());
}
if (ne != new_edges.end () && ne->second.p2 () != p2) {
ctr.push_back (ne->second.p2 ());
}
} else {
ctr.push_back (p1);
}
}
if (compress) {
assign_path_compressed (p, ctr);
} else {
p.assign (ctr.begin (), ctr.end ());
}
}
bool
insert_point_poly (const db::Polygon &p, const std::set<EdgeWithIndex> &sel, db::Point &ins, db::Polygon &new_poly)
{
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
bool found = false;
std::vector<db::Point> ctr;
size_t points = p.contour (c).size ();
ctr.reserve (points + 1);
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
ctr.push_back ((*e).p1 ());
if (! found && sel.find (EdgeWithIndex (*e, n, nn, c)) != sel.end ()) {
// project the point onto the edge
std::pair <bool, db::Point> projected = (*e).projected (ins);
if (projected.first) {
ins = projected.second;
ctr.push_back (ins);
found = true;
}
}
}
if (found) {
new_poly = p;
if (c == 0) {
new_poly.assign_hull (ctr.begin (), ctr.end (), false /*don't compress*/);
} else {
new_poly.assign_hole (c - 1, ctr.begin (), ctr.end (), false /*don't compress*/);
}
return true;
}
}
return false;
}
static db::Polygon
del_points_poly (const db::Polygon &p, const std::set<EdgeWithIndex> &sel)
{
db::Polygon new_poly = p;
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
std::vector<db::Point> ctr;
size_t points = p.contour (c).size ();
ctr.reserve (points);
unsigned int n = 0;
for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); ++e, ++n) {
db::Point p1 = (*e).p1 ();
if (sel.find (EdgeWithIndex (db::Edge (p1, p1), n, n, c)) == sel.end ()) {
ctr.push_back (p1);
}
}
if (c == 0) {
new_poly.assign_hull (ctr.begin (), ctr.end (), true /*compress*/);
} else {
new_poly.assign_hole (c - 1, ctr.begin (), ctr.end (), true /*compress*/);
}
}
return new_poly;
}
static void
modify_polygon (db::Polygon &p,
const std::map <PointWithIndex, db::Point> &new_points,
const std::map <EdgeWithIndex, db::Edge> &new_edges,
bool compress = false)
{
for (unsigned int c = 0; c < p.holes () + 1; ++c) {
std::vector<db::Point> ctr;
size_t points = p.contour (c).size ();
ctr.reserve (points);
std::map <PointWithIndex, db::Point>::const_iterator np;
std::map <EdgeWithIndex, db::Edge>::const_iterator ne;
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = p.begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
db::Point p1 = (*e).p1 ();
np = new_points.find (PointWithIndex (p1, n, c));
if (np != new_points.end ()) {
p1 = np->second;
}
db::Point p2 = (*e).p2 ();
np = new_points.find (PointWithIndex (p2, nn, c));
if (np != new_points.end ()) {
p2 = np->second;
}
ne = new_edges.find (EdgeWithIndex (*e, n, nn, c));
ctr.push_back (p1);
if (ne != new_edges.end () && ne->second.p1 () != p1) {
ctr.push_back (ne->second.p1 ());
}
if (ne != new_edges.end () && ne->second.p2 () != p2) {
ctr.push_back (ne->second.p2 ());
}
}
if (c == 0) {
p.assign_hull (ctr.begin (), ctr.end (), compress);
} else {
p.assign_hole (c - 1, ctr.begin (), ctr.end (), compress);
}
}
}
static void
constrain (std::map <PointWithIndex, Constraint> &constr, const EdgeWithIndex &edge)
{
constr.insert (std::make_pair (edge.pi1 (), Constraint ())).first->second *= Constraint (edge.d ());
constr.insert (std::make_pair (edge.pi2 (), Constraint ())).first->second *= Constraint (edge.d ());
}
static void
create_shift_sets (const db::Shape &shape, const std::set <EdgeWithIndex> &sel, std::map <PointWithIndex, db::Point> &new_points, std::map <EdgeWithIndex, db::Edge> &new_edges, db::Vector mv)
{
// Set up a map of new edges and new points
for (std::set <EdgeWithIndex>::const_iterator e = sel.begin (); e != sel.end (); ++e) {
if (e->p1 () != e->p2 ()) {
new_edges.insert (std::make_pair (*e, db::Edge (*e)));
} else {
new_points.insert (std::make_pair (PointWithIndex (e->p1 (), e->n, e->c), e->p1 ()));
}
}
// new_points should only contain the selected points, not the start and end points of selected edges
for (std::set <EdgeWithIndex>::const_iterator e = sel.begin (); e != sel.end (); ++e) {
if (e->p1 () != e->p2 ()) {
new_points.erase (e->pi1 ());
new_points.erase (e->pi2 ());
}
}
std::map <PointWithIndex, Constraint> point_constr;
if (shape.is_polygon ()) {
for (unsigned int c = 0; c < shape.holes () + 1; ++c) {
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = shape.begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
if ((*e).p1 () != (*e).p2 () && sel.find (EdgeWithIndex (*e, n, nn, c)) == sel.end ()) {
constrain (point_constr, EdgeWithIndex (*e, n, nn, c));
}
}
}
} else if (shape.is_path ()) {
if (shape.begin_point () != shape.end_point ()) {
db::Shape::point_iterator pt = shape.begin_point ();
db::Point p1 = *pt;
unsigned int n = 0;
while (true) {
++pt;
if (pt == shape.end_point ()) {
break;
}
EdgeWithIndex e (db::Edge (p1, *pt), n, n + 1, 0);
if (e.p1 () != e.p2 () && sel.find (e) == sel.end ()) {
constrain (point_constr, e);
}
p1 = *pt;
++n;
}
}
} else if (shape.is_box ()) {
// convert to polygon and test those edges
db::Polygon poly (shape.box ());
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
EdgeWithIndex ewi (*e, n, nn, 0);
if ((*e).p1 () != (*e).p2 () && sel.find (ewi) == sel.end ()) {
// add some moveable edges to impose manhattan constraints
if (new_points.find (ewi.pi1 ()) != new_points.end () ||
new_points.find (ewi.pi2 ()) != new_points.end ()) {
new_edges.insert (std::make_pair (ewi, db::Edge ((*e))));
} else {
constrain (point_constr, ewi);
}
}
}
}
// Simply move the points
for (std::map <PointWithIndex, db::Point>::iterator np = new_points.begin (); np != new_points.end (); ++np) {
np->second += mv;
}
// The edges are treated somewhat more elaborate:
for (std::map <EdgeWithIndex, db::Edge>::iterator ne = new_edges.begin (); ne != new_edges.end (); ++ne) {
std::map <PointWithIndex, Constraint>::iterator c1, c2;
// compute normal of move vector
db::DVector nmv;
if (ne->first.d () != db::Vector ()) {
nmv = db::DVector (mv) - db::DVector (ne->first.d ()) * (double (db::sprod (mv, ne->first.d ())) / ne->first.d ().sq_double_length ());
}
db::Point p1 = ne->second.p1 (), p2 = ne->second.p2 ();
db::Point p1e = p1, p2e = p2;
c1 = point_constr.find (ne->first.pi1 ());
if (c1 != point_constr.end ()) {
std::pair <bool, db::Point> pm = c1->second.move (p1, nmv);
if (pm.first) {
p1e = p1 = pm.second;
} else {
// if the movement was not possible, create a new "detached" edge
p1e = p1 + db::Vector (nmv);
}
} else {
p1 += mv;
p1e = p1;
}
c2 = point_constr.find (ne->first.pi2 ());
if (c2 != point_constr.end ()) {
std::pair <bool, db::Point> pm = c2->second.move (p2, nmv);
if (pm.first) {
p2e = p2 = pm.second;
} else {
// if the movement was not possible, create a new "detached" edge
p2e = p2 + db::Vector (nmv);
}
} else {
p2 += mv;
p2e = p2;
}
// if the moved edge is
// 1. result of two constraints
// ( commented out: 2. inverted (the direction has changed) or the length grows 4x larger than the move distance )
// then create a "detached edge" as well
db::Vector ve (p2e - p1e);
db::Vector vo (ne->second.p2 () - ne->second.p1 ());
if (c1 != point_constr.end () && c2 != point_constr.end ()
&& (/* db::sprod_sign (ve, vo) <= 0 || */ (ve - vo).double_length () > 4.0 * nmv.double_length ())) {
#if 0
// first try to detach just one (the one with the largest movement)
if (p2e.sq_double_distance (ne->second.p2 ()) > p1e.sq_double_distance (ne->second.p1 ())) {
p2 = ne->second.p2 ();
p2e = p2 + db::Vector (nmv);
} else {
p1 = ne->second.p1 ();
p1e = p1 + db::Vector (nmv);
}
// if that still is not sufficient to avoid inversion, do both
if (db::sprod_sign (db::Vector (p2e - p1e), db::Vector (ne->second.p2 () - ne->second.p1 ())) <= 0) {
p2 = ne->second.p2 ();
p1 = ne->second.p1 ();
p2e = p2 + db::Vector (nmv);
p1e = p1 + db::Vector (nmv);
}
#else
// this approach is more simple: just create the detached edge ..
p2 = ne->second.p2 ();
p1 = ne->second.p1 ();
p2e = p2 + db::Vector (nmv);
p1e = p1 + db::Vector (nmv);
#endif
}
ne->second = db::Edge (p1e, p2e);
// insert the end points into the point list in order to find them by looking up a point alone
new_points.insert (std::make_pair (ne->first.pi1 (), db::Point ())).first->second = p1;
new_points.insert (std::make_pair (ne->first.pi2 (), db::Point ())).first->second = p2;
}
}
// -------------------------------------------------------------
// PartialShapeFinder declaration
/**
* @brief Partial shape finder utility class
*
* This class specializes the finder to finding vertices or edges of shapes.
*/
class PartialShapeFinder
: public lay::ShapeFinder
{
public:
typedef std::vector<std::pair <lay::ObjectInstPath, std::vector <EdgeWithIndex> > > founds_vector_type;
typedef founds_vector_type::const_iterator iterator;
PartialShapeFinder (bool point_mode, bool top_level_sel, db::ShapeIterator::flags_type flags);
iterator begin () const
{
return m_founds.begin ();
}
iterator end () const
{
return m_founds.end ();
}
private:
virtual void visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int /*level*/);
founds_vector_type m_founds;
};
// -------------------------------------------------------------
// PartialShapeFinder implementation
PartialShapeFinder::PartialShapeFinder (bool point_mode, bool top_level_sel, db::ShapeIterator::flags_type flags)
: lay::ShapeFinder (point_mode, top_level_sel, flags)
{
set_test_count (point_sel_tests);
}
void
PartialShapeFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const db::ICplxTrans &t, int /*level*/)
{
if (! point_mode ()) {
for (std::vector<int>::const_iterator l = layers ().begin (); l != layers ().end (); ++l) {
if (layers ().size () == 1 || (layers ().size () > 1 && cell.bbox ((unsigned int) *l).touches (search_box))) {
checkpoint ();
const db::Shapes &shapes = cell.shapes (*l);
db::ShapeIterator shape = shapes.begin_touching (search_box, flags (), prop_sel (), inv_prop_sel ());
while (! shape.at_end ()) {
checkpoint ();
// in point mode just store that found that has the least "distance"
m_founds.push_back (founds_vector_type::value_type ());
lay::ObjectInstPath &inst_path = m_founds.back ().first;
std::vector<EdgeWithIndex> &edges = m_founds.back ().second;
inst_path.set_cv_index (cv_index ());
inst_path.set_topcell (topcell ());
inst_path.assign_path (path ().begin (), path ().end ());
inst_path.set_layer (*l);
inst_path.set_shape (*shape);
// in point mode, test the edges and use a "closest" criterion
if (shape->is_polygon ()) {
for (unsigned int c = 0; c < shape->holes () + 1; ++c) {
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = shape->begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
if (search_box.contains ((*e).p1 ())) {
edges.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c));
if (search_box.contains ((*e).p2 ())) {
edges.push_back (EdgeWithIndex (*e, n, nn, c));
}
}
}
}
} else if (shape->is_path ()) {
bool pl_set = false;
db::Point pl;
unsigned int n = 0;
for (db::Shape::point_iterator pt = shape->begin_point (); pt != shape->end_point (); ++pt, ++n) {
if (search_box.contains (*pt)) {
edges.push_back (EdgeWithIndex (db::Edge (*pt, *pt), n, n, 0));
if (pl_set && search_box.contains (pl)) {
edges.push_back (EdgeWithIndex (db::Edge (pl, *pt), n - 1, n, 0));
}
}
pl = *pt;
pl_set = true;
}
} else if (shape->is_box ()) {
const db::Box &box = shape->box ();
// convert to polygon and test those edges
db::Polygon poly (box);
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
if (search_box.contains ((*e).p1 ())) {
edges.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0));
if (search_box.contains ((*e).p2 ())) {
edges.push_back (EdgeWithIndex (*e, n, nn, 0));
}
}
}
} else if (shape->is_text ()) {
db::Point tp (shape->text_trans () * db::Point ());
if (search_box.contains (tp)) {
edges.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0));
}
}
// do not select shapes that do not have at least one edge selected
if (edges.empty ()) {
m_founds.pop_back ();
}
++shape;
}
}
}
} else {
for (std::vector<int>::const_iterator l = layers ().begin (); l != layers ().end (); ++l) {
if (layers ().size () == 1 || (layers ().size () > 1 && cell.bbox ((unsigned int) *l).touches (search_box))) {
checkpoint ();
const db::Shapes &shapes = cell.shapes (*l);
std::vector <EdgeWithIndex> edge_sel;
db::ShapeIterator shape = shapes.begin_touching (search_box, flags (), prop_sel (), inv_prop_sel ());
while (! shape.at_end ()) {
bool match = false;
double d = std::numeric_limits<double>::max ();
edge_sel.clear ();
checkpoint ();
// in point mode, test the edges and use a "closest" criterion
if (shape->is_polygon ()) {
for (unsigned int c = 0; c < shape->holes () + 1; ++c) {
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = shape->begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
unsigned int r = test_edge (t * *e, d, match);
if (r) {
edge_sel.clear ();
if ((r & 1) == 1) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, c));
}
if ((r & 2) == 2) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, c));
}
if (r == 3) {
edge_sel.push_back (EdgeWithIndex (*e, n, nn, c));
}
}
}
}
} else if (shape->is_path ()) {
// test the "spine"
db::Shape::point_iterator pt = shape->begin_point ();
if (pt != shape->end_point ()) {
db::Point p (*pt);
++pt;
unsigned int n = 0;
for (; pt != shape->end_point (); ++pt, ++n) {
unsigned int r = test_edge (t * db::Edge (p, *pt), d, match);
if (r) {
edge_sel.clear ();
if ((r & 1) == 1) {
edge_sel.push_back (EdgeWithIndex (db::Edge (p, p), n, n, 0));
}
if ((r & 2) == 2) {
edge_sel.push_back (EdgeWithIndex (db::Edge (*pt, *pt), n + 1, n + 1, 0));
}
if (r == 3) {
edge_sel.push_back (EdgeWithIndex (db::Edge (p, *pt), n, n + 1, 0));
}
}
p = *pt;
}
}
} else if (shape->is_box ()) {
const db::Box &box = shape->box ();
// convert to polygon and test those edges
db::Polygon poly (box);
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
unsigned int r = test_edge (t * *e, d, match);
if (r) {
edge_sel.clear ();
if ((r & 1) == 1) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p1 (), (*e).p1 ()), n, n, 0));
}
if ((r & 2) == 2) {
edge_sel.push_back (EdgeWithIndex (db::Edge ((*e).p2 (), (*e).p2 ()), nn, nn, 0));
}
if (r == 3) {
edge_sel.push_back (EdgeWithIndex (*e, n, nn, 0));
}
}
}
} else if (shape->is_text ()) {
db::Point tp (shape->text_trans () * db::Point ());
if (search_box.contains (tp)) {
d = tp.distance (search_box.center ());
edge_sel.clear ();
edge_sel.push_back (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0));
match = true;
}
}
if (match && closer (d)) {
// in point mode just store that found that has the least "distance"
if (m_founds.empty ()) {
m_founds.push_back (founds_vector_type::value_type ());
}
lay::ObjectInstPath &inst_path = m_founds.back ().first;
inst_path.set_cv_index (cv_index ());
inst_path.set_topcell (topcell ());
inst_path.assign_path (path ().begin (), path ().end ());
inst_path.set_layer (*l);
inst_path.set_shape (*shape);
m_founds.back ().second = edge_sel;
}
++shape;
}
}
}
}
}
// -----------------------------------------------------------------------------
// Main Service implementation
PartialService::PartialService (db::Manager *manager, lay::LayoutView *view, lay::PluginRoot *root)
: QObject (),
lay::ViewService (view->view_object_widget ()),
lay::Editable (view),
lay::Plugin (view),
db::Object (manager),
mp_view (view),
mp_root (root),
m_dragging (false),
m_keep_selection (true),
mp_box (0),
m_color (0),
m_buttons (0),
m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), m_alt_ac (lay::AC_Global),
m_snap_to_objects (true),
m_top_level_sel (false),
m_hover (false),
m_hover_wait (false)
{
m_timer.setInterval (100 /*hover time*/);
m_timer.setSingleShot (true);
connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timeout ()));
}
PartialService::~PartialService ()
{
resize_markers (0, true);
resize_markers (0, false);
resize_inst_markers (0, true);
resize_inst_markers (0, false);
if (mp_box) {
delete mp_box;
mp_box = 0;
}
}
lay::angle_constraint_type
PartialService::connect_ac () const
{
// m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint
return m_alt_ac != lay::AC_Global ? m_alt_ac : m_connect_ac;
}
lay::angle_constraint_type
PartialService::move_ac () const
{
// m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint
return m_alt_ac != lay::AC_Global ? m_alt_ac : m_move_ac;
}
void
PartialService::deactivated ()
{
// clear selection when this mode is left
partial_select (db::DBox (), lay::Editable::Reset);
clear_partial_transient_selection ();
}
void
PartialService::activated ()
{
// ...
}
void
PartialService::hover_reset ()
{
if (m_hover_wait) {
m_timer.stop ();
m_hover_wait = false;
}
if (m_hover) {
clear_partial_transient_selection ();
m_hover = false;
}
}
void
PartialService::timeout ()
{
m_hover_wait = false;
m_hover = true;
mp_view->clear_transient_selection ();
// compute search box
double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag ();
db::DBox search_box = db::DBox (m_hover_point, m_hover_point).enlarged (db::DVector (l, l));
PartialShapeFinder finder (true, m_top_level_sel, db::ShapeIterator::All);
finder.find (view (), search_box);
size_t n_marker = 0;
size_t n_inst_marker = 0;
if (finder.begin () != finder.end ()) {
partial_objects transient_selection;
transient_selection.insert (std::make_pair (finder.begin ()->first, std::set <EdgeWithIndex> (finder.begin ()->second.begin (), finder.begin ()->second.end ())));
partial_objects::const_iterator r = transient_selection.begin ();
// build the transformation variants cache
TransformationVariants tv (view ());
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
// compute the global transformation including context and explicit transformation
db::ICplxTrans gt = (cv.context_trans () * r->first.trans ());
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 && !tv_list->empty ()) {
// dummy shift set
std::map <EdgeWithIndex, db::Edge> new_edges;
std::map <PointWithIndex, db::Point> new_points;
// create the markers to represent vertices and edges
enter_vertices (n_marker, r, new_points, new_edges, gt, *tv_list, true);
if (r->first.shape ().is_polygon ()) {
for (unsigned int c = 0; c < r->first.shape ().holes () + 1; ++c) {
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = r->first.shape ().begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
enter_edge (EdgeWithIndex (*e, n, nn, c), n_marker, r, new_points, new_edges, gt, *tv_list, true);
}
}
} else if (r->first.shape ().is_path ()) {
if (r->first.shape ().begin_point () != r->first.shape ().end_point ()) {
db::Shape::point_iterator pt = r->first.shape ().begin_point ();
db::Point p1 = *pt;
unsigned int n = 0;
while (true) {
++pt;
if (pt == r->first.shape ().end_point ()) {
break;
}
enter_edge (EdgeWithIndex (db::Edge (p1, *pt), n, n + 1, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true);
p1 = *pt;
++n;
}
}
} else if (r->first.shape ().is_box ()) {
// convert to polygon and test those edges
db::Polygon poly (r->first.shape ().box ());
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
enter_edge (EdgeWithIndex (*e, n, nn, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true);
}
} else if (r->first.shape ().is_text ()) {
db::Point tp (r->first.shape ().text_trans () * db::Point ());
enter_edge (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0), n_marker, r, new_points, new_edges, gt, *tv_list, true);
}
}
} else {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv (r->first.cv_index ());
if (tv_list && ! tv_list->empty ()) {
lay::InstanceMarker *marker = new_inst_marker (n_inst_marker, r->first.cv_index (), true);
marker->set (r->first.back ().inst_ptr, gt, *tv_list);
}
}
}
// delete superfluous markers
resize_markers (n_marker, true);
resize_inst_markers (n_inst_marker, true);
}
void
PartialService::clear_partial_transient_selection ()
{
mp_view->clear_transient_selection ();
resize_markers (0, true);
resize_inst_markers (0, true);
}
void
PartialService::set_colors (QColor /*background*/, QColor color)
{
m_color = color.rgb ();
if (mp_box) {
mp_box->set_color (m_color);
}
}
void
PartialService::menu_activated (const std::string & /*symbol*/)
{
// ...
}
bool
PartialService::configure (const std::string &name, const std::string &value)
{
edt::EditGridConverter egc;
edt::ACConverter acc;
if (name == cfg_edit_global_grid) {
egc.from_string (value, m_global_grid);
} else if (name == cfg_edit_grid) {
egc.from_string (value, m_edit_grid);
return true; // taken
} else if (name == cfg_edit_snap_to_objects) {
tl::from_string (value, m_snap_to_objects);
return true; // taken
} else if (name == cfg_edit_move_angle_mode) {
acc.from_string (value, m_move_ac);
return true; // taken
} else if (name == cfg_edit_connect_angle_mode) {
acc.from_string (value, m_connect_ac);
return true; // taken
} else if (name == cfg_edit_top_level_selection) {
tl::from_string (value, m_top_level_sel);
}
return false; // not taken
}
void
PartialService::config_finalize ()
{
// ...
}
db::DPoint
PartialService::snap (const db::DPoint &p) const
{
// snap according to the grid
if (m_edit_grid == db::DVector ()) {
return lay::snap_xy (p, m_global_grid);
} else if (m_edit_grid.x () >= 1e-6) {
return lay::snap_xy (p, m_edit_grid);
} else {
return p;
}
}
db::DVector
PartialService::snap (const db::DVector &v) const
{
// snap according to the grid
if (m_edit_grid == db::DVector ()) {
return lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint ();
} else if (m_edit_grid.x () >= 1e-6) {
return lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint ();
} else {
return v;
}
}
const int sr_pixels = 8; // TODO: make variable
db::DPoint
PartialService::snap2 (const db::DPoint &p) const
{
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (sr_pixels);
return lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range).second;
}
void
PartialService::transform (const db::DCplxTrans &tr)
{
// ignore this function is non-editable mode
if (! view ()->is_editable ()) {
return;
}
// just allow displacements
db::DTrans move_trans (tr.disp ());
transform_selection (move_trans);
selection_to_view ();
}
void
PartialService::transform_selection (const db::DTrans &move_trans)
{
// build the transformation variants cache
TransformationVariants tv (view ());
// since a shape reference may become invalid while moving it and
// because it creates ambiguities, we treat each shape separately:
// collect the valid selected items in a selection-per-shape map.
std::map <db::Shape, std::vector<partial_objects::iterator> > sel_per_shape;
for (partial_objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
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 && ! tv_list->empty ()) {
sel_per_shape.insert (std::make_pair (r->first.shape (), std::vector<partial_objects::iterator> ())).first->second.push_back (r);
}
}
}
for (std::map <db::Shape, std::vector<partial_objects::iterator> >::iterator sps = sel_per_shape.begin (); sps != sel_per_shape.end (); ++sps) {
db::Shape shape = sps->first;
for (std::vector<partial_objects::iterator>::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) {
partial_objects::iterator r = *rr;
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
// use only the first one of the explicit transformations
// TODO: clarify how this can be implemented in a more generic form or leave it thus.
db::ICplxTrans gt (cv.context_trans () * r->first.trans ());
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ());
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * gt;
db::Vector move_vector = db::Vector ((tt.inverted () * db::DCplxTrans (move_trans) * tt).disp ());
std::map <EdgeWithIndex, db::Edge> new_edges;
std::map <PointWithIndex, db::Point> new_points;
create_shift_sets (shape, r->second, new_points, new_edges, move_vector);
// modify the shapes and insert
db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ());
if (shape.is_polygon ()) {
db::Polygon poly;
shape.polygon (poly);
// warning: poly is modified:
modify_polygon (poly, new_points, new_edges, true /*compress*/);
shape = shapes.replace (shape, poly);
} else if (shape.is_path ()) {
db::Path path;
shape.path (path);
// warning: path is modified:
modify_path (path, new_points, new_edges, true /*compress*/);
shape = shapes.replace (shape, path);
} else if (shape.is_box ()) {
db::Polygon poly;
shape.polygon (poly);
// warning: poly is modified:
modify_polygon (poly, new_points, new_edges, true /*compress*/);
shape = shapes.replace (shape, poly.box ());
} else if (shape.is_text ()) {
db::Text t;
shape.text (t);
db::Point tp (shape.text_trans () * db::Point ());
std::map <PointWithIndex, db::Point>::const_iterator np = new_points.find (PointWithIndex (tp, 0, 0));
if (np != new_points.end ()) {
t.transform (db::Trans (np->second - tp));
shape = shapes.replace (shape, t);
}
}
// transform the selection
std::set <EdgeWithIndex> new_sel;
new_sel.swap (r->second);
for (std::set <EdgeWithIndex>::const_iterator s = new_sel.begin (); s != new_sel.end () && m_keep_selection; ++s) {
if (s->p1 () == s->p2 ()) {
std::map <PointWithIndex, db::Point>::const_iterator np = new_points.find (s->pi1 ());
if (np != new_points.end ()) {
r->second.insert (EdgeWithIndex (db::Edge (np->second, np->second), s->n, s->n, s->c));
} else {
r->second.insert (*s);
}
} else {
std::map <EdgeWithIndex, db::Edge>::const_iterator ne = new_edges.find (*s);
if (ne != new_edges.end ()) {
r->second.insert (EdgeWithIndex (ne->second, s->n, s->nn, s->c));
} else {
r->second.insert (*s);
}
}
}
}
// change the shape references if required
if (shape != sps->first) {
for (std::vector<partial_objects::iterator>::const_iterator rr = sps->second.begin (); rr != sps->second.end (); ++rr) {
std::set <EdgeWithIndex> sel;
sel.swap ((*rr)->second);
lay::ObjectInstPath inst_path ((*rr)->first);
inst_path.set_shape (shape);
m_selection.erase ((*rr)->first);
m_selection.insert (std::make_pair (inst_path, std::set <EdgeWithIndex> ())).first->second.swap (sel);
}
}
}
// then move all instances.
// TODO: DTrans should have a ctor that takes a vector
db::DTrans move_trans_inst = db::DTrans (db::DVector () + lay::snap_angle (m_current - m_start, move_ac ()));
// sort the selected objects (the instances) by the cell they are in
// The key is a pair: cell_index, cv_index
std::map <std::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> > insts_by_cell;
for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
if (r->first.is_cell_inst ()) {
insts_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), r->first.cv_index ()), std::vector <partial_objects::const_iterator> ())).first->second.push_back (r);
}
}
std::vector <std::pair <db::Instance, db::ICplxTrans> > insts_to_transform;
for (std::map <std::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> >::const_iterator ibc = insts_by_cell.begin (); ibc != insts_by_cell.end (); ++ibc) {
insts_to_transform.clear ();
insts_to_transform.reserve (ibc->second.size ());
for (std::vector <partial_objects::const_iterator>::const_iterator i = ibc->second.begin (); i != ibc->second.end (); ++i) {
insts_to_transform.push_back (std::make_pair ((*i)->first.back ().inst_ptr, (*i)->first.trans ()));
}
const lay::CellView &cv = view ()->cellview (ibc->first.second);
if (cv.is_valid ()) {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv (ibc->first.second);
if (tv_list && ! tv_list->empty ()) {
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
db::ICplxTrans move_trans_dbu (tt.inverted () * db::DCplxTrans (move_trans_inst) * tt);
std::sort (insts_to_transform.begin (), insts_to_transform.end ());
std::vector <std::pair <db::Instance, db::ICplxTrans> >::const_iterator unique_end = std::unique (insts_to_transform.begin (), insts_to_transform.end ());
db::Cell &cell = cv->layout ().cell (ibc->first.first);
for (std::vector <std::pair <db::Instance, db::ICplxTrans> >::const_iterator inst = insts_to_transform.begin (); inst != unique_end; ++inst) {
db::ICplxTrans mt (inst->second.inverted () * move_trans_dbu * inst->second);
cell.transform (inst->first, mt);
}
}
}
}
handle_guiding_shape_changes ();
}
void
PartialService::edit_cancel ()
{
// stop dragging, clear selection
m_dragging = false;
if (mp_box) {
delete mp_box;
mp_box = 0;
}
widget ()->ungrab_mouse (this);
selection_to_view ();
}
bool
PartialService::wheel_event (int /*delta*/, bool /*horizonal*/, const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/)
{
hover_reset ();
return false;
}
bool
PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
if (m_dragging) {
set_cursor (lay::Cursor::size_all);
m_alt_ac = ac_from_buttons (buttons);
// drag the vertex or edge/segment
if (is_single_point_selection ()) {
// for a single selected point, 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
m_current = snap2 (p);
} else {
m_current = m_start + snap (p - m_start);
}
selection_to_view ();
m_alt_ac = lay::AC_Global;
} else if (prio) {
if (mp_box) {
m_alt_ac = ac_from_buttons (buttons);
m_p2 = p;
mp_box->set_points (m_p1, m_p2);
m_alt_ac = lay::AC_Global;
} else if (view ()->transient_selection_mode ()) {
m_hover_wait = true;
m_timer.start ();
m_hover_point = p;
}
}
// pass on this event to other handlers
return false;
}
bool
PartialService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
hover_reset ();
if (! view ()->is_editable ()) {
return false;
}
// only respond to left button clicks
if ((buttons & lay::LeftButton) == 0) {
return false;
}
// only respond to first order events
if (! prio) {
return false;
}
if (m_dragging) {
// eat events if already dragging
return true;
} else if (! mp_box) {
m_alt_ac = ac_from_buttons (buttons);
if (m_selection.empty ()) {
// clear other selection when this mode gets active
view ()->clear_selection ();
// nothing is selected yet:
// try to select something here.
// (select is allowed to throw an exception)
try {
partial_select (db::DBox (p, p), lay::Editable::Replace);
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ()));
// clear selection
partial_select (db::DBox (), lay::Editable::Reset);
}
}
if (m_selection.empty () || ((buttons & lay::ShiftButton) != 0) || ((buttons & lay::ControlButton) != 0)) {
// if nothing was selected by this point or Ctrl or Shift was pressed, start dragging a box
view ()->stop_redraw (); // TODO: how to restart if selection is aborted?
m_buttons = buttons;
if (mp_box) {
delete mp_box;
}
m_p1 = p;
m_p2 = p;
mp_box = new lay::RubberBox (widget (), m_color, p, p);
mp_box->set_stipple (6); // coarse hatched
widget ()->grab_mouse (this, true);
} else {
// something was selected: start dragging this ..
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 to bring a to grid
m_current = m_start = single_selected_point ();
} else {
m_current = m_start = p;
}
widget ()->grab_mouse (this, true);
}
m_alt_ac = lay::AC_Global;
return true;
}
return false;
}
bool
PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
hover_reset ();
if (! view ()->is_editable ()) {
return false;
}
// only respond to left button clicks
if ((buttons & lay::LeftButton) == 0) {
return false;
}
// only respond to first order events
if (! prio) {
return false;
}
if (m_dragging) {
m_alt_ac = ac_from_buttons (buttons);
if (m_current != m_start) {
// stop dragging
widget ()->ungrab_mouse (this);
manager ()->transaction (tl::to_string (QObject::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;
if (m_selection.size () == 1 && m_selection.begin ()->second.size () == 3 /*p1,p2,edge*/) {
move_trans = db::DTrans (m_current - m_start);
} else {
// TODO: DTrans should have a ctor that takes a vector
move_trans = db::DTrans (lay::snap_angle (m_current - m_start, move_ac ()));
}
transform_selection (move_trans);
manager ()->commit ();
}
if (! m_keep_selection) {
m_selection.clear ();
}
m_dragging = false;
selection_to_view ();
m_alt_ac = lay::AC_Global;
return true;
} else if (widget ()->mouse_event_viewport ().contains (p)) {
// clear other selection when this mode gets active
// (save the selection so our own selection does not get cleared)
partial_objects selection = m_selection;
view ()->clear_selection ();
m_selection = selection;
m_alt_ac = ac_from_buttons (buttons);
lay::Editable::SelectionMode mode = lay::Editable::Replace;
bool shift = ((buttons & lay::ShiftButton) != 0);
bool ctrl = ((buttons & lay::ControlButton) != 0);
if (shift && ctrl) {
mode = lay::Editable::Invert;
} else if (shift) {
mode = lay::Editable::Add;
} else if (ctrl) {
mode = lay::Editable::Reset;
}
// select is allowed to throw an exception
try {
// compute search box
double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag ();
db::DBox search_box = db::DBox (p, p).enlarged (db::DVector (l, l));
// check, if there is a selected shape under the mouse - in this case, we do not do a new selection
PartialShapeFinder finder (true /*point mode*/, m_top_level_sel, db::ShapeIterator::All);
finder.find (view (), search_box);
// check, if there is a selected shape under the mouse - in this case, we do not do a new selection
lay::InstFinder inst_finder (true /*point mode*/, m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no excludes*/, true /*visible layers*/);
inst_finder.find (view (), search_box);
// collect the founds from the finder
// consider a new selection if new objects are selected or the current selection is shape-only
// (this may happen if points have been inserted)
bool new_selection = ((finder.begin () == finder.end () && inst_finder.begin () == inst_finder.end ())
|| mode != lay::Editable::Replace);
for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end () && ! new_selection; ++f) {
partial_objects::const_iterator sel = m_selection.find (f->first);
new_selection = true;
if (sel != m_selection.end ()) {
for (std::vector<edt::EdgeWithIndex>::const_iterator e = f->second.begin (); e != f->second.end () && new_selection; ++e) {
if (sel->second.find (*e) != sel->second.end ()) {
new_selection = false;
}
}
}
}
if (finder.begin () == finder.end ()) {
for (lay::InstFinder::iterator f = inst_finder.begin (); f != inst_finder.end () && ! new_selection; ++f) {
partial_objects::const_iterator sel = m_selection.find (*f);
if (sel == m_selection.end ()) {
new_selection = true;
}
}
}
if (new_selection) {
if (mode == lay::Editable::Replace) {
m_selection.clear ();
}
// clear the selection if we now select a guiding shape or if it was consisting of a guiding shape before
// (that way we ensure there is only a guiding shape selected)
PartialShapeFinder::iterator f0 = finder.begin ();
if (f0 != finder.end () && f0->first.layer () == view ()->cellview (f0->first.cv_index ())->layout ().guiding_shape_layer ()) {
m_selection.clear ();
} else {
partial_objects::const_iterator s0 = m_selection.begin ();
if (s0 != m_selection.end () && s0->first.layer () == view ()->cellview (s0->first.cv_index ())->layout ().guiding_shape_layer ()) {
m_selection.clear ();
}
}
// collect the founds from the finder
for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end (); ++f) {
if (mode == lay::Editable::Replace || mode == lay::Editable::Add) {
// select
partial_objects::iterator sel = m_selection.find (f->first);
if (sel == m_selection.end ()) {
sel = m_selection.insert (std::make_pair (f->first, std::set <EdgeWithIndex> ())).first;
}
sel->second.insert (f->second.begin (), f->second.end ());
} else if (mode == lay::Editable::Reset) {
// unselect
if (m_selection.find (f->first) != m_selection.end ()) {
m_selection.erase (f->first);
}
} else {
// invert selection
if (m_selection.find (f->first) != m_selection.end ()) {
m_selection.erase (f->first);
} else {
m_selection.insert (std::make_pair (f->first, std::set<EdgeWithIndex> ())).first->second.insert (f->second.begin (), f->second.end ());
}
}
}
}
// start dragging with that single selection
if (mode == lay::Editable::Replace && ! m_selection.empty ()) {
m_dragging = true;
m_keep_selection = ! new_selection;
if (is_single_point_selection ()) {
// for a single selected point we use the original point as the start location which
// allows to bring a to grid
m_current = m_start = single_selected_point ();
} else {
m_current = m_start = p;
}
}
selection_to_view ();
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ()));
// clear selection
partial_select (db::DBox (), lay::Editable::Reset);
}
m_alt_ac = lay::AC_Global;
return true;
}
return false;
}
bool
PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
hover_reset ();
if (! view ()->is_editable ()) {
return false;
}
if ((buttons & lay::LeftButton) != 0 && prio) {
m_alt_ac = ac_from_buttons (buttons);
// stop dragging
widget ()->ungrab_mouse (this);
m_dragging = false;
partial_select (db::DBox (p, p), lay::Editable::Replace);
if (! m_selection.empty ()) {
partial_objects::iterator r = m_selection.begin (); // we assert above that we have at least one selected element
if (! r->first.is_cell_inst ()) {
manager ()->transaction (tl::to_string (QObject::tr ("Insert point")));
// snap the point
db::DPoint new_point_d = snap (p);
// build the transformation variants cache
TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/);
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ());
if (tv_list && ! tv_list->empty ()) {
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * (cv.context_trans () * r->first.trans ());
db::Point new_point = db::Point (tt.inverted () * new_point_d);
// modify the shapes and replace
db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ());
db::Shape shape = r->first.shape ();
if (shape.is_polygon ()) {
db::Polygon poly;
shape.polygon (poly);
db::Polygon new_poly;
if (insert_point_poly (poly, r->second, new_point, new_poly)) {
shape = shapes.replace (shape, new_poly);
}
} else if (shape.is_path ()) {
db::Path path;
shape.path (path);
db::Path new_path;
if (insert_point_path (path, r->second, new_point, new_path)) {
shape = shapes.replace (shape, new_path);
}
} else if (shape.is_box ()) {
// convert the box into a polygon unless the shape is on a guiding shape layer
// (if it's a guiding shape we must not change it's nature)
if (r->first.layer () != view ()->cellview (r->first.cv_index ())->layout ().guiding_shape_layer ()) {
db::Polygon poly (shape.box ());
db::Polygon new_poly;
if (insert_point_poly (poly, r->second, new_point, new_poly)) {
shape = shapes.replace (shape, new_poly);
}
}
}
lay::ObjectInstPath obj = r->first;
obj.set_shape (shape);
m_selection.clear ();
m_selection.insert (std::make_pair (obj, std::set<EdgeWithIndex> ()));
handle_guiding_shape_changes ();
manager ()->commit ();
selection_to_view ();
}
}
}
m_alt_ac = lay::AC_Global;
return true;
} else {
return false;
}
}
bool
PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
hover_reset ();
if (prio && mp_box) {
m_alt_ac = ac_from_buttons (buttons);
widget ()->ungrab_mouse (this);
delete mp_box;
mp_box = 0;
if (widget ()->mouse_event_viewport ().contains (p)) {
lay::Editable::SelectionMode mode = lay::Editable::Replace;
bool shift = ((m_buttons & lay::ShiftButton) != 0);
bool ctrl = ((m_buttons & lay::ControlButton) != 0);
if (shift && ctrl) {
mode = lay::Editable::Invert;
} else if (shift) {
mode = lay::Editable::Add;
} else if (ctrl) {
mode = lay::Editable::Reset;
}
// select is allowed to throw an exception
try {
partial_select (db::DBox (m_p1, m_p2), mode);
} catch (tl::Exception &ex) {
tl::error << ex.msg ();
QMessageBox::critical (0, QObject::tr ("Error"), tl::to_qstring (ex.msg ()));
// clear selection
partial_select (db::DBox (), lay::Editable::Reset);
}
}
m_alt_ac = lay::AC_Global;
return true;
}
return false;
}
size_t
PartialService::selection_size ()
{
return m_selection.size ();
}
void
PartialService::del ()
{
// stop dragging
widget ()->ungrab_mouse (this);
std::map <std::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <partial_objects::const_iterator> > shapes_to_delete_by_cell;
for (partial_objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
if (! r->first.is_cell_inst ()) {
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
// modify the shapes and replace
db::Shapes &shapes = cv->layout ().cell (r->first.cell_index ()).shapes (r->first.layer ());
db::Shape shape = r->first.shape ();
if (shape.is_polygon ()) {
db::Polygon poly;
shape.polygon (poly);
db::Polygon new_poly = del_points_poly (poly, r->second);
if (new_poly.hull ().size () < 3) {
shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector <partial_objects::const_iterator> ())).first->second.push_back (r);
} else {
shapes.replace (shape, new_poly);
}
} else if (shape.is_path ()) {
db::Path path;
shape.path (path);
db::Path new_path = del_points_path (path, r->second);
if (new_path.points () < 2) {
shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector <partial_objects::const_iterator> ())).first->second.push_back (r);
} else {
shapes.replace (shape, new_path);
}
} else if (shape.is_box ()) {
// if more than one point is deleted, the box basically collapses, if one point is deleted
// nothing changes on the box.
if (r->second.size () > 1) {
shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector <partial_objects::const_iterator> ())).first->second.push_back (r);
}
} else if (shape.is_text ()) {
shapes_to_delete_by_cell.insert (std::make_pair (std::make_pair (r->first.cell_index (), std::make_pair (r->first.cv_index (), r->first.layer ())), std::vector <partial_objects::const_iterator> ())).first->second.push_back (r);
}
}
}
// delete all shapes that are really lost
std::vector <db::Shape> shapes_to_delete;
for (std::map <std::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <partial_objects::const_iterator> >::const_iterator sbc = shapes_to_delete_by_cell.begin (); sbc != shapes_to_delete_by_cell.end (); ++sbc) {
const lay::CellView &cv = view ()->cellview (sbc->first.second.first);
if (cv.is_valid ()) {
// don't delete guiding shapes
if (sbc->first.second.second != cv->layout ().guiding_shape_layer ()) {
for (std::vector <partial_objects::const_iterator>::const_iterator s = sbc->second.begin (); s != sbc->second.end (); ++s) {
cv->layout ().cell (sbc->first.first).shapes (sbc->first.second.second).erase_shape ((*s)->first.shape ());
}
}
}
}
// then delete all instances.
// sort the selected objects (the instances) by the cell they are in
// The key is a pair: cell_index, cv_index
std::map <std::pair <db::cell_index_type, unsigned int>, std::vector <partial_objects::const_iterator> > insts_by_cell;
for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
if (r->first.is_cell_inst ()) {
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
if (cv.is_valid ()) {
cv->layout ().cell (r->first.cell_index ()).erase (r->first.back ().inst_ptr);
}
}
}
// Hint: calling this method is somewhat dangerous since the selection is not necessarily valid (the shapes
// may have been deleted). However, since we did not delete guiding shapes before and this method in particular
// handles guiding shapes, this should be fairly safe.
handle_guiding_shape_changes ();
m_selection.clear ();
m_dragging = false;
selection_to_view ();
}
lay::InstanceMarker *
PartialService::new_inst_marker (size_t &nmarker, unsigned int cv_index, bool transient)
{
lay::InstanceMarker *marker;
if (transient) {
if (nmarker >= m_transient_inst_markers.size ()) {
marker = new lay::InstanceMarker (view (), cv_index);
m_transient_inst_markers.push_back (marker);
} else {
marker = m_transient_inst_markers [nmarker];
}
} else {
if (nmarker >= m_inst_markers.size ()) {
marker = new lay::InstanceMarker (view (), cv_index);
m_inst_markers.push_back (marker);
} else {
marker = m_inst_markers [nmarker];
}
}
++nmarker;
return marker;
}
lay::Marker *
PartialService::new_marker (size_t &nmarker, unsigned int cv_index, bool transient)
{
lay::Marker *marker;
if (transient) {
if (nmarker >= m_transient_markers.size ()) {
marker = new lay::Marker (view (), cv_index);
m_transient_markers.push_back (marker);
} else {
marker = m_transient_markers [nmarker];
}
} else {
if (nmarker >= m_markers.size ()) {
marker = new lay::Marker (view (), cv_index);
m_markers.push_back (marker);
} else {
marker = m_markers [nmarker];
}
}
++nmarker;
if (transient) {
marker->set_vertex_size (0);
marker->set_dither_pattern (1 /*hollow*/);
marker->set_frame_pattern (0 /*solid*/);
marker->set_line_width (1);
marker->set_halo (false);
} else {
marker->set_vertex_size (-1 /*default*/);
marker->set_dither_pattern (1 /*hollow*/);
marker->set_frame_pattern (0 /*solid*/);
marker->set_line_width (-1 /*default*/);
marker->set_halo (-1 /*default*/);
}
return marker;
}
void
PartialService::enter_path (db::Path &p, size_t &nmarker, partial_objects::const_iterator sel, const std::map <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans &gt, const std::vector<db::DCplxTrans> &tv, bool transient)
{
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_dither_pattern (3 /*dotted*/);
marker->set_frame_pattern (2 /*dotted*/);
marker->set_line_width (1);
marker->set_halo (0);
modify_path (p, new_points, new_edges);
marker->set (p, gt, tv);
}
void
PartialService::enter_polygon (db::Polygon &p, size_t &nmarker, partial_objects::const_iterator sel, const std::map <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans &gt, const std::vector<db::DCplxTrans> &tv, bool transient)
{
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_dither_pattern (3 /*dotted*/);
marker->set_frame_pattern (2 /*dotted*/);
marker->set_line_width (1);
marker->set_halo (0);
modify_polygon (p, new_points, new_edges);
marker->set (p, gt, tv);
}
void
PartialService::enter_vertices (size_t &nmarker, partial_objects::const_iterator sel, const std::map <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> & /*new_edges*/, const db::ICplxTrans &gt, const std::vector<db::DCplxTrans> &tv, bool transient)
{
// TODO: create vertex markers only for vertices that are not for an edge
// and use "fat" vertices on the edge markers.
for (std::set <EdgeWithIndex>::const_iterator e = sel->second.begin (); e != sel->second.end (); ++e) {
if (e->p1 () == e->p2 ()) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
db::Point pnew = e->p1 ();
std::map <PointWithIndex, db::Point>::const_iterator np = new_points.find (PointWithIndex (pnew, e->n, e->c));
if (np != new_points.end ()) {
pnew = np->second;
}
marker->set (db::Edge (pnew, pnew), gt, tv);
}
}
}
void
PartialService::enter_edge (const EdgeWithIndex &e, size_t &nmarker, partial_objects::const_iterator sel, const std::map <PointWithIndex, db::Point> &new_points, const std::map <EdgeWithIndex, db::Edge> &new_edges, const db::ICplxTrans &gt, const std::vector<db::DCplxTrans> &tv, bool transient)
{
db::Point ep1 (e.p1 ());
db::Point ep2 (e.p2 ());
bool p1_sel = sel->second.find (EdgeWithIndex (db::Edge (ep1, ep1), e.n, e.n, e.c)) != sel->second.end ();
bool p2_sel = sel->second.find (EdgeWithIndex (db::Edge (ep2, ep2), e.nn, e.nn, e.c)) != sel->second.end ();
bool p12_sel = sel->second.find (e) != sel->second.end ();
if (p1_sel || p2_sel || p12_sel) {
// map points to moved ones
std::map <PointWithIndex, db::Point>::const_iterator np;
np = new_points.find (e.pi1 ());
if (np != new_points.end ()) {
ep1 = np->second;
}
np = new_points.find (e.pi2 ());
if (np != new_points.end ()) {
ep2 = np->second;
}
db::Edge enew (ep1, ep2);
std::map <EdgeWithIndex, db::Edge>::const_iterator ne;
ne = new_edges.find (e);
if (ne != new_edges.end ()) {
enew = ne->second;
if (enew.p1 () != ep1) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0);
marker->set (db::Edge (ep1, enew.p1 ()), gt, tv);
}
if (enew.p2 () != ep2) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0);
marker->set (db::Edge (enew.p2 (), ep2), gt, tv);
}
}
if (p2_sel && !p12_sel) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0);
db::DEdge ee = db::DEdge (db::DPoint (ep2) + ((db::DPoint (ep1) - db::DPoint (ep2)) * 0.25), db::DPoint (ep2));
marker->set (ee, db::DCplxTrans (gt), tv);
}
if (p1_sel && !p12_sel) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0);
db::DEdge ee = db::DEdge (db::DPoint (ep1), db::DPoint (ep1) + ((db::DPoint (ep2) - db::DPoint (ep1)) * 0.25));
marker->set (ee, db::DCplxTrans (gt), tv);
}
if (p12_sel) {
lay::Marker *marker = new_marker (nmarker, sel->first.cv_index (), transient);
marker->set_vertex_size (0);
marker->set (enew, gt, tv);
}
}
}
db::DPoint
PartialService::single_selected_point () const
{
// build the transformation variants cache and
// use only the first one of the explicit transformations
// TODO: clarify how this can be implemented in a more generic form or leave it thus.
TransformationVariants tv (view ());
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ());
const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ());
db::ICplxTrans gt (cv.context_trans () * m_selection.begin ()->first.trans ());
db::CplxTrans tt = (*tv_list)[0] * db::CplxTrans (cv->layout ().dbu ()) * gt;
return tt * m_selection.begin ()->second.begin ()->p1 ();
}
bool
PartialService::is_single_point_selection () const
{
return (m_selection.size () == 1 && ! m_selection.begin ()->first.is_cell_inst () && m_selection.begin ()->second.size () == 1 /*p*/);
}
bool
PartialService::select (const db::DBox &box, SelectionMode mode)
{
if (box.empty () && mode == lay::Editable::Reset) {
// clear selection
m_selection.clear ();
selection_to_view ();
}
return false;
}
void
PartialService::selection_to_view ()
{
// if dragging, establish the current displacement
db::DTrans move_trans;
if (m_dragging) {
// heuristically, if there is just one edge selected: do not confine to the movement
// angle constraint - the edge usually is confined enough
if (m_selection.size () == 1 && ! m_selection.begin ()->first.is_cell_inst () && m_selection.begin ()->second.size () == 3 /*p1,p2,edge*/) {
move_trans = db::DTrans (m_current - m_start);
} else {
// TODO: DTrans should have a ctor that takes a vector
move_trans = db::DTrans (lay::snap_angle (m_current - m_start, move_ac ()));
}
// display vector
view ()->message (std::string ("dx: ") + tl::micron_to_string (move_trans.disp ().x ()) +
std::string (" dy: ") + tl::micron_to_string (move_trans.disp ().y ()) +
std::string (" d: ") + tl::micron_to_string (move_trans.disp ().length ()));
}
// build the transformation variants cache
TransformationVariants tv (view ());
size_t n_marker = 0;
size_t n_inst_marker = 0;
for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
const lay::CellView &cv = view ()->cellview (r->first.cv_index ());
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 && !tv_list->empty ()) {
// use only the first one of the explicit transformations
// TODO: clarify how this can be implemented in a more generic form or leave it thus.
db::ICplxTrans gt (cv.context_trans () * r->first.trans ());
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * gt;
db::Vector move_vector (tt.inverted () * (move_trans * (tt * db::Point ())));
// create the shift sets describing how points and edges are being moved
std::map <EdgeWithIndex, db::Edge> new_edges;
std::map <PointWithIndex, db::Point> new_points;
if (m_dragging) {
create_shift_sets (r->first.shape (), r->second, new_points, new_edges, move_vector);
}
// create the markers to represent vertices and edges
enter_vertices (n_marker, r, new_points, new_edges, gt, *tv_list, false);
if (r->first.shape ().is_polygon ()) {
for (unsigned int c = 0; c < r->first.shape ().holes () + 1; ++c) {
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = r->first.shape ().begin_edge (c); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
enter_edge (EdgeWithIndex (*e, n, nn, c), n_marker, r, new_points, new_edges, gt, *tv_list, false);
}
}
db::Polygon poly;
r->first.shape ().polygon (poly);
// warning: poly is modified:
enter_polygon (poly, n_marker, r, new_points, new_edges, gt, *tv_list, false);
} else if (r->first.shape ().is_path ()) {
if (r->first.shape ().begin_point () != r->first.shape ().end_point ()) {
db::Shape::point_iterator pt = r->first.shape ().begin_point ();
db::Point p1 = *pt;
unsigned int n = 0;
while (true) {
++pt;
if (pt == r->first.shape ().end_point ()) {
break;
}
enter_edge (EdgeWithIndex (db::Edge (p1, *pt), n, n + 1, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false);
p1 = *pt;
++n;
}
// TODO: ... put this somewhere else:
db::Path path;
r->first.shape ().path (path);
// warning: path is modified:
enter_path (path, n_marker, r, new_points, new_edges, gt, *tv_list, false);
}
} else if (r->first.shape ().is_box ()) {
// convert to polygon and test those edges
db::Polygon poly (r->first.shape ().box ());
unsigned int n = 0;
db::Shape::polygon_edge_iterator ee;
for (db::Shape::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); e = ee, ++n) {
ee = e;
++ee;
unsigned int nn = ee.at_end () ? 0 : n + 1;
enter_edge (EdgeWithIndex (*e, n, nn, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false);
}
// warning: poly is modified:
enter_polygon (poly, n_marker, r, new_points, new_edges, gt, *tv_list, false);
} else if (r->first.shape ().is_text ()) {
db::Point tp (r->first.shape ().text_trans () * db::Point ());
enter_edge (EdgeWithIndex (db::Edge (tp, tp), 0, 0, 0), n_marker, r, new_points, new_edges, gt, *tv_list, false);
}
}
} else {
// compute the global transformation including movement, context and explicit transformation
db::ICplxTrans gt = db::VCplxTrans (cv->layout ().dbu ()) * db::DCplxTrans (move_trans) * db::CplxTrans (cv->layout ().dbu ());
gt *= (cv.context_trans () * r->first.trans ());
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv (r->first.cv_index ());
if (tv_list && ! tv_list->empty ()) {
lay::InstanceMarker *marker = new_inst_marker (n_inst_marker, r->first.cv_index (), false);
marker->set (r->first.back ().inst_ptr, gt, *tv_list);
}
}
}
// delete superfluous markers
resize_markers (n_marker, false);
resize_inst_markers (n_inst_marker, false);
}
void
PartialService::resize_markers (size_t n, bool transient)
{
if (transient) {
for (std::vector<lay::Marker *>::iterator r = m_transient_markers.begin () + n; r != m_transient_markers.end (); ++r) {
delete *r;
}
m_transient_markers.erase (m_transient_markers.begin () + n, m_transient_markers.end ());
} else {
for (std::vector<lay::Marker *>::iterator r = m_markers.begin () + n; r != m_markers.end (); ++r) {
delete *r;
}
m_markers.erase (m_markers.begin () + n, m_markers.end ());
}
}
void
PartialService::resize_inst_markers (size_t n, bool transient)
{
if (transient) {
for (std::vector<lay::InstanceMarker *>::iterator r = m_transient_inst_markers.begin () + n; r != m_transient_inst_markers.end (); ++r) {
delete *r;
}
m_transient_inst_markers.erase (m_transient_inst_markers.begin () + n, m_transient_inst_markers.end ());
} else {
for (std::vector<lay::InstanceMarker *>::iterator r = m_inst_markers.begin () + n; r != m_inst_markers.end (); ++r) {
delete *r;
}
m_inst_markers.erase (m_inst_markers.begin () + n, m_inst_markers.end ());
}
}
bool
PartialService::partial_select (const db::DBox &box, lay::Editable::SelectionMode mode)
{
clear_partial_transient_selection ();
// compute search box
double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag ();
db::DBox search_box = box.enlarged (db::DVector (l, l));
bool needs_update = false;
bool any_selected = false;
// clear before unless "add" is selected
if (mode == lay::Editable::Replace) {
if (! m_selection.empty ()) {
m_selection.clear ();
needs_update = true;
}
}
if (box.empty ()) {
// unconditional selection
if (mode == lay::Editable::Reset) {
if (! m_selection.empty ()) {
m_selection.clear ();
needs_update = true;
}
} else {
// extract all shapes
// ... not implemented yet ...
}
} else {
PartialShapeFinder finder (box.is_point (), m_top_level_sel, db::ShapeIterator::All);
finder.find (view (), search_box);
// We must make sure that guiding shapes are only selected alone. The first selected object will
// determine whether we take guiding shapes into account or not.
bool gs_mode = (finder.begin () != finder.end () && finder.begin ()->first.layer () == view ()->cellview (finder.begin ()->first.cv_index ())->layout ().guiding_shape_layer ());
// Clear the selection if it was consisting of a guiding shape or non-guiding shape before (depending on mode).
// This way we make sure there is not mixture between guiding shapes and others.
if (m_selection.begin () != m_selection.end ()) {
if (gs_mode != (m_selection.begin ()->first.layer () == view ()->cellview (m_selection.begin ()->first.cv_index ())->layout ().guiding_shape_layer ())) {
m_selection.clear ();
needs_update = true;
}
}
// collect the founds from the finder
for (PartialShapeFinder::iterator f = finder.begin (); f != finder.end (); ++f) {
if (gs_mode == (f->first.layer () == view ()->cellview (f->first.cv_index ())->layout ().guiding_shape_layer ())) {
if (mode == lay::Editable::Replace || mode == lay::Editable::Add) {
// select
partial_objects::iterator sel = m_selection.find (f->first);
if (sel == m_selection.end ()) {
sel = m_selection.insert (std::make_pair (f->first, std::set <EdgeWithIndex> ())).first;
}
sel->second.insert (f->second.begin (), f->second.end ());
} else if (mode == lay::Editable::Reset) {
// unselect
if (m_selection.find (f->first) != m_selection.end ()) {
m_selection.erase (f->first);
}
} else {
// invert selection
if (m_selection.find (f->first) != m_selection.end ()) {
m_selection.erase (f->first);
} else {
m_selection.insert (std::make_pair (f->first, std::set <EdgeWithIndex> ())).first->second.insert (f->second.begin (), f->second.end ());
}
}
needs_update = true;
any_selected = true;
}
}
// check, if there is a selected instance inside the box - in this case, we do not do a new selection
if (! box.is_point ()) {
lay::InstFinder inst_finder (box.is_point (), m_top_level_sel, true /*full arrays*/, true /*enclose*/, 0 /*no exludes*/, true /*visible layers*/);
inst_finder.find (view (), search_box);
// collect the founds from the finder
for (lay::InstFinder::iterator f = inst_finder.begin (); f != inst_finder.end (); ++f) {
if (mode == lay::Editable::Replace || mode == lay::Editable::Add) {
// select
m_selection.insert (std::make_pair (*f, std::set <EdgeWithIndex> ()));
} else if (mode == lay::Editable::Reset) {
// unselect
if (m_selection.find (*f) != m_selection.end ()) {
m_selection.erase (*f);
}
} else {
// invert selection
if (m_selection.find (*f) != m_selection.end ()) {
m_selection.erase (*f);
} else {
m_selection.insert (std::make_pair (*f, std::set <EdgeWithIndex> ()));
}
}
needs_update = true;
any_selected = true;
}
}
}
// if required, update the list of objects to display the selection
if (needs_update) {
selection_to_view ();
}
return any_selected;
}
bool
PartialService::handle_guiding_shape_changes ()
{
// just allow one guiding shape to be selected
if (m_selection.empty ()) {
return false;
}
partial_objects::const_iterator s = m_selection.begin ();
unsigned int cv_index = s->first.cv_index ();
lay::CellView cv = view ()->cellview (cv_index);
db::Layout *layout = &cv->layout ();
if (s->first.is_cell_inst () || s->first.layer () != layout->guiding_shape_layer ()) {
return false;
}
if (! s->first.shape ().has_prop_id ()) {
return false;
}
if (! layout->is_pcell_instance (s->first.cell_index ()).first) {
return false;
}
db::cell_index_type top_cell = std::numeric_limits<db::cell_index_type>::max ();
db::cell_index_type parent_cell = std::numeric_limits<db::cell_index_type>::max ();
db::Instance parent_inst;
db::pcell_parameters_type parameters_for_pcell;
// determine parent cell and instance if required
lay::ObjectInstPath::iterator e = s->first.end ();
if (e == s->first.begin ()) {
top_cell = s->first.cell_index ();
} else {
--e;
db::cell_index_type pc = s->first.topcell ();
if (e != s->first.begin ()) {
--e;
pc = e->inst_ptr.cell_index ();
}
parent_cell = pc;
parent_inst = s->first.back ().inst_ptr;
}
db::property_names_id_type pn = layout->properties_repository ().prop_name_id ("name");
const db::PropertiesRepository::properties_set &input_props = layout->properties_repository ().properties (s->first.shape ().prop_id ());
db::PropertiesRepository::properties_set::const_iterator input_pv = input_props.find (pn);
if (input_pv == input_props.end ()) {
return false;
}
std::string shape_name = input_pv->second.to_string ();
// Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed
// guiding shapes. We must not access s->shape after that.
if (! get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) {
return false;
}
partial_objects new_sel;
if (parent_cell != std::numeric_limits <db::cell_index_type>::max ()) {
db::Instance new_inst = layout->cell (parent_cell).change_pcell_parameters (parent_inst, parameters_for_pcell);
// try to identify the selected shape in the new shapes and select this one
db::Shapes::shape_iterator sh = layout->cell (new_inst.cell_index ()).shapes (layout->guiding_shape_layer ()).begin (db::ShapeIterator::All);
while (! sh.at_end ()) {
const db::PropertiesRepository::properties_set &props = layout->properties_repository ().properties (sh->prop_id ());
db::PropertiesRepository::properties_set::const_iterator pv = props.find (pn);
if (pv != props.end ()) {
if (pv->second.to_string () == shape_name) {
lay::ObjectInstPath inst_path = s->first;
inst_path.back ().inst_ptr = new_inst;
inst_path.back ().array_inst = new_inst.begin ();
inst_path.set_shape (*sh);
new_sel.insert (std::make_pair (inst_path, s->second));
break;
}
}
++sh;
}
}
if (top_cell != std::numeric_limits <db::cell_index_type>::max ()) {
// TODO: implement the case of a PCell variant being a top cell
// Currently there is not way to create such a configuration ...
}
// remove superfluous proxies
layout->cleanup ();
m_selection = new_sel;
selection_to_view ();
return true;
}
} // namespace edt