/* 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::iterator r = m_markers.begin (); r != m_markers.end (); ++r) { delete *r; } m_markers.clear (); for (std::vector::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::iterator r = m_markers.begin (); r != m_markers.end (); ++r) { (*r)->visible (false); } } void Service::restore_highlights () { for (std::vector::iterator r = m_markers.begin (); r != m_markers.end (); ++r) { (*r)->visible (true); } } void Service::highlight (unsigned int n) { for (std::vector::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 cv_indices; for (objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { cv_indices.insert (r->cv_index ()); } for (std::set ::const_iterator cvi = cv_indices.begin (); cvi != cv_indices.end (); ++cvi) { db::ClipboardValue *cd = new db::ClipboardValue (); // 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::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 (*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 bc (layout); if (! r->is_cell_inst ()) { const std::vector *tv_list = tv.per_cv_and_layer (r->cv_index (), r->layer ()); if (tv_list != 0) { for (std::vector::const_iterator t = tv_list->begin (); t != tv_list->end (); ++t) { box += *t * (ctx_trans * r->shape ().bbox ()); } } } else { const std::vector *tv_list = tv.per_cv (r->cv_index ()); if (tv_list != 0) { for (std::vector::const_iterator t = tv_list->begin (); t != tv_list->end (); ++t) { box += *t * (ctx_trans * r->back ().bbox (bc)); } } } } return box; } void Service::set_edit_marker (lay::ViewObject *edit_marker) { for (std::vector::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 *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 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::vector > 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 ())).first->second.push_back (n); } } for (std::map >, std::vector >::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 *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 new_shapes; for (std::vector ::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 ::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 ::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::vector > 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 ())).first->second.push_back (n); } } for (std::map , std::vector >::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 *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 new_insts; db::Cell &cell = cv->layout ().cell (ibc->first.first); for (std::vector ::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 ::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 ::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::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 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::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 > variants = view ()->cv_transform_variants (); for (std::set< std::pair >::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 > variants = view ()->cv_transform_variants (); for (std::set< std::pair >::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 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 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 > variants = view ()->cv_transform_variants (); for (std::set< std::pair >::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 &sel) const { sel.clear (); sel.reserve (m_selection.size ()); // positions will hold a set of iterators that are to be erased for (std::set::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 (*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 (*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::iterator r = m_markers.begin (); r != m_markers.end (); ++r) { lay::GenericMarkerBase *marker = dynamic_cast (*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::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::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 *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 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 *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 ::const_iterator s1, std::vector ::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::max (); db::cell_index_type parent_cell = std::numeric_limits::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 new_sel; if (parent_cell != std::numeric_limits ::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 ::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