mirror of https://github.com/KLayout/klayout.git
589 lines
15 KiB
C++
589 lines
15 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 "layEditable.h"
|
|
#include "dbClipboard.h"
|
|
#include "tlAssert.h"
|
|
|
|
#include "layPropertiesDialog.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace lay
|
|
{
|
|
|
|
// ----------------------------------------------------------------
|
|
// A helper compare functor
|
|
|
|
template <class X, class Y>
|
|
struct first_of_pair_cmp_f
|
|
{
|
|
bool operator() (const std::pair<X,Y> &p1, const std::pair<X,Y> &p2) const
|
|
{
|
|
return p1.first < p2.first;
|
|
}
|
|
};
|
|
|
|
// ----------------------------------------------------------------
|
|
// Editable implementation
|
|
|
|
Editable::Editable (lay::Editables *editables)
|
|
: mp_editables (editables)
|
|
{
|
|
if (editables) {
|
|
editables->m_editables.push_back (this);
|
|
}
|
|
}
|
|
|
|
Editable::~Editable ()
|
|
{
|
|
// erase the object from the table of enabled services
|
|
if (mp_editables) {
|
|
mp_editables->enable (this, false);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Editables implementation
|
|
|
|
Editables::Editables (db::Manager *manager)
|
|
: db::Object (manager), mp_properties_dialog (0), m_move_selection (false), m_any_move_operation (false)
|
|
{
|
|
// .. nothing yet ..
|
|
}
|
|
|
|
Editables::~Editables ()
|
|
{
|
|
cancel_edits ();
|
|
}
|
|
|
|
void
|
|
Editables::del ()
|
|
{
|
|
if (selection_size () > 0) {
|
|
|
|
cancel_edits ();
|
|
|
|
// begin the transaction
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Delete")));
|
|
// this dummy operation will update the screen:
|
|
manager ()->queue (this, new db::Op ());
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->del ();
|
|
}
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::cut ()
|
|
{
|
|
if (selection_size () > 0) {
|
|
|
|
cancel_edits ();
|
|
|
|
// this dummy operation will update the screen:
|
|
manager ()->queue (this, new db::Op ());
|
|
|
|
db::Clipboard::instance ().clear ();
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->cut ();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::copy ()
|
|
{
|
|
if (selection_size () > 0) {
|
|
db::Clipboard::instance ().clear ();
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->copy ();
|
|
}
|
|
}
|
|
}
|
|
|
|
db::DBox
|
|
Editables::selection_bbox ()
|
|
{
|
|
db::DBox sel_bbox;
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
sel_bbox += e->selection_bbox ();
|
|
}
|
|
return sel_bbox;
|
|
}
|
|
|
|
void
|
|
Editables::transform (const db::DCplxTrans &tr)
|
|
{
|
|
if (selection_size () > 0) {
|
|
|
|
try {
|
|
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Transform")));
|
|
// this dummy operation will update the screen:
|
|
manager ()->queue (this, new db::Op ());
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->transform (tr);
|
|
}
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
} catch (...) {
|
|
manager ()->clear ();
|
|
throw;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::paste ()
|
|
{
|
|
if (! db::Clipboard::instance ().empty ()) {
|
|
|
|
cancel_edits ();
|
|
|
|
// this dummy operation will update the screen:
|
|
if (manager ()->transacting ()) {
|
|
manager ()->queue (this, new db::Op ());
|
|
}
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->paste ();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::enable (lay::Editable *obj, bool en)
|
|
{
|
|
if (en) {
|
|
m_enabled.insert (obj);
|
|
} else {
|
|
cancel_edits ();
|
|
obj->select (db::DBox (), lay::Editable::Reset); // clear selection
|
|
m_enabled.erase (obj);
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::transient_select (const db::DPoint &pt)
|
|
{
|
|
bool same_point = (m_last_selected_point.is_point () && m_last_selected_point.center ().sq_double_distance (pt) < 1e-10);
|
|
if (! same_point) {
|
|
clear_previous_selection ();
|
|
}
|
|
|
|
m_last_selected_point = db::DBox (pt, pt);
|
|
|
|
// in a first pass evaluate the point selection proximity value to select
|
|
// those plugins that are active. This code is a copy of the code for the single-point selection below.
|
|
|
|
std::vector< std::pair <double, iterator> > plugins;
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
plugins.push_back (std::make_pair (e->click_proximity (pt, lay::Editable::Replace), e));
|
|
}
|
|
}
|
|
|
|
// sort the plugins found by the proximity
|
|
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
|
|
|
|
// and call select on those objects until the first one (with the least proximity)
|
|
// has something selected
|
|
std::vector< std::pair<double, iterator> >::const_iterator pi = plugins.begin ();
|
|
for ( ; pi != plugins.end (); ++pi) {
|
|
if (pi->second->transient_select (pt)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if no plugin has selected anything, try a reset (clear previous selection) and select again: this is supposed
|
|
// to implement the cycling protocol which allows the plugins to cycle through different
|
|
// selections for repeated clicks on the same point. Let this happen for replace mode
|
|
// only because otherwise clearing the selection does not make sense.
|
|
if (same_point && pi == plugins.end ()) {
|
|
|
|
clear_previous_selection ();
|
|
|
|
plugins.clear ();
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
plugins.push_back (std::make_pair (e->click_proximity (pt, lay::Editable::Replace), e));
|
|
}
|
|
}
|
|
|
|
// sort the plugins found by the proximity
|
|
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
|
|
|
|
for (pi = plugins.begin (); pi != plugins.end (); ++pi) {
|
|
if (pi->second->transient_select (pt)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_transient_selection_changed ();
|
|
}
|
|
|
|
void
|
|
Editables::clear_previous_selection ()
|
|
{
|
|
m_last_selected_point = db::DBox ();
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->clear_previous_selection ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::clear_transient_selection ()
|
|
{
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->clear_transient_selection ();
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_transient_selection_changed ();
|
|
}
|
|
|
|
void
|
|
Editables::clear_selection ()
|
|
{
|
|
cancel_edits ();
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->select (db::DBox (), lay::Editable::Reset); // clear selection
|
|
e->clear_transient_selection ();
|
|
e->clear_previous_selection ();
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_selection_changed ();
|
|
}
|
|
|
|
void
|
|
Editables::select ()
|
|
{
|
|
cancel_edits ();
|
|
clear_transient_selection ();
|
|
clear_previous_selection ();
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
e->select (db::DBox (), lay::Editable::Replace); // select "all"
|
|
}
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_selection_changed ();
|
|
}
|
|
|
|
void
|
|
Editables::select (const db::DBox &box, lay::Editable::SelectionMode mode)
|
|
{
|
|
if (box.is_point ()) {
|
|
select (box.center (), mode);
|
|
} else {
|
|
|
|
cancel_edits ();
|
|
clear_transient_selection ();
|
|
clear_previous_selection ();
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
e->select (box, mode);
|
|
}
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_selection_changed ();
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::select (const db::DPoint &pt, lay::Editable::SelectionMode mode)
|
|
{
|
|
bool same_point = (m_last_selected_point.is_point () && m_last_selected_point.center ().sq_double_distance (pt) < 1e-10);
|
|
if (! same_point) {
|
|
clear_previous_selection ();
|
|
}
|
|
|
|
m_last_selected_point = db::DBox (pt, pt);
|
|
|
|
cancel_edits ();
|
|
clear_transient_selection ();
|
|
|
|
// in a first pass evaluate the point selection proximity value to select
|
|
// those plugins that are active.
|
|
|
|
std::vector< std::pair <double, iterator> > plugins;
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
plugins.push_back (std::make_pair (e->click_proximity (pt, mode), e));
|
|
}
|
|
}
|
|
|
|
// sort the plugins found by the proximity
|
|
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
|
|
|
|
// and call select on those objects until the first one (with the least proximity)
|
|
// has something selected
|
|
std::vector< std::pair<double, iterator> >::const_iterator pi = plugins.begin ();
|
|
for ( ; pi != plugins.end (); ++pi) {
|
|
if (pi->second->select (db::DBox (pt, pt), mode)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if no plugin has selected anything, try a reset (clear_previous selection) and select again: this is supposed
|
|
// to implement the cycling protocol which allows the plugins to cycle through different
|
|
// selections for repeated clicks on the same point. Let this happen for replace mode
|
|
// only because otherwise clearing the selection does not make sense.
|
|
if (same_point && pi == plugins.end () && mode == lay::Editable::Replace) {
|
|
|
|
clear_previous_selection ();
|
|
|
|
plugins.clear ();
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
plugins.push_back (std::make_pair (e->click_proximity (pt, mode), e));
|
|
}
|
|
}
|
|
|
|
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
|
|
|
|
for (pi = plugins.begin (); pi != plugins.end (); ++pi) {
|
|
if (pi->second->select (db::DBox (pt, pt), mode)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// in replace mode clear the selections on the following plugins
|
|
if (mode == lay::Editable::Replace && pi != plugins.end ()) {
|
|
for (++pi ; pi != plugins.end (); ++pi) {
|
|
pi->second->select (db::DBox (), lay::Editable::Reset);
|
|
}
|
|
}
|
|
|
|
// send a signal to the observers
|
|
signal_selection_changed ();
|
|
}
|
|
|
|
bool
|
|
Editables::begin_move (const db::DPoint &p, lay::angle_constraint_type ac)
|
|
{
|
|
cancel_edits ();
|
|
clear_previous_selection ();
|
|
|
|
m_move_selection = false;
|
|
m_any_move_operation = false;
|
|
|
|
// In a first pass evaluate the point selection proximity value to select
|
|
// those plugins that are close to the given point. This code is a copy of the code for the single-point selection below.
|
|
std::vector< std::pair <double, iterator> > plugins;
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
if (m_enabled.find (&*e) != m_enabled.end ()) {
|
|
plugins.push_back (std::make_pair (e->click_proximity (p, lay::Editable::Replace), e));
|
|
}
|
|
}
|
|
|
|
// sort the plugins found by the proximity
|
|
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
|
|
|
|
if (selection_size () > 0 && selection_bbox ().contains (p)) {
|
|
|
|
// if anything is selected and we are within the selection bbox,
|
|
// issue a move operation on all editables: first try a Partial mode begin_move
|
|
for (std::vector< std::pair<double, iterator> >::const_iterator pi = plugins.begin (); pi != plugins.end (); ++pi) {
|
|
|
|
if (pi->second->begin_move (Editable::Partial, p, ac)) {
|
|
|
|
// clear the selection on all other plugins, because we focus on one plugin
|
|
for (std::vector< std::pair<double, iterator> >::const_iterator pii = plugins.begin (); pii != plugins.end (); ++pii) {
|
|
if (pii->second != pi->second) {
|
|
pii->second->select (db::DBox (), lay::Editable::Reset);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if something is selected, issue a move operation on all editables
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->begin_move (Editable::Selected, p, ac);
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// don't move the selection - clear the existing one first
|
|
clear_selection ();
|
|
|
|
// if nothing is selected, issue a move operation on all editables
|
|
std::vector< std::pair<double, iterator> >::const_iterator pi = plugins.begin ();
|
|
// HACK: only the first plugin (measured through click_proximity) has a chance to intercept
|
|
// the standard procedure which is select + move selected. The reason behind that is that
|
|
// for example edtService does not implement Any but rather relies on select + Selected mode.
|
|
// Hence allowing some "later" plugin to override it at this point would break the least
|
|
// proximity priority rule.
|
|
if (pi != plugins.end () && pi->second->begin_move (Editable::Any, p, ac)) {
|
|
return true;
|
|
}
|
|
|
|
// nothing particular selected - select ourselves and start over
|
|
select (p, Editable::Replace);
|
|
|
|
// now we assume we have a selection - try to begin_move on this.
|
|
if (selection_size () > 0) {
|
|
m_move_selection = true;
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->begin_move (Editable::Selected, p, ac);
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::move (const db::DPoint &p, lay::angle_constraint_type ac)
|
|
{
|
|
m_any_move_operation = true;
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->move (p, ac);
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::move_transform (const db::DPoint &p, db::DFTrans t, lay::angle_constraint_type ac)
|
|
{
|
|
m_any_move_operation = true;
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->move_transform (p, t, ac);
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::end_move (const db::DPoint &p, lay::angle_constraint_type ac)
|
|
{
|
|
if (m_any_move_operation) {
|
|
|
|
// begin the transaction
|
|
tl_assert (! manager ()->transacting ());
|
|
manager ()->transaction (tl::to_string (QObject::tr ("Move")));
|
|
// this dummy operation will update the screen:
|
|
manager ()->queue (this, new db::Op ());
|
|
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->end_move (p, ac);
|
|
}
|
|
|
|
// end the transaction
|
|
manager ()->commit ();
|
|
|
|
// clear the selection that was set previously
|
|
if (m_move_selection) {
|
|
clear_selection ();
|
|
}
|
|
|
|
} else {
|
|
|
|
// if nothing was moved, treat the end_move as a select which makes the move sticky or
|
|
// replaces a complex selection by a simple one
|
|
edit_cancel ();
|
|
select (p, Editable::Replace);
|
|
|
|
}
|
|
}
|
|
|
|
size_t
|
|
Editables::selection_size ()
|
|
{
|
|
size_t c = 0;
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
c += e->selection_size ();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void
|
|
Editables::edit_cancel ()
|
|
{
|
|
clear_previous_selection ();
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->edit_cancel ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::cancel_edits ()
|
|
{
|
|
// close the property dialog
|
|
if (mp_properties_dialog) {
|
|
delete mp_properties_dialog;
|
|
}
|
|
mp_properties_dialog = 0;
|
|
|
|
// cancel any edit operations
|
|
for (iterator e = begin (); e != end (); ++e) {
|
|
e->edit_cancel ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Editables::show_properties (QWidget *parent)
|
|
{
|
|
cancel_edits ();
|
|
mp_properties_dialog = new lay::PropertiesDialog (parent, manager (), this);
|
|
mp_properties_dialog->show ();
|
|
}
|
|
|
|
}
|
|
|