diff --git a/src/db/db/dbPCellDeclaration.h b/src/db/db/dbPCellDeclaration.h index acd4d12c9..c354b12a7 100644 --- a/src/db/db/dbPCellDeclaration.h +++ b/src/db/db/dbPCellDeclaration.h @@ -429,6 +429,17 @@ public: return db::Trans (); } + /** + * @brief Returns a value indicating that the PCell wants lazy evaluation + * + * In lazy evaluation mode, the PCell is not immediately updated when a parameter is changed in the UI, but only when it is requested + * to be updated. + */ + virtual bool wants_lazy_evaluation () const + { + return false; + } + /** * @brief Gets the Layout object the PCell is registered inside or NULL if it is not registered */ diff --git a/src/db/db/gsiDeclDbLibrary.cc b/src/db/db/gsiDeclDbLibrary.cc index 0d6570bef..fc2ea5ed6 100644 --- a/src/db/db/gsiDeclDbLibrary.cc +++ b/src/db/db/gsiDeclDbLibrary.cc @@ -276,6 +276,7 @@ Class decl_PCellDeclaration_Native ("db", "PCellDeclaratio gsi::method ("can_create_from_shape", &db::PCellDeclaration::can_create_from_shape) + gsi::method ("parameters_from_shape", &db::PCellDeclaration::parameters_from_shape) + gsi::method ("transformation_from_shape", &db::PCellDeclaration::transformation_from_shape) + + gsi::method ("wants_lazy_evaluation", &db::PCellDeclaration::wants_lazy_evaluation) + gsi::method ("display_text", &db::PCellDeclaration::get_display_name) + gsi::method ("layout", &db::PCellDeclaration::layout, "@brief Gets the Layout object the PCell is registered in or nil if it is not registered yet.\n" @@ -407,6 +408,20 @@ public: } } + bool wants_lazy_evaluation_fb () const + { + return db::PCellDeclaration::wants_lazy_evaluation (); + } + + virtual bool wants_lazy_evaluation () const + { + if (cb_wants_lazy_evaluation.can_issue ()) { + return cb_wants_lazy_evaluation.issue (&db::PCellDeclaration::wants_lazy_evaluation); + } else { + return db::PCellDeclaration::wants_lazy_evaluation (); + } + } + std::string get_display_name_fb (const db::pcell_parameters_type ¶meters) const { return db::PCellDeclaration::get_display_name (parameters); @@ -427,6 +442,7 @@ public: gsi::Callback cb_can_create_from_shape; gsi::Callback cb_parameters_from_shape; gsi::Callback cb_transformation_from_shape; + gsi::Callback cb_wants_lazy_evaluation; gsi::Callback cb_coerce_parameters; gsi::Callback cb_get_display_name; }; @@ -507,6 +523,16 @@ Class decl_PCellDeclaration (decl_PCellDeclaration_Native, "it will use this method to derive the transformation for the PCell instance that will replace the shape. " "See also \\parameters_from_shape and \\can_create_from_shape." ) + + gsi::callback ("wants_lazy_evaluation", &PCellDeclarationImpl::wants_lazy_evaluation, &PCellDeclarationImpl::cb_wants_lazy_evaluation, + "@brief Gets a value indicating whether the PCell wants lazy evaluation\n" + "In lazy evaluation mode, the PCell UI will not immediately update the layout when a parameter is changed. " + "Instead, the user has to commit the changes in order to have the parameters updated. This is " + "useful for PCells that take a long time to compute.\n" + "\n" + "The default implementation will return 'false' indicating immediate updates.\n" + "\n" + "This method has been added in version 0.27.6.\n" + ) + gsi::callback ("display_text", &PCellDeclarationImpl::get_display_name, &PCellDeclarationImpl::cb_get_display_name, gsi::arg ("parameters"), "@brief Returns the display text for this PCell given a certain parameter set\n" "Reimplement this method to create a distinct display text for a PCell variant with \n" diff --git a/src/edt/edt/edtPCellParametersPage.cc b/src/edt/edt/edtPCellParametersPage.cc index 4c15ca492..000b59068 100644 --- a/src/edt/edt/edtPCellParametersPage.cc +++ b/src/edt/edt/edtPCellParametersPage.cc @@ -99,6 +99,8 @@ static void set_value (const db::PCellParameterDeclaration &p, QWidget *widget, db::LayerProperties lp; if (value.is_user ()) { lp = value.to_user (); + } else if (value.is_nil ()) { + // empty LayerProperties } else { std::string s = value.to_string (); tl::Extractor ex (s.c_str ()); @@ -154,6 +156,9 @@ PCellParametersPage::PCellParametersPage (QWidget *parent, bool dense) void PCellParametersPage::init () { + QPalette palette; + QFont font; + mp_pcell_decl.reset (0); mp_view = 0; m_cv_index = 0; @@ -162,24 +167,69 @@ PCellParametersPage::init () QGridLayout *frame_layout = new QGridLayout (this); // spacing and margin for tool windows frame_layout->setMargin (0); + frame_layout->setVerticalSpacing (0); setLayout (frame_layout); - mp_error_icon = new QLabel (this); - mp_error_icon->setPixmap (QPixmap (":/warn.png")); - mp_error_icon->hide (); - frame_layout->addWidget (mp_error_icon, 1, 0, 1, 1); + mp_update_frame = new QFrame (); + mp_update_frame->setFrameShape (QFrame::NoFrame); + frame_layout->addWidget (mp_update_frame, 0, 0, 1, 1); - mp_error_label = new QLabel (this); + QGridLayout *update_frame_layout = new QGridLayout (mp_update_frame); + mp_update_frame->setLayout (update_frame_layout); + if (m_dense) { + update_frame_layout->setMargin (4); + update_frame_layout->setHorizontalSpacing (6); + update_frame_layout->setVerticalSpacing (2); + } + + mp_changed_icon = new QLabel (mp_update_frame); + mp_changed_icon->setPixmap (QPixmap (":/warn.png")); + update_frame_layout->addWidget (mp_changed_icon, 0, 0, 1, 1); + + mp_update_button = new QToolButton (mp_update_frame); + mp_update_button->setText (tr ("Update")); + connect (mp_update_button, SIGNAL (clicked()), this, SLOT (update_button_pressed ())); + update_frame_layout->addWidget (mp_update_button, 0, 1, 1, 1); + + mp_changed_label = new QLabel (mp_update_frame); + mp_changed_label->setText (tr ("Update needed")); + update_frame_layout->addWidget (mp_changed_label, 0, 2, 1, 1); + + update_frame_layout->setColumnStretch (2, 1); + + mp_error_frame = new QFrame (); + mp_error_frame->setFrameShape (QFrame::NoFrame); + frame_layout->addWidget (mp_error_frame, 1, 0, 1, 1); + + QGridLayout *error_frame_layout = new QGridLayout (mp_update_frame); + mp_error_frame->setLayout (error_frame_layout); + if (m_dense) { + error_frame_layout->setMargin (4); + error_frame_layout->setHorizontalSpacing (6); + error_frame_layout->setVerticalSpacing (2); + } + + mp_error_icon = new QLabel (mp_update_frame); + mp_error_icon->setPixmap (QPixmap (":/warn.png")); + error_frame_layout->addWidget (mp_error_icon, 1, 0, 1, 1); + + mp_error_label = new QLabel (mp_update_frame); mp_error_label->setWordWrap (true); - QPalette palette = mp_error_label->palette (); + palette = mp_error_label->palette (); palette.setColor (QPalette::Foreground, Qt::red); mp_error_label->setPalette (palette); - QFont font = mp_error_label->font (); + font = mp_error_label->font (); font.setBold (true); mp_error_label->setFont (font); - mp_error_label->hide (); - frame_layout->addWidget (mp_error_label, 1, 1, 1, 1); - frame_layout->setColumnStretch (1, 1); + error_frame_layout->addWidget (mp_error_label, 1, 1, 1, 2); + + error_frame_layout->setColumnStretch (2, 1); +} + +bool +PCellParametersPage::lazy_evaluation () +{ + return mp_pcell_decl.get () && mp_pcell_decl->wants_lazy_evaluation (); } void @@ -199,8 +249,8 @@ PCellParametersPage::setup (lay::LayoutView *view, int cv_index, const db::PCell mp_parameters_area = new QScrollArea (this); mp_parameters_area->setFrameShape (QFrame::NoFrame); QGridLayout *frame_layout = dynamic_cast (QFrame::layout ()); - frame_layout->addWidget (mp_parameters_area, 0, 0, 1, 2); - frame_layout->setRowStretch (0, 1); + frame_layout->addWidget (mp_parameters_area, 2, 0, 1, 1); + frame_layout->setRowStretch (2, 1); QFrame *fi = new QFrame (mp_parameters_area); QWidget *inner_frame = fi; @@ -396,9 +446,7 @@ PCellParametersPage::setup (lay::LayoutView *view, int cv_index, const db::PCell mp_parameters_area->setWidget (main_frame); main_frame->show (); - // does a first coerce and update. Ignore errors for now. - bool ok = false; - get_parameters (&ok); + update_current_parameters (); } PCellParametersPage::State @@ -446,12 +494,33 @@ PCellParametersPage::do_parameter_changed () { // does a coerce and update bool ok = false; - get_parameters (&ok); - if (ok) { + std::vector parameters = get_parameters (&ok); + if (ok && ! lazy_evaluation ()) { emit edited (); } } +void +PCellParametersPage::update_button_pressed () +{ + if (update_current_parameters ()) { + emit edited (); + } +} + +bool +PCellParametersPage::update_current_parameters () +{ + bool ok = false; + std::vector parameters = get_parameters (&ok); + if (ok) { + m_current_parameters = parameters; + mp_update_frame->hide (); + } + + return ok; +} + std::vector PCellParametersPage::get_parameters (bool *ok) { @@ -594,10 +663,7 @@ PCellParametersPage::get_parameters (bool *ok) if (mp_view->cellview (m_cv_index).is_valid ()) { mp_pcell_decl->coerce_parameters (mp_view->cellview (m_cv_index)->layout (), parameters); } - set_parameters (parameters); - - mp_error_label->hide (); - mp_error_icon->hide (); + set_parameters_internal (parameters, lazy_evaluation ()); if (ok) { *ok = true; @@ -608,8 +674,7 @@ PCellParametersPage::get_parameters (bool *ok) if (ok) { mp_error_label->setText (tl::to_qstring (ex.basic_msg ())); mp_error_label->setToolTip (tl::to_qstring (ex.msg ())); - mp_error_icon->show (); - mp_error_label->show (); + mp_error_frame->show (); *ok = false; } else { throw; @@ -619,8 +684,7 @@ PCellParametersPage::get_parameters (bool *ok) if (ok) { mp_error_label->setText (tl::to_qstring (ex.msg ())); - mp_error_icon->show (); - mp_error_label->show (); + mp_error_frame->show (); *ok = false; } else { throw; @@ -631,8 +695,14 @@ PCellParametersPage::get_parameters (bool *ok) return parameters; } -void +void PCellParametersPage::set_parameters (const std::vector ¶meters) +{ + set_parameters_internal (parameters, false); +} + +void +PCellParametersPage::set_parameters_internal (const std::vector ¶meters, bool tentatively) { if (! mp_pcell_decl) { return; @@ -646,6 +716,18 @@ PCellParametersPage::set_parameters (const std::vector ¶meters) set_value (*p, m_widgets [r], parameters [r]); } } + + mp_error_frame->hide (); + + bool update_needed = false; + + if (! tentatively) { + m_current_parameters = parameters; + } else { + update_needed = (m_current_parameters != parameters); + } + + mp_update_frame->setVisible (update_needed); } } diff --git a/src/edt/edt/edtPCellParametersPage.h b/src/edt/edt/edtPCellParametersPage.h index bbf144d64..0bfbea67b 100644 --- a/src/edt/edt/edtPCellParametersPage.h +++ b/src/edt/edt/edtPCellParametersPage.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace lay { @@ -117,11 +118,16 @@ signals: private slots: void parameter_changed (); + void update_button_pressed (); private: QScrollArea *mp_parameters_area; QLabel *mp_error_label; QLabel *mp_error_icon; + QLabel *mp_changed_label; + QLabel *mp_changed_icon; + QToolButton *mp_update_button; + QFrame *mp_error_frame, *mp_update_frame; tl::weak_ptr mp_pcell_decl; std::vector m_widgets; lay::LayoutView *mp_view; @@ -129,9 +135,13 @@ private: db::pcell_parameters_type m_parameters; bool m_dense; tl::DeferredMethod dm_parameter_changed; + std::vector m_current_parameters; void init (); void do_parameter_changed (); + bool lazy_evaluation (); + void set_parameters_internal (const std::vector &values, bool tentatively); + bool update_current_parameters (); }; } diff --git a/src/lay/lay/macro_templates/pcell.lym b/src/lay/lay/macro_templates/pcell.lym index e3665c365..31ed9240f 100644 --- a/src/lay/lay/macro_templates/pcell.lym +++ b/src/lay/lay/macro_templates/pcell.lym @@ -71,6 +71,13 @@ module PCellLibModule # TODO: return a RBA::Trans object for the initial transformation of # the instance # end + # + # optional: + # def wants_lazy_evaluation + # TODO: return "true" here if the PCell takes a long time to compute. + # In lazy mode, the user has to acknowledge parameter changes before + # they are executed. + # end end diff --git a/src/lay/lay/macro_templates/pcell_python.lym b/src/lay/lay/macro_templates/pcell_python.lym index d3f0b9f17..2d7e3c334 100644 --- a/src/lay/lay/macro_templates/pcell_python.lym +++ b/src/lay/lay/macro_templates/pcell_python.lym @@ -54,6 +54,12 @@ class PCell(pya.PCellDeclarationHelper): # def transformation_from_shape_impl(self): # TODO: return a RBA::Trans object for the initial transformation of # the instance + # + # optional: + # def wants_lazy_evaluation(self): + # TODO: return "True" here if the PCell takes a long time to compute. + # In lazy mode, the user has to acknowledge parameter changes before + # they are executed. # TODO: add more PCell classes ..