klayout/src/laybasic/layEditable.cc

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