klayout/src/edt/edtService.cc

1589 lines
47 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 "dbClipboard.h"
#include "dbPCellDeclaration.h"
#include "dbLibrary.h"
#include "edtPlugin.h"
#include "edtService.h"
#include "edtEditorOptionsPages.h"
#include "edtDialogs.h"
#include "layFinder.h"
#include "layLayoutView.h"
#include "laySnap.h"
#include "tlProgress.h"
#include "tlTimer.h"
namespace edt
{
// -------------------------------------------------------------
// Convert buttons to an angle constraint
lay::angle_constraint_type
ac_from_buttons (unsigned int buttons)
{
if ((buttons & lay::ShiftButton) != 0) {
if ((buttons & lay::ControlButton) != 0) {
return lay::AC_Any;
} else {
return lay::AC_Ortho;
}
} else {
if ((buttons & lay::ControlButton) != 0) {
return lay::AC_Diagonal;
} else {
return lay::AC_Global;
}
}
}
// -------------------------------------------------------------
Service::Service (db::Manager *manager, lay::LayoutView *view, db::ShapeIterator::flags_type flags)
: lay::ViewService (view->view_object_widget ()),
lay::Editable (view),
lay::Plugin (view),
db::Object (manager),
mp_view (view),
mp_transient_marker (0),
m_editing (false), m_immediate (false),
m_cell_inst_service (false),
m_flags (flags),
m_move_sel (false), m_moving (false),
m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), m_alt_ac (lay::AC_Global),
m_snap_to_objects (false),
m_top_level_sel (false), m_show_shapes_of_instances (true), m_max_shapes_of_instances (1000),
m_indicate_secondary_selection (false),
m_seq (0),
dm_selection_to_view (this, &edt::Service::do_selection_to_view)
{
// .. nothing yet ..
}
Service::Service (db::Manager *manager, lay::LayoutView *view)
: lay::ViewService (view->view_object_widget ()),
lay::Editable (view),
lay::Plugin (view),
db::Object (manager),
mp_view (view),
mp_transient_marker (0),
m_editing (false), m_immediate (false),
m_cell_inst_service (true),
m_flags (db::ShapeIterator::Nothing),
m_move_sel (false), m_moving (false),
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_show_shapes_of_instances (true), m_max_shapes_of_instances (1000),
m_indicate_secondary_selection (false),
m_seq (0),
dm_selection_to_view (this, &edt::Service::do_selection_to_view)
{
// .. nothing yet ..
}
Service::~Service ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
delete *r;
}
m_markers.clear ();
for (std::vector<lay::ViewObject *>::iterator r = m_edit_markers.begin (); r != m_edit_markers.end (); ++r) {
delete *r;
}
m_edit_markers.clear ();
clear_transient_selection ();
}
lay::angle_constraint_type
Service::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
Service::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;
}
db::DPoint
Service::snap (db::DPoint p) const
{
// snap according to the grid
if (m_edit_grid == db::DVector ()) {
p = lay::snap_xy (p, m_global_grid);
} else if (m_edit_grid.x () < 1e-6) {
; // nothing
} else {
p = lay::snap_xy (p, m_edit_grid);
}
return p;
}
db::DVector
Service::snap (db::DVector v) const
{
// snap according to the grid
if (m_edit_grid == db::DVector ()) {
v = lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint ();
} else if (m_edit_grid.x () < 1e-6) {
; // nothing
} else {
v = lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint ();
}
return v;
}
db::DVector
Service::snap (const db::DVector &v, bool connect) const
{
return snap (lay::snap_angle (v, connect ? connect_ac () : move_ac ()));
}
db::DPoint
Service::snap (const db::DPoint &p, const db::DPoint &plast, bool connect) const
{
db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac () : move_ac ());
return snap (ps);
}
const int sr_pixels = 8; // TODO: make variable
db::DPoint
Service::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;
}
db::DPoint
Service::snap2 (const db::DPoint &p, const db::DPoint &plast, bool connect) const
{
double snap_range = widget ()->mouse_event_trans ().inverted ().ctrans (sr_pixels);
return lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac () : move_ac (), snap_range).second;
}
bool
Service::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_show_shapes_of_instances) {
tl::from_string (value, m_show_shapes_of_instances);
} else if (name == cfg_edit_max_shapes_of_instances) {
tl::from_string (value, m_max_shapes_of_instances);
} 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
Service::clear_highlights ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (false);
}
}
void
Service::restore_highlights ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (true);
}
}
void
Service::highlight (unsigned int n)
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (n-- == 0);
}
}
void
Service::cut ()
{
if (selection_size () > 0 && view ()->is_editable ()) {
// copy & delete the selected objects
copy_selected ();
del_selected ();
}
}
void
Service::copy ()
{
if (view ()->is_editable ()) {
// copy the selected objects
copy_selected ();
}
}
void
Service::copy_selected ()
{
edt::CopyModeDialog mode_dialog (view ());
bool need_to_ask_for_copy_mode = false;
for (objects::const_iterator r = m_selection.begin (); r != m_selection.end () && ! need_to_ask_for_copy_mode; ++r) {
if (r->is_cell_inst ()) {
const db::Cell &cell = view ()->cellview (r->cv_index ())->layout ().cell (r->back ().inst_ptr.cell_index ());
if (! cell.is_proxy ()) {
need_to_ask_for_copy_mode = true;
}
}
}
unsigned int inst_mode = 0;
if (! need_to_ask_for_copy_mode || mode_dialog.exec_dialog (inst_mode)) {
// create one ClipboardData object per cv_index because, this one assumes that there is
// only one source layout object.
std::set <unsigned int> cv_indices;
for (objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
cv_indices.insert (r->cv_index ());
}
for (std::set <unsigned int>::const_iterator cvi = cv_indices.begin (); cvi != cv_indices.end (); ++cvi) {
db::ClipboardValue<edt::ClipboardData> *cd = new db::ClipboardValue<edt::ClipboardData> ();
// add the selected objects to the clipboard data objects.
const lay::CellView &cv = view ()->cellview (*cvi);
for (objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
if (r->cv_index () == *cvi) {
if (! r->is_cell_inst ()) {
cd->get ().add (cv->layout (), r->layer (), r->shape (), cv.context_trans () * r->trans ());
} else {
cd->get ().add (cv->layout (), r->back ().inst_ptr, inst_mode, cv.context_trans () * r->trans ());
}
}
}
db::Clipboard::instance () += cd;
}
}
}
bool
Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::angle_constraint_type /*ac*/)
{
if (view ()->is_editable () && mode == lay::Editable::Selected) {
m_move_start = p;
m_move_trans = db::DTrans ();
m_move_sel = true; // TODO: there is no "false". Remove this.
m_moving = true;
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->thaw ();
// Show the inner structure of the instances
lay::InstanceMarker *inst_marker = dynamic_cast<lay::InstanceMarker *> (*r);
if (inst_marker) {
inst_marker->set_draw_outline (! m_show_shapes_of_instances);
inst_marker->set_max_shapes (m_show_shapes_of_instances ? m_max_shapes_of_instances : 0);
}
}
}
return false;
}
void
Service::move (const db::DPoint &pu, lay::angle_constraint_type ac)
{
m_alt_ac = ac;
db::DPoint p = snap (m_move_start) + snap (pu - m_move_start, false /*move*/);
if (view ()->is_editable () && m_moving) {
move_markers (db::DTrans (p - db::DPoint ()) * db::DTrans (m_move_trans.fp_trans ()) * db::DTrans (db::DPoint () - snap (m_move_start)));
}
m_alt_ac = lay::AC_Global;
}
void
Service::move_transform (const db::DPoint &pu, db::DFTrans tr, lay::angle_constraint_type ac)
{
m_alt_ac = ac;
db::DPoint p = snap (m_move_start) + snap (pu - m_move_start, false);
if (view ()->is_editable () && m_moving) {
move_markers (db::DTrans (p - db::DPoint ()) * db::DTrans (m_move_trans.fp_trans () * tr) * db::DTrans (db::DPoint () - snap (m_move_start)));
}
m_alt_ac = lay::AC_Global;
}
void
Service::end_move (const db::DPoint & /*p*/, lay::angle_constraint_type ac)
{
m_alt_ac = ac;
if (view ()->is_editable () && m_moving) {
transform (db::DCplxTrans (m_move_trans));
move_cancel (); // formally this functionality fits here
// accept changes to guiding shapes
handle_guiding_shape_changes ();
}
m_alt_ac = lay::AC_Global;
}
db::DBox
Service::selection_bbox ()
{
// build the transformation variants cache
// TODO: this is done multiple times - once for each service!
TransformationVariants tv (view ());
db::DBox box;
for (objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
const lay::CellView &cv = view ()->cellview (r->cv_index ());
const db::Layout &layout = cv->layout ();
db::CplxTrans ctx_trans = db::CplxTrans (layout.dbu ()) * cv.context_trans () * r->trans ();
db::box_convert<db::CellInst> bc (layout);
if (! r->is_cell_inst ()) {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (r->cv_index (), r->layer ());
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->shape ().bbox ());
}
}
} else {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv (r->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->back ().bbox (bc));
}
}
}
}
return box;
}
void
Service::set_edit_marker (lay::ViewObject *edit_marker)
{
for (std::vector<lay::ViewObject *>::iterator r = m_edit_markers.begin (); r != m_edit_markers.end (); ++r) {
delete *r;
}
m_edit_markers.clear ();
add_edit_marker (edit_marker);
}
void
Service::add_edit_marker (lay::ViewObject *edit_marker)
{
if (edit_marker) {
m_edit_markers.push_back (edit_marker);
}
}
lay::ViewObject *
Service::edit_marker ()
{
if (! m_edit_markers.empty ()) {
return m_edit_markers.front ();
} else {
return 0;
}
}
void
Service::transform (const db::DCplxTrans &trans, const std::vector<db::DCplxTrans> *p_trv)
{
// ignore this function in non-editable mode
if (! view ()->is_editable ()) {
return;
}
// HINT: sorting the selected shapes/instances ensures that a shape/instance is not moved twice.
// This may happen due to per-instance selection of lower-level shapes.
size_t n;
// build a list of object references corresponding to the p_trv vector
std::vector <objects::iterator> obj_ptrs;
obj_ptrs.reserve (m_selection.size ());
n = 0;
for (objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r, ++n) {
obj_ptrs.push_back (r);
}
// build the transformation variants cache
TransformationVariants tv (view ());
// 1.) first transform all shapes
// sort the selected objects (the shapes) by the cell they are in
// The key is a triple: cell_index, cv_index, layer
std::map <std::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <size_t> > shapes_by_cell;
n = 0;
for (objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r, ++n) {
if (! r->is_cell_inst ()) {
shapes_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), std::make_pair (r->cv_index (), r->layer ())), std::vector <size_t> ())).first->second.push_back (n);
}
}
for (std::map <std::pair <db::cell_index_type, std::pair <unsigned int, unsigned int> >, std::vector <size_t> >::iterator sbc = shapes_by_cell.begin (); sbc != shapes_by_cell.end (); ++sbc) {
const lay::CellView &cv = view ()->cellview (sbc->first.second.first);
if (cv.is_valid ()) {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (sbc->first.second.first, sbc->first.second.second);
if (tv_list != 0) {
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
db::DCplxTrans mt_mu (tt.inverted () * trans * tt);
db::Shapes &shapes = cv->layout ().cell (sbc->first.first).shapes (sbc->first.second.second);
std::map <db::Shape, db::Shape> new_shapes;
for (std::vector <size_t>::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) {
objects::iterator s = obj_ptrs [*si];
// mt = transformation in DBU units
db::ICplxTrans mt;
db::CplxTrans t (s->trans ());
if (p_trv != 0 && *si < p_trv->size ()) {
db::DCplxTrans t_mu (tt.inverted () * (*p_trv) [*si] * tt);
mt = db::ICplxTrans (t.inverted () * t_mu * t);
} else {
mt = db::ICplxTrans (t.inverted () * mt_mu * t);
}
std::map <db::Shape, db::Shape>::iterator ns = new_shapes.find (s->shape ());
if (ns == new_shapes.end ()) {
new_shapes.insert (std::make_pair (s->shape (), shapes.transform (s->shape (), mt)));
} else {
ns->second = shapes.transform (ns->second, mt);
}
}
for (std::vector <size_t>::iterator si = sbc->second.begin (); si != sbc->second.end (); ++si) {
objects::iterator &s = obj_ptrs [*si];
lay::ObjectInstPath new_path (*s);
new_path.set_shape (new_shapes.find (s->shape ())->second);
// modify the selection
m_selection.erase (s);
s = m_selection.insert (new_path).first;
}
}
}
}
// 2.) then transform 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 <size_t> > insts_by_cell;
n = 0;
for (objects::iterator r = m_selection.begin (); r != m_selection.end (); ++r, ++n) {
if (r->is_cell_inst ()) {
insts_by_cell.insert (std::make_pair (std::make_pair (r->cell_index (), r->cv_index ()), std::vector <size_t> ())).first->second.push_back (n);
}
}
for (std::map <std::pair <db::cell_index_type, unsigned int>, std::vector <size_t> >::iterator ibc = insts_by_cell.begin (); ibc != insts_by_cell.end (); ++ibc) {
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 != 0) {
db::CplxTrans tt = (*tv_list) [0] * db::CplxTrans (cv->layout ().dbu ()) * cv.context_trans ();
db::ICplxTrans mt_mu (tt.inverted () * trans * tt);
std::map <db::Instance, db::Instance> new_insts;
db::Cell &cell = cv->layout ().cell (ibc->first.first);
for (std::vector <size_t>::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) {
objects::iterator i = obj_ptrs [*ii];
// mt = transformation in DBU units
db::ICplxTrans mt;
db::ICplxTrans t (i->trans ());
if (p_trv != 0 && *ii < p_trv->size ()) {
db::ICplxTrans t_mu (tt.inverted () * (*p_trv) [*ii] * tt);
mt = t.inverted () * t_mu * t;
} else {
mt = t.inverted () * mt_mu * t;
}
db::Instance inst_ptr = i->back ().inst_ptr;
std::map <db::Instance, db::Instance>::iterator ni = new_insts.find (inst_ptr);
if (ni == new_insts.end ()) {
new_insts.insert (std::make_pair (inst_ptr, cell.transform (inst_ptr, mt)));
} else {
ni->second = cell.transform (inst_ptr, mt);
}
}
for (std::vector <size_t>::iterator ii = ibc->second.begin (); ii != ibc->second.end (); ++ii) {
objects::iterator &i = obj_ptrs [*ii];
lay::ObjectInstPath new_path (*i);
new_path.back ().inst_ptr = new_insts.find (i->back ().inst_ptr)->second;
// modify the selection
m_selection.erase (i);
i = m_selection.insert (new_path).first;
}
}
}
}
handle_guiding_shape_changes ();
selection_to_view ();
}
void
Service::move_cancel ()
{
if (m_move_trans != db::DTrans () && m_moving) {
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->freeze ();
}
m_move_trans = db::DTrans ();
m_move_start = db::DPoint ();
// reset to unmoved or clear selection and do not do anything
if (m_move_sel) {
selection_to_view ();
} else {
clear_selection ();
}
m_moving = false;
}
}
bool
Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
if (view ()->is_editable () && prio) {
if (m_editing || m_immediate) {
m_alt_ac = ac_from_buttons (buttons);
if (! m_editing) {
// in this mode, ignore exceptions here since it is rather annoying to have messages popping
// up then.
try {
do_begin_edit (p);
m_editing = true;
} catch (...) {
set_edit_marker (0);
}
}
if (m_editing) {
do_mouse_move (p);
}
m_alt_ac = lay::AC_Global;
} else if (prio) {
do_mouse_move_inactive (p);
}
}
return false; // not taken to allow the mouse tracker to receive events as well
}
bool
Service::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
if (view ()->is_editable () && prio) {
if ((buttons & lay::LeftButton) != 0) {
m_alt_ac = ac_from_buttons (buttons);
if (! m_editing) {
view ()->cancel (); // cancel any pending edit operations and clear the selection
set_edit_marker (0);
do_begin_edit (p);
m_editing = true;
} else {
if (do_mouse_click (p)) {
m_editing = false;
set_edit_marker (0);
do_finish_edit ();
}
}
m_alt_ac = lay::AC_Global;
return true;
}
}
return false;
}
bool
Service::mouse_double_click_event (const db::DPoint & /*p*/, unsigned int buttons, bool prio)
{
if (m_editing && prio && (buttons & lay::LeftButton) != 0) {
m_alt_ac = ac_from_buttons (buttons);
do_finish_edit ();
m_editing = false;
set_edit_marker (0);
m_alt_ac = lay::AC_Global;
return true;
} else {
return false;
}
}
bool
Service::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio)
{
if (view ()->is_editable () && prio && (buttons & lay::RightButton) != 0 && m_editing) {
m_alt_ac = ac_from_buttons (buttons);
do_mouse_transform (p, db::DFTrans (db::DFTrans::r90));
m_alt_ac = lay::AC_Global;
return true;
} else {
return mouse_press_event (p, buttons, prio);
}
}
void
Service::activated ()
{
// make all editor option pages visible
activate_service (plugin_declaration (), true);
if (view ()->is_editable ()) {
view ()->cancel (); // cancel any pending edit operations and clear the selection
set_edit_marker (0);
m_immediate = do_activated ();
m_editing = false;
}
}
void
Service::deactivated ()
{
// make all editor option pages visible
activate_service (plugin_declaration (), false);
edit_cancel ();
m_immediate = false;
}
void
Service::edit_cancel ()
{
move_cancel ();
if (m_editing) {
do_cancel_edit ();
m_editing = false;
set_edit_marker (0);
}
}
void
Service::del ()
{
if (selection_size () > 0 && view ()->is_editable ()) {
// delete the selected objects
del_selected ();
}
}
void
Service::del_selected ()
{
std::set<db::Layout *> needs_cleanup;
// delete all shapes and instances.
for (objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
const lay::CellView &cv = view ()->cellview (r->cv_index ());
if (cv.is_valid ()) {
db::Cell &cell = cv->layout ().cell (r->cell_index ());
if (! r->is_cell_inst ()) {
if (r->layer () != cv->layout ().guiding_shape_layer () && cell.shapes (r->layer ()).is_valid (r->shape ())) {
cell.shapes (r->layer ()).erase_shape (r->shape ());
}
} else {
if (cell.is_valid (r->back ().inst_ptr)) {
if (cv->layout ().cell (r->back ().inst_ptr.cell_index ()).is_proxy ()) {
needs_cleanup.insert (& cv->layout ());
}
cell.erase (r->back ().inst_ptr);
}
}
}
}
// 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 ();
}
}
size_t
Service::selection_size ()
{
return m_selection.size ();
}
bool
Service::has_transient_selection ()
{
return ! m_transient_selection.empty ();
}
double
Service::click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mode)
{
// compute search box
double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag ();
db::DBox search_box = db::DBox (pos, pos).enlarged (db::DVector (l, l));
// for single-point selections either exclude the current selection or the
// accumulated previous selection from the search.
const objects *exclude = 0;
if (mode == lay::Editable::Replace) {
exclude = &m_previous_selection;
} else if (mode == lay::Editable::Add) {
exclude = &m_selection;
} else if (mode == lay::Editable::Reset) {
// TODO: the finder should favor the current selection in this case.
}
if (m_cell_inst_service) {
lay::InstFinder finder (true, view ()->is_editable () && m_top_level_sel, view ()->is_editable () /*full arrays in editable mode*/, true /*enclose_inst*/, exclude, true /*visible layers*/);
// go through all cell views
std::set< std::pair<db::DCplxTrans, int> > variants = view ()->cv_transform_variants ();
for (std::set< std::pair<db::DCplxTrans, int> >::const_iterator v = variants.begin (); v != variants.end (); ++v) {
finder.find (view (), v->second, v->first, search_box);
}
// Return the finder's proximity value
if (finder.begin () != finder.end ()) {
return finder.proximity ();
} else {
return lay::Editable::click_proximity (pos, mode);
}
} else {
lay::ShapeFinder finder (true, view ()->is_editable () && m_top_level_sel, m_flags, exclude);
// go through all visible layers of all cellviews
finder.find (view (), search_box);
// Return the finder's proximity value
if (finder.begin () != finder.end ()) {
return finder.proximity ();
} else {
return lay::Editable::click_proximity (pos, mode);
}
}
}
bool
Service::transient_select (const db::DPoint &pos)
{
clear_transient_selection ();
// if in move mode (which also receives transient_select requests) the move will take the selection,
// hence don't do a transient selection if there is one.
if (view ()->has_selection () && view ()->is_move_mode ()) {
return false;
}
// compute search box
double l = double (lay::search_range) / widget ()->mouse_event_trans ().mag ();
db::DBox search_box = db::DBox (pos, pos).enlarged (db::DVector (l, l));
if (m_cell_inst_service) {
lay::InstFinder finder (true, view ()->is_editable () && m_top_level_sel, view ()->is_editable () /*full arrays in editable mode*/, true /*enclose instances*/, &m_previous_selection, true /*visible layers only*/);
// go through all transform variants
std::set< std::pair<db::DCplxTrans, int> > variants = view ()->cv_transform_variants ();
for (std::set< std::pair<db::DCplxTrans, int> >::const_iterator v = variants.begin (); v != variants.end (); ++v) {
finder.find (view (), v->second, v->first, search_box);
}
// collect the founds from the finder
lay::InstFinder::iterator r = finder.begin ();
if (r != finder.end ()) {
m_transient_selection.insert (*r);
const lay::CellView &cv = view ()->cellview (r->cv_index ());
// compute the global transformation including movement, context and explicit transformation
double dbu = cv->layout ().dbu ();
db::ICplxTrans gt = db::VCplxTrans (1.0 / dbu) * db::DCplxTrans (m_move_trans) * db::CplxTrans (dbu) * cv.context_trans () * r->trans ();
tl_assert (r->is_cell_inst () == m_cell_inst_service);
db::Instance inst = r->back ().inst_ptr;
std::vector<db::DCplxTrans> tv = mp_view->cv_transform_variants (r->cv_index ());
if (view ()->is_editable ()) {
#if 0
// to show the content of the cell when the instance is selected:
lay::InstanceMarker *marker = new lay::InstanceMarker (view (), r->cv_index (), ! show_shapes_of_instances (), show_shapes_of_instances () ? max_shapes_of_instances () : 0);
#else
lay::InstanceMarker *marker = new lay::InstanceMarker (view (), r->cv_index ());
#endif
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
marker->set (inst, gt, tv);
marker->set_line_width (1);
marker->set_halo (0);
marker->set_text_enabled (false);
mp_transient_marker = marker;
} else {
// In viewer mode, individual instances of arrays can be selected. Since that is not supported by
// InstanceMarker, we just indicate the individual instance's bounding box.
lay::Marker *marker = new lay::Marker (view (), r->cv_index ());
db::box_convert<db::CellInst> bc (cv->layout ());
marker->set (bc (r->back ().inst_ptr.cell_inst ().object ()), gt * r->back ().inst_ptr.cell_inst ().complex_trans (*r->back ().array_inst), tv);
marker->set_vertex_size (0);
marker->set_line_width (1);
marker->set_halo (0);
mp_transient_marker = marker;
}
if (editables ()->selection_size () == 0) {
display_status (true);
}
return true;
} else {
return false;
}
} else {
lay::ShapeFinder finder (true, view ()->is_editable () && m_top_level_sel, m_flags, &m_previous_selection);
// go through all visible layers of all cellviews
finder.find (view (), search_box);
// collect the founds from the finder
lay::ShapeFinder::iterator r = finder.begin ();
if (r != finder.end ()) {
m_transient_selection.insert (*r);
const lay::CellView &cv = view ()->cellview (r->cv_index ());
// compute the global transformation including movement, context and explicit transformation
double dbu = cv->layout ().dbu ();
db::ICplxTrans gt = db::VCplxTrans (1.0 / dbu) * db::DCplxTrans (m_move_trans) * db::CplxTrans (dbu) * cv.context_trans () * r->trans ();
tl_assert (r->is_cell_inst () == m_cell_inst_service);
lay::ShapeMarker *marker = new lay::ShapeMarker (view (), r->cv_index ());
marker->set (r->shape (), gt, mp_view->cv_transform_variants (r->cv_index (), r->layer ()));
marker->set_vertex_size (0);
marker->set_line_width (1);
marker->set_halo (0);
mp_transient_marker = marker;
if (editables ()->selection_size () == 0) {
display_status (true);
}
return true;
} else {
return false;
}
}
}
static std::string path_to_string (const db::Layout &layout, const lay::ObjectInstPath &p)
{
std::string r;
lay::ObjectInstPath::iterator b = p.begin ();
lay::ObjectInstPath::iterator e = p.end ();
if (b != e && p.is_cell_inst ()) {
--e;
}
r += "\\("; // group separator for shortening
if (layout.is_valid_cell_index (p.topcell ())) {
r += layout.cell_name (p.topcell ());
} else {
r += "?";
}
r += "\\)";
while (b != e) {
r += "\\("; // group separator for shortening
r += "/";
if (layout.is_valid_cell_index (b->inst_ptr.cell_index ())) {
r += layout.cell_name (b->inst_ptr.cell_index ());
} else {
r += "?";
}
r += "\\)";
++b;
}
r += tl::sprintf ("@%d", p.cv_index () + 1);
return r;
}
void
Service::display_status (bool transient)
{
const objects *selection = transient ? &m_transient_selection : &m_selection;
if (selection->size () == 1) {
objects::const_iterator r = selection->begin ();
const db::Layout &layout = view ()->cellview (r->cv_index ())->layout ();
if (m_cell_inst_service) {
std::string msg;
if (! transient) {
msg = tl::to_string (QObject::tr ("selected: "));
}
db::Instance inst = r->back ().inst_ptr;
db::Vector a, b;
unsigned long amax = 0, bmax = 0;
if (! inst.is_regular_array (a, b, amax, bmax)) {
msg += tl::sprintf (tl::to_string (QObject::tr ("instance(\"%s\" %s)")), layout.display_name (inst.cell_index ()), inst.complex_trans ().to_string ());
} else {
msg += tl::sprintf (tl::to_string (QObject::tr ("instance(\"%s\" %s %ldx%ld)")), layout.display_name (inst.cell_index ()), inst.complex_trans ().to_string (), amax, bmax);
}
msg += tl::to_string (QObject::tr (" in "));
msg += path_to_string (layout, *r);
view ()->message (msg, transient ? 10 : 10000);
} else {
std::string msg;
if (! transient) {
msg = tl::to_string (QObject::tr ("selected: "));
}
if (r->shape ().is_box ()) {
db::Box b (r->shape ().bbox ());
msg += tl::sprintf (tl::to_string (QObject::tr ("box(%d,%d %d,%d)")), int (b.left ()), int (b.bottom ()), int (b.right ()), int (b.top ()));
} else if (r->shape ().is_text ()) {
msg += tl::sprintf (tl::to_string (QObject::tr ("text(\"%s\" %s)")), tl::escape_string (r->shape ().text_string ()), r->shape ().text_trans ().to_string ());
} else if (r->shape ().is_polygon ()) {
size_t npoints = 0;
for (db::Shape::polygon_edge_iterator e = r->shape ().begin_edge (); ! e.at_end (); ++e) {
++npoints;
}
msg += tl::sprintf (tl::to_string (QObject::tr ("polygon(#points=%lu)")), npoints);
} else if (r->shape ().is_path ()) {
size_t npoints = 0;
for (db::Shape::point_iterator p = r->shape ().begin_point (); p != r->shape ().end_point (); ++p) {
++npoints;
}
msg += tl::sprintf (tl::to_string (QObject::tr ("path(w=%d #points=%lu)")), int (r->shape ().path_width ()), npoints);
}
if (! msg.empty ()) {
msg += tl::to_string (QObject::tr (" on "));
std::string ln = layout.get_properties (r->layer ()).to_string ();
for (lay::LayerPropertiesConstIterator lp = view ()->begin_layers (); ! lp.at_end (); ++lp) {
if (lp->layer_index () == int (r->layer ()) && lp->cellview_index () == int (r->cv_index ())) {
ln = lp->display_string (view (), true, false);
break;
}
}
msg += ln;
msg += tl::to_string (QObject::tr (" in "));
msg += path_to_string (layout, *r);
view ()->message (msg, transient ? 10 : 10000);
}
}
} else {
view ()->message (std::string ());
}
}
void
Service::clear_transient_selection ()
{
if (mp_transient_marker) {
delete mp_transient_marker;
mp_transient_marker = 0;
}
m_transient_selection.clear ();
}
bool
Service::selection_applies (const lay::ObjectInstPath & /*sel*/) const
{
return false;
}
void
Service::clear_previous_selection ()
{
m_previous_selection.clear ();
}
bool
Service::select (const db::DBox &box, lay::Editable::SelectionMode mode)
{
// 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;
}
}
// for single-point selections either exclude the current selection or the
// accumulated previous selection from the search.
const objects *exclude = 0;
if (mode == lay::Editable::Replace) {
exclude = &m_previous_selection;
} else if (mode == lay::Editable::Add) {
exclude = &m_selection;
} else if (mode == lay::Editable::Reset) {
// TODO: the finder should favor the current selection in this case.
}
if (box.empty ()) {
// unconditional selection
if (mode == lay::Editable::Reset) {
if (! m_selection.empty ()) {
m_selection.clear ();
needs_update = true;
}
} else {
// extract all shapes
// TODO: not implemented yet
}
} else if (m_cell_inst_service) {
lay::InstFinder finder (box.is_point (), view ()->is_editable () && m_top_level_sel, view ()->is_editable () /*full arrays in editable mode*/, true /*enclose_inst*/, exclude, true /*only visible layers*/);
// go through all cell views
std::set< std::pair<db::DCplxTrans, int> > variants = view ()->cv_transform_variants ();
for (std::set< std::pair<db::DCplxTrans, int> >::const_iterator v = variants.begin (); v != variants.end (); ++v) {
finder.find (view (), v->second, v->first, search_box);
}
// collect the founds from the finder
for (lay::InstFinder::iterator f = finder.begin (); f != finder.end (); ++f) {
select (*f, mode);
if (box.is_point ()) {
m_previous_selection.insert (*f);
}
needs_update = true;
any_selected = true;
}
} else {
lay::ShapeFinder finder (box.is_point (), view ()->is_editable () && m_top_level_sel, m_flags, exclude);
// go through all visible layers of all cellviews
finder.find (view (), search_box);
// guiding shapes are only selected in point selection mode and even then, we
// only select the first shape
lay::ShapeFinder::iterator f0 = finder.begin ();
if (box.is_point () && f0 != finder.end () && f0->layer () == view ()->cellview (f0->cv_index ())->layout ().guiding_shape_layer ()) {
m_selection.clear ();
select (*f0, mode);
m_previous_selection.insert (*f0);
needs_update = true;
any_selected = true;
} else {
// clear the selection if it was consisting of a guiding shape before
objects::const_iterator s0 = m_selection.begin ();
if (s0 != m_selection.end () && s0->layer () == view ()->cellview (s0->cv_index ())->layout ().guiding_shape_layer ()) {
m_selection.clear ();
}
// collect the founds from the finder
for (lay::ShapeFinder::iterator f = finder.begin (); f != finder.end (); ++f) {
if (f->layer () != view ()->cellview (f->cv_index ())->layout ().guiding_shape_layer ()) {
select (*f, mode);
if (box.is_point ()) {
m_previous_selection.insert (*f);
}
needs_update = true;
any_selected = true;
}
}
}
}
// if required, update the list of ruler objects to display the selection
if (needs_update) {
selection_to_view ();
}
if (any_selected) {
display_status (false);
}
return any_selected;
}
void
Service::get_selection (std::vector <lay::ObjectInstPath> &sel) const
{
sel.clear ();
sel.reserve (m_selection.size ());
// positions will hold a set of iterators that are to be erased
for (std::set<lay::ObjectInstPath>::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
sel.push_back (*r);
}
}
bool
Service::select (const lay::ObjectInstPath &obj, lay::Editable::SelectionMode mode)
{
// allocate next sequence number
if (mode == lay::Editable::Replace) {
m_seq = 0;
} else if (mode != lay::Editable::Reset) {
++m_seq;
}
if (mode == lay::Editable::Replace || mode == lay::Editable::Add) {
// select
if (m_selection.find (obj) == m_selection.end ()) {
obj_iterator o = m_selection.insert (obj).first;
(const_cast <lay::ObjectInstPath &> (*o)).set_seq (m_seq); // we can do that since the sequence number is not part of the less operator
selection_to_view ();
return true;
}
} else if (mode == lay::Editable::Reset) {
// unselect
if (m_selection.find (obj) != m_selection.end ()) {
m_selection.erase (obj);
selection_to_view ();
return true;
}
} else {
// invert selection
if (m_selection.find (obj) != m_selection.end ()) {
m_selection.erase (obj);
} else {
obj_iterator o = m_selection.insert (obj).first;
(const_cast <lay::ObjectInstPath &> (*o)).set_seq (m_seq); // we can do that since the sequence number is not part of the less operator
}
selection_to_view ();
return true;
}
return false;
}
void
Service::move_markers (const db::DTrans &t)
{
if (m_move_trans != t) {
// display current move vector
if (selection_size () > 0) {
std::string pos = std::string ("dx: ") + tl::micron_to_string (t.disp ().x ()) + " dy: " + tl::micron_to_string (t.disp ().y ());
if (t.rot () != 0) {
pos += std::string (" ") + ((const db::DFTrans &) t).to_string ();
}
view ()->message (pos);
}
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
lay::GenericMarkerBase *marker = dynamic_cast<lay::GenericMarkerBase *> (*r);
if (marker) {
db::DCplxTrans dt = db::DCplxTrans (t) * db::DCplxTrans (m_move_trans).inverted ();
marker->set_trans (dt * marker->trans ());
}
}
m_move_trans = t;
}
}
void
Service::selection_to_view ()
{
// we don't handle the transient selection properly, so clear it for safety reasons
clear_transient_selection ();
// the selection objects need to be recreated since we destroyed the old markers
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
delete *r;
}
m_markers.clear ();
dm_selection_to_view ();
}
void
Service::do_selection_to_view ()
{
// Hint: this is a lower bound:
m_markers.reserve (m_selection.size ());
// build the transformation variants cache
TransformationVariants tv (view ());
for (std::set<lay::ObjectInstPath>::iterator r = m_selection.begin (); r != m_selection.end (); ++r) {
const lay::CellView &cv = view ()->cellview (r->cv_index ());
// compute the global transformation including movement, context and explicit transformation
double dbu = cv->layout ().dbu ();
db::ICplxTrans gt = db::VCplxTrans (1.0 / dbu) * db::DCplxTrans (m_move_trans) * db::CplxTrans (dbu) * cv.context_trans () * r->trans ();
tl_assert (r->is_cell_inst () == m_cell_inst_service);
if (m_cell_inst_service) {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv (r->cv_index ());
if (tv_list != 0) {
if (view ()->is_editable ()) {
#if 0
// to show the content of the cell when the instance is selected:
lay::InstanceMarker *marker = new lay::InstanceMarker (view (), r->cv_index (), ! show_shapes_of_instances (), show_shapes_of_instances () ? max_shapes_of_instances () : 0);
#else
lay::InstanceMarker *marker = new lay::InstanceMarker (view (), r->cv_index ());
#endif
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
if (r->seq () > 0 && m_indicate_secondary_selection) {
marker->set_dither_pattern (3);
}
marker->set (r->back ().inst_ptr, gt, *tv_list);
m_markers.push_back (marker);
} else {
lay::Marker *marker = new lay::Marker (view (), r->cv_index ());
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
if (r->seq () > 0 && m_indicate_secondary_selection) {
marker->set_dither_pattern (3);
}
db::box_convert<db::CellInst> bc (cv->layout ());
marker->set (bc (r->back ().inst_ptr.cell_inst ().object ()), gt * r->back ().inst_ptr.cell_inst ().complex_trans (*r->back ().array_inst), *tv_list);
m_markers.push_back (marker);
}
}
} else {
const std::vector<db::DCplxTrans> *tv_list = tv.per_cv_and_layer (r->cv_index (), r->layer ());
if (tv_list != 0) {
lay::ShapeMarker *marker = new lay::ShapeMarker (view (), r->cv_index ());
if (r->seq () > 0 && m_indicate_secondary_selection) {
marker->set_dither_pattern (3);
}
marker->set (r->shape (), gt, *tv_list);
if (r->shape ().is_text ()) {
// show the origins as crosses for texts
marker->set_vertex_shape (lay::ViewOp::Cross);
marker->set_vertex_size (9 /*cross vertex size*/);
}
m_markers.push_back (marker);
}
}
}
}
void
Service::set_selection (std::vector <lay::ObjectInstPath>::const_iterator s1, std::vector <lay::ObjectInstPath>::const_iterator s2)
{
m_selection.clear ();
m_selection.insert (s1, s2);
selection_to_view ();
}
void
Service::remove_selection (const lay::ObjectInstPath &sel)
{
m_selection.erase (sel);
selection_to_view ();
}
void
Service::add_selection (const lay::ObjectInstPath &sel)
{
m_selection.insert (sel);
selection_to_view ();
}
bool
Service::handle_guiding_shape_changes ()
{
// just allow one guiding shape to be selected
if (m_selection.empty ()) {
return false;
}
objects::const_iterator s = m_selection.begin ();
unsigned int cv_index = s->cv_index ();
lay::CellView cv = view ()->cellview (cv_index);
db::Layout *layout = &cv->layout ();
if (s->is_cell_inst () || s->layer () != layout->guiding_shape_layer ()) {
return false;
}
if (! s->shape ().has_prop_id ()) {
return false;
}
if (! layout->is_pcell_instance (s->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->end ();
if (e == s->begin ()) {
top_cell = s->cell_index ();
} else {
--e;
db::cell_index_type pc = s->topcell ();
if (e != s->begin ()) {
--e;
pc = e->inst_ptr.cell_index ();
}
parent_cell = pc;
parent_inst = s->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->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->cell_index (), parameters_for_pcell)) {
return false;
}
std::vector<lay::ObjectInstPath> 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) {
new_sel.push_back (*s);
new_sel.back ().back ().inst_ptr = new_inst;
new_sel.back ().back ().array_inst = new_inst.begin ();
new_sel.back ().set_shape (*sh);
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 ();
set_selection (new_sel.begin (), new_sel.end ());
return true;
}
// -------------------------------------------------------------
} // namespace edt