WIP: callbacks for PCells

This commit is contained in:
Matthias Koefferlein 2022-10-27 00:42:28 +02:00
parent 879059f830
commit e6da3fc22c
4 changed files with 498 additions and 148 deletions

View File

@ -61,6 +61,7 @@ public:
t_layer, // a layer (value is a db::LayerProperties object)
t_shape, // a shape (a db::Point, db::Box, db::Polygon, db::Edge or db::Path) rendering a guiding shape
t_list, // a list of strings
t_callback, // callback only (button)
t_none // no specific type
};
@ -323,6 +324,174 @@ public:
std::string symbolic;
};
/**
* @brief Represents the dynamic state of a single parameter
*/
class DB_PUBLIC ParameterState
{
public:
/**
* @brief Parameterized constructor
*/
ParameterState ()
: m_value (), m_visible (true), m_enabled (true),
m_value_changed (false), m_visible_changed (false), m_enabled_changed (false)
{
// .. nothing yet ..
}
/**
* @brief Gets the value
*/
const tl::Variant &value () const
{
return m_value;
}
/**
* @brief Sets the value
*/
void set_value (const tl::Variant &v)
{
if (m_value != v) {
m_value = v;
m_value_changed = true;
}
}
/**
* @brief Gets a value indicating wheter the value has changed
*/
bool value_changed () const
{
return m_value_changed;
}
/**
* @brief Gets the visibility state
*/
bool is_visible () const
{
return m_visible;
}
/**
* @brief Sets the visibility
*/
void set_visible (bool v)
{
if (m_visible != v) {
m_visible = v;
m_visible_changed = true;
}
}
/**
* @brief Gets a value indicating wheter the visibility has changed
*/
bool visible_changed () const
{
return m_visible_changed;
}
/**
* @brief Gets the enabled state
*/
bool is_enabled () const
{
return m_enabled;
}
/**
* @brief Sets the enabled state
*/
void set_enabled (bool v)
{
if (m_enabled != v) {
m_enabled = v;
m_enabled_changed = true;
}
}
/**
* @brief Gets a value indicating wheter the enabled state has changed
*/
bool enabled_changed () const
{
return m_enabled_changed;
}
/**
* @brief Resets the modified flags
*/
void reset ()
{
m_enabled_changed = m_visible_changed = m_value_changed = false;
}
private:
tl::Variant m_value;
bool m_visible, m_enabled;
bool m_value_changed, m_visible_changed, m_enabled_changed;
};
/**
* @brief Represents the state of call parameters for the callback implementation
*/
class DB_PUBLIC ParameterStates
{
public:
/**
* @brief Default constructor
*/
ParameterStates ()
: m_states ()
{
// .. nothing yet ..
}
/**
* @brief Sets a parameter from a given state
*/
void set_parameter (const std::string &name, const ParameterState &ps)
{
m_states [name] = ps;
}
/**
* @brief Gets the parameter state for the parameter with the given name
*
* If the name is not a valid parameter name, the behavior is undefined.
*/
ParameterState &parameter (const std::string &name)
{
return m_states [name];
}
/**
* @brief Gets the parameter state for the parameter with the given name
*
* If the name is not a valid parameter name, the behavior is undefined.
*/
const ParameterState &parameter (const std::string &name) const
{
return const_cast<ParameterStates *> (this)->parameter (name);
}
/**
* @brief Resets the modified flags
*/
void reset ()
{
for (auto p = m_states.begin (); p != m_states.end (); ++p) {
p->second.reset ();
}
}
public:
std::map<std::string, ParameterState> m_states;
};
/**
* @brief A declaration for a PCell
*/
@ -374,7 +543,25 @@ public:
}
/**
* @brief Produce a layout for the given parameter set and using the given layers.
* @brief Callback on parameter change
*
* This method allows implementing dynamic behavior on the change of a parameter value.
* A ParameterStatus object is supplied that allows changing parameter enabled status, visibility and value.
* The callback also acts as receiver for t_callback type parameters which only present a button.
*
* The callback function receives the name of the parameter that was changed.
* On some occasions, the callback is called unspecifically, for example for the initialization.
* In that case, the parameter name is empty.
*
* Exceptions from this implementation are ignored.
*/
virtual void callback (const db::Layout & /*layout*/, const std::string & /*name*/, ParameterStates & /*states*/) const
{
// the default implementation does nothing
}
/**
* @brief Produces a layout for the given parameter set and using the given layers.
*
* A reimplementation of that method should produce the desired layout for the given parameter set.
* The layout shall be put into the given cell. This code may create cell instances to other cells

View File

@ -634,6 +634,11 @@ static unsigned int pd_type_list ()
return (unsigned int) db::PCellParameterDeclaration::t_list;
}
static unsigned int pd_type_callback ()
{
return (unsigned int) db::PCellParameterDeclaration::t_callback;
}
static unsigned int pd_type_none ()
{
return (unsigned int) db::PCellParameterDeclaration::t_none;
@ -763,7 +768,8 @@ Class<db::PCellParameterDeclaration> decl_PCellParameterDeclaration ("db", "PCel
gsi::method ("TypeList", &pd_type_list, "@brief Type code: a list of variants") +
gsi::method ("TypeLayer", &pd_type_layer, "@brief Type code: a layer (a \\LayerInfo object)") +
gsi::method ("TypeShape", &pd_type_shape, "@brief Type code: a guiding shape (Box, Edge, Point, Polygon or Path)") +
gsi::method ("TypeNone", &pd_type_none, "@brief Type code: unspecific type")
gsi::method ("TypeCallback", &pd_type_callback, "@brief Type code: a button triggering a callback") +
gsi::method ("TypeNone", &pd_type_none, "@brief Type code: unspecific type")
,
"@brief A PCell parameter declaration\n"
"\n"

View File

@ -238,13 +238,15 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
mp_pcell_decl.reset (const_cast<db::PCellDeclaration *> (pcell_decl)); // no const weak_ptr ...
mp_view = view;
m_cv_index = cv_index;
m_parameters = parameters;
m_states = db::ParameterStates ();
m_initial_parameters.clear ();
if (mp_parameters_area) {
delete mp_parameters_area;
}
m_widgets.clear ();
m_all_widgets.clear ();
mp_parameters_area = new QScrollArea (this);
mp_parameters_area->setFrameShape (QFrame::NoFrame);
@ -276,7 +278,23 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
const std::vector<db::PCellParameterDeclaration> &pcp = pcell_decl->parameter_declarations ();
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p, ++r) {
if (p->is_hidden () || p->get_type () == db::PCellParameterDeclaration::t_shape) {
tl::Variant value;
if (r < int (parameters.size ())) {
value = parameters [r];
} else {
value = p->get_default ();
}
m_initial_parameters.push_back (value);
db::ParameterState &ps = m_states.parameter (p->get_name ());
ps.set_value (value);
ps.set_enabled (! p->is_readonly ());
ps.set_visible (! p->is_hidden ());
m_all_widgets.push_back (std::vector<QWidget *> ());
if (p->get_type () == db::PCellParameterDeclaration::t_shape) {
m_widgets.push_back (0);
continue;
}
@ -324,13 +342,10 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
}
inner_grid->addWidget (new QLabel (tl::to_qstring (description), inner_frame), row, 0);
tl::Variant value;
if (r < int (parameters.size ())) {
value = parameters [r];
} else {
value = p->get_default ();
if (p->get_type () != db::PCellParameterDeclaration::t_callback) {
QLabel *l = new QLabel (tl::to_qstring (description), inner_frame);
inner_grid->addWidget (l, row, 0);
m_all_widgets.back ().push_back (l);
}
if (p->get_choices ().empty ()) {
@ -347,7 +362,6 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
f->setFrameShape (QFrame::NoFrame);
QLineEdit *le = new QLineEdit (f);
le->setEnabled (! p->is_readonly ());
hb->addWidget (le);
le->setMaximumWidth (150);
le->setObjectName (tl::to_qstring (p->get_name ()));
@ -358,20 +372,36 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
ul->setText (tl::to_qstring (p->get_unit ()));
inner_grid->addWidget (f, row, 1);
m_all_widgets.back ().push_back (f);
connect (le, SIGNAL (editingFinished ()), this, SLOT (parameter_changed ()));
}
break;
case db::PCellParameterDeclaration::t_callback:
{
QPushButton *pb = new QPushButton (inner_frame);
pb->setObjectName (tl::to_qstring (p->get_name ()));
pb->setText (tl::to_qstring (description));
pb->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Preferred);
m_widgets.push_back (pb);
inner_grid->addWidget (pb, row, 1);
m_all_widgets.back ().push_back (pb);
connect (pb, SIGNAL (clicked ()), this, SLOT (parameter_changed ()));
}
break;
case db::PCellParameterDeclaration::t_string:
case db::PCellParameterDeclaration::t_shape:
case db::PCellParameterDeclaration::t_list:
{
QLineEdit *le = new QLineEdit (inner_frame);
le->setEnabled (! p->is_readonly ());
le->setObjectName (tl::to_qstring (p->get_name ()));
m_widgets.push_back (le);
inner_grid->addWidget (le, row, 1);
m_all_widgets.back ().push_back (le);
connect (le, SIGNAL (editingFinished ()), this, SLOT (parameter_changed ()));
}
@ -380,12 +410,12 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
case db::PCellParameterDeclaration::t_layer:
{
lay::LayerSelectionComboBox *ly = new lay::LayerSelectionComboBox (inner_frame);
ly->setEnabled (! p->is_readonly ());
ly->set_no_layer_available (true);
ly->set_view (mp_view, m_cv_index, true /*all layers*/);
ly->setObjectName (tl::to_qstring (p->get_name ()));
m_widgets.push_back (ly);
inner_grid->addWidget (ly, row, 1);
m_all_widgets.back ().push_back (ly);
connect (ly, SIGNAL (activated (int)), this, SLOT (parameter_changed ()));
}
@ -396,10 +426,10 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
QCheckBox *cbx = new QCheckBox (inner_frame);
// this makes the checkbox not stretch over the full width - better when navigating with tab
cbx->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Preferred));
cbx->setEnabled (! p->is_readonly ());
cbx->setObjectName (tl::to_qstring (p->get_name ()));
m_widgets.push_back (cbx);
inner_grid->addWidget (cbx, row, 1);
m_all_widgets.back ().push_back (cbx);
connect (cbx, SIGNAL (stateChanged (int)), this, SLOT (parameter_changed ()));
}
@ -426,16 +456,14 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
connect (cb, SIGNAL (activated (int)), this, SLOT (parameter_changed ()));
cb->setEnabled (! p->is_readonly ());
cb->setMinimumContentsLength (30);
cb->setSizeAdjustPolicy (QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_widgets.push_back (cb);
inner_grid->addWidget (cb, row, 1);
m_all_widgets.back ().push_back (cb);
}
set_value (*p, m_widgets.back (), value);
++row;
if (inner_frame == main_frame) {
++main_row;
@ -443,12 +471,64 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P
}
// initial callback
try {
mp_pcell_decl->callback (mp_view->cellview (m_cv_index)->layout (), std::string (), m_states);
} catch (tl::Exception &ex) {
// potentially caused by script errors in callback implementation
tl::error << ex.msg ();
} catch (std::runtime_error &ex) {
tl::error << ex.what ();
} catch (...) {
// ignore other errors
}
update_widgets_from_states (m_states);
m_states.reset ();
mp_parameters_area->setWidget (main_frame);
main_frame->show ();
update_current_parameters ();
}
void
PCellParametersPage::update_widgets_from_states (const db::ParameterStates &states)
{
if (! mp_pcell_decl) {
return;
}
bool update_needed = false;
size_t i = 0;
const std::vector<db::PCellParameterDeclaration> &pcp = mp_pcell_decl->parameter_declarations ();
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p, ++i) {
const std::string &name = p->get_name ();
const db::ParameterState &ps = states.parameter (name);
if (ps.value_changed ()) {
update_needed = true;
set_value (*p, m_widgets [i], ps.value ());
}
if (ps.enabled_changed ()) {
m_widgets [i]->setEnabled (ps.is_enabled ());
}
if (ps.visible_changed ()) {
for (auto w = m_all_widgets [i].begin (); w != m_all_widgets [i].end (); ++w) {
(*w)->setVisible (ps.is_enabled ());
}
}
}
mp_update_frame->setVisible (update_needed);
}
PCellParametersPage::State
PCellParametersPage::get_state ()
{
@ -483,9 +563,51 @@ PCellParametersPage::set_state (const State &s)
}
}
void
void
PCellParametersPage::parameter_changed ()
{
if (! mp_pcell_decl) {
return;
}
if (! mp_view->cellview (m_cv_index).is_valid ()) {
return;
}
const std::vector<db::PCellParameterDeclaration> &pcp = mp_pcell_decl->parameter_declarations ();
const db::PCellParameterDeclaration *pd = 0;
for (auto w = m_widgets.begin (); w != m_widgets.end (); ++w) {
if (*w == sender ()) {
pd = &pcp [w - m_widgets.begin ()];
break;
}
}
try {
db::ParameterStates states = m_states;
states.reset ();
bool edit_error = false;
get_parameters_internal (states, edit_error);
if (! edit_error) {
mp_pcell_decl->callback (mp_view->cellview (m_cv_index)->layout (), pd ? pd->get_name () : std::string (), states);
update_widgets_from_states (states);
states.reset ();
m_states = states;
}
} catch (tl::Exception &ex) {
// potentially caused by script errors in callback implementation
tl::error << ex.msg ();
} catch (std::runtime_error &ex) {
tl::error << ex.what ();
} catch (...) {
// ignore other errors
}
dm_parameter_changed ();
}
@ -493,9 +615,10 @@ void
PCellParametersPage::do_parameter_changed ()
{
// does a coerce and update
bool ok = false;
std::vector<tl::Variant> parameters = get_parameters (&ok);
if (ok && ! lazy_evaluation ()) {
bool edit_error = false;
db::ParameterStates states = m_states;
get_parameters_internal (states, edit_error);
if (! edit_error && ! lazy_evaluation ()) {
emit edited ();
}
}
@ -521,6 +644,125 @@ PCellParametersPage::update_current_parameters ()
return ok;
}
void
PCellParametersPage::get_parameters_internal (db::ParameterStates &states, bool &edit_error)
{
edit_error = true;
int r = 0;
const std::vector<db::PCellParameterDeclaration> &pcp = mp_pcell_decl->parameter_declarations ();
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p, ++r) {
db::ParameterState &ps = states.parameter (p->get_name ());
if (! ps.is_visible () || ! ps.is_enabled () || p->get_type () == db::PCellParameterDeclaration::t_shape) {
continue;
}
if (p->get_choices ().empty ()) {
switch (p->get_type ()) {
case db::PCellParameterDeclaration::t_int:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
try {
int v = 0;
tl::from_string_ext (tl::to_string (le->text ()), v);
ps.set_value (tl::Variant (v));
lay::indicate_error (le, (tl::Exception *) 0);
} catch (tl::Exception &ex) {
lay::indicate_error (le, &ex);
edit_error = false;
}
}
}
break;
case db::PCellParameterDeclaration::t_double:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
try {
double v = 0;
tl::from_string_ext (tl::to_string (le->text ()), v);
ps.set_value (tl::Variant (v));
lay::indicate_error (le, (tl::Exception *) 0);
} catch (tl::Exception &ex) {
lay::indicate_error (le, &ex);
edit_error = false;
}
}
}
break;
case db::PCellParameterDeclaration::t_string:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
ps.set_value (tl::Variant (tl::to_string (le->text ())));
}
}
break;
case db::PCellParameterDeclaration::t_list:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
std::vector<std::string> values = tl::split (tl::to_string (le->text ()), ",");
ps.set_value (tl::Variant (values.begin (), values.end ()));
}
}
break;
case db::PCellParameterDeclaration::t_layer:
{
lay::LayerSelectionComboBox *ly = dynamic_cast<lay::LayerSelectionComboBox *> (m_widgets [r]);
if (ly) {
ps.set_value (tl::Variant (ly->current_layer_props ()));
}
}
break;
case db::PCellParameterDeclaration::t_boolean:
{
QCheckBox *cbx = dynamic_cast<QCheckBox *> (m_widgets [r]);
if (cbx) {
ps.set_value (tl::Variant (cbx->isChecked ()));
}
}
break;
default:
break;
}
} else {
QComboBox *cb = dynamic_cast<QComboBox*> (m_widgets [r]);
if (cb && cb->currentIndex () >= 0 && cb->currentIndex () < int (p->get_choices ().size ())) {
ps.set_value (p->get_choices () [cb->currentIndex ()]);
}
}
}
}
std::vector<tl::Variant>
PCellParametersPage::get_parameters (bool *ok)
{
@ -532,130 +774,17 @@ PCellParametersPage::get_parameters (bool *ok)
throw tl::Exception (tl::to_string (tr ("PCell no longer valid.")));
}
bool edit_error = true;
bool edit_error = false;
db::ParameterStates states = m_states;
get_parameters_internal (states, edit_error);
int r = 0;
const std::vector<db::PCellParameterDeclaration> &pcp = mp_pcell_decl->parameter_declarations ();
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p, ++r) {
if (p->is_hidden () || p->get_type () == db::PCellParameterDeclaration::t_shape) {
if (r < (int) m_parameters.size ()) {
parameters.push_back (m_parameters [r]);
} else {
parameters.push_back (p->get_default ());
}
} else {
parameters.push_back (tl::Variant ());
if (p->get_choices ().empty ()) {
switch (p->get_type ()) {
case db::PCellParameterDeclaration::t_int:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
try {
int v = 0;
tl::from_string_ext (tl::to_string (le->text ()), v);
parameters.back () = tl::Variant (v);
lay::indicate_error (le, (tl::Exception *) 0);
} catch (tl::Exception &ex) {
lay::indicate_error (le, &ex);
edit_error = false;
}
}
}
break;
case db::PCellParameterDeclaration::t_double:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
try {
double v = 0;
tl::from_string_ext (tl::to_string (le->text ()), v);
parameters.back () = tl::Variant (v);
lay::indicate_error (le, (tl::Exception *) 0);
} catch (tl::Exception &ex) {
lay::indicate_error (le, &ex);
edit_error = false;
}
}
}
break;
case db::PCellParameterDeclaration::t_string:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
parameters.back () = tl::Variant (tl::to_string (le->text ()));
}
}
break;
case db::PCellParameterDeclaration::t_list:
{
QLineEdit *le = dynamic_cast<QLineEdit *> (m_widgets [r]);
if (le) {
std::vector<std::string> values = tl::split (tl::to_string (le->text ()), ",");
parameters.back () = tl::Variant (values.begin (), values.end ());
}
}
break;
case db::PCellParameterDeclaration::t_layer:
{
lay::LayerSelectionComboBox *ly = dynamic_cast<lay::LayerSelectionComboBox *> (m_widgets [r]);
if (ly) {
parameters.back () = tl::Variant (ly->current_layer_props ());
}
}
break;
case db::PCellParameterDeclaration::t_boolean:
{
QCheckBox *cbx = dynamic_cast<QCheckBox *> (m_widgets [r]);
if (cbx) {
parameters.back () = tl::Variant (cbx->isChecked ());
}
}
break;
default:
break;
}
} else {
QComboBox *cb = dynamic_cast<QComboBox*> (m_widgets [r]);
if (cb && cb->currentIndex () >= 0 && cb->currentIndex () < int (p->get_choices ().size ())) {
parameters.back () = p->get_choices () [cb->currentIndex ()];
}
}
}
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p) {
parameters.push_back (states.parameter (p->get_name ()).value ());
}
if (! edit_error) {
if (edit_error) {
throw tl::Exception (tl::to_string (tr ("There are errors. See the highlighted edit fields for details.")));
}
@ -698,7 +827,34 @@ PCellParametersPage::get_parameters (bool *ok)
void
PCellParametersPage::set_parameters (const std::vector<tl::Variant> &parameters)
{
m_parameters = parameters;
if (! mp_pcell_decl) {
return;
}
size_t r = 0;
const std::vector<db::PCellParameterDeclaration> &pcp = mp_pcell_decl->parameter_declarations ();
for (std::vector<db::PCellParameterDeclaration>::const_iterator p = pcp.begin (); p != pcp.end (); ++p, ++r) {
db::ParameterState &ps = m_states.parameter (p->get_name ());
if (r < parameters.size ()) {
ps.set_value (parameters [r]);
} else {
ps.set_value (p->get_default ());
}
}
try {
if (mp_view->cellview (m_cv_index).is_valid ()) {
mp_pcell_decl->callback (mp_view->cellview (m_cv_index)->layout (), std::string (), m_states);
}
} catch (tl::Exception &ex) {
// potentially caused by script errors in callback implementation
tl::error << ex.msg ();
} catch (std::runtime_error &ex) {
tl::error << ex.what ();
} catch (...) {
// ignore other errors
}
set_parameters_internal (parameters, false);
}

View File

@ -103,12 +103,10 @@ public:
/**
* @brief Gets the initial parameters
*
* The initial parameters are the ones present on "setup".
*/
const std::vector<tl::Variant> &initial_parameters () const
{
return m_parameters;
return m_initial_parameters;
}
/**
@ -141,18 +139,21 @@ private:
QFrame *mp_error_frame, *mp_update_frame;
tl::weak_ptr<db::PCellDeclaration> mp_pcell_decl;
std::vector<QWidget *> m_widgets;
std::vector<std::vector<QWidget *> > m_all_widgets;
lay::LayoutViewBase *mp_view;
int m_cv_index;
db::pcell_parameters_type m_parameters;
bool m_dense;
tl::DeferredMethod<PCellParametersPage> dm_parameter_changed;
std::vector<tl::Variant> m_current_parameters;
std::vector<tl::Variant> m_current_parameters, m_initial_parameters;
db::ParameterStates m_states;
void init ();
void do_parameter_changed ();
bool lazy_evaluation ();
void set_parameters_internal (const std::vector<tl::Variant> &values, bool tentatively);
bool update_current_parameters ();
void update_widgets_from_states (const db::ParameterStates &states);
void get_parameters_internal (db::ParameterStates &states, bool &edit_error);
};
}