From e6da3fc22c0ece2d57e0df01e25d1383420f7f78 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 27 Oct 2022 00:42:28 +0200 Subject: [PATCH] WIP: callbacks for PCells --- src/db/db/dbPCellDeclaration.h | 189 ++++++++++- src/db/db/gsiDeclDbLibrary.cc | 8 +- src/edt/edt/edtPCellParametersPage.cc | 438 +++++++++++++++++--------- src/edt/edt/edtPCellParametersPage.h | 11 +- 4 files changed, 498 insertions(+), 148 deletions(-) diff --git a/src/db/db/dbPCellDeclaration.h b/src/db/db/dbPCellDeclaration.h index 808a75fe2..aaecb02df 100644 --- a/src/db/db/dbPCellDeclaration.h +++ b/src/db/db/dbPCellDeclaration.h @@ -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 ¶meter (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 ¶meter (const std::string &name) const + { + return const_cast (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 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 diff --git a/src/db/db/gsiDeclDbLibrary.cc b/src/db/db/gsiDeclDbLibrary.cc index d6537599f..dd306f0cb 100644 --- a/src/db/db/gsiDeclDbLibrary.cc +++ b/src/db/db/gsiDeclDbLibrary.cc @@ -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 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" diff --git a/src/edt/edt/edtPCellParametersPage.cc b/src/edt/edt/edtPCellParametersPage.cc index ed28773cf..426c2becf 100644 --- a/src/edt/edt/edtPCellParametersPage.cc +++ b/src/edt/edt/edtPCellParametersPage.cc @@ -238,13 +238,15 @@ PCellParametersPage::setup (lay::LayoutViewBase *view, int cv_index, const db::P mp_pcell_decl.reset (const_cast (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 &pcp = pcell_decl->parameter_declarations (); for (std::vector::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 ()); + + 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 &pcp = mp_pcell_decl->parameter_declarations (); + for (std::vector::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 &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 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 &pcp = mp_pcell_decl->parameter_declarations (); + for (std::vector::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 (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 (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 (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 (m_widgets [r]); + if (le) { + std::vector 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 (m_widgets [r]); + if (ly) { + ps.set_value (tl::Variant (ly->current_layer_props ())); + } + } + break; + case db::PCellParameterDeclaration::t_boolean: + { + QCheckBox *cbx = dynamic_cast (m_widgets [r]); + if (cbx) { + ps.set_value (tl::Variant (cbx->isChecked ())); + } + } + break; + + default: + break; + } + + } else { + + QComboBox *cb = dynamic_cast (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 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 &pcp = mp_pcell_decl->parameter_declarations (); - for (std::vector::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 (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 (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 (m_widgets [r]); - if (le) { - parameters.back () = tl::Variant (tl::to_string (le->text ())); - } - } - break; - - case db::PCellParameterDeclaration::t_list: - { - QLineEdit *le = dynamic_cast (m_widgets [r]); - if (le) { - std::vector 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 (m_widgets [r]); - if (ly) { - parameters.back () = tl::Variant (ly->current_layer_props ()); - } - } - break; - case db::PCellParameterDeclaration::t_boolean: - { - QCheckBox *cbx = dynamic_cast (m_widgets [r]); - if (cbx) { - parameters.back () = tl::Variant (cbx->isChecked ()); - } - } - break; - - default: - break; - } - - } else { - - QComboBox *cb = dynamic_cast (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::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 ¶meters) { - m_parameters = parameters; + if (! mp_pcell_decl) { + return; + } + + size_t r = 0; + const std::vector &pcp = mp_pcell_decl->parameter_declarations (); + for (std::vector::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); } diff --git a/src/edt/edt/edtPCellParametersPage.h b/src/edt/edt/edtPCellParametersPage.h index 83eadfea1..ea21b1f94 100644 --- a/src/edt/edt/edtPCellParametersPage.h +++ b/src/edt/edt/edtPCellParametersPage.h @@ -103,12 +103,10 @@ public: /** * @brief Gets the initial parameters - * - * The initial parameters are the ones present on "setup". */ const std::vector &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 mp_pcell_decl; std::vector m_widgets; + std::vector > m_all_widgets; lay::LayoutViewBase *mp_view; int m_cv_index; - db::pcell_parameters_type m_parameters; bool m_dense; tl::DeferredMethod dm_parameter_changed; - std::vector m_current_parameters; + std::vector 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 &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); }; }