WIP: experimental - modal editor options pages

This commit is contained in:
Matthias Koefferlein 2025-09-02 00:45:42 +02:00
parent 53a7414757
commit 2b04ecb1f7
6 changed files with 272 additions and 39 deletions

View File

@ -48,10 +48,20 @@ Class<lay::EditorOptionsPage> decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWid
"@brief Sets a flag indicating whether the page is a focus page\n"
"The focus page is the page that is selected when the tab key is pressed during some plugin action.\n"
) +
method ("make_current", &lay::EditorOptionsPage::make_current,
"@brief Brings the page to the front of the tab stack\n"
"Calling this method will make this page the current one in the tab stack, provided "
"the page is visible."
method ("is_modal_page?", &lay::EditorOptionsPage::is_modal_page,
"@brief Gets a flag indicating whether the page is a modal page\n"
"See \\modal_page= for a description is this attribute.\n"
) +
method ("modal_page=", &lay::EditorOptionsPage::set_modal_page, gsi::arg ("flag"),
"@brief Sets a flag indicating whether the page is a modal page\n"
"A modal page is shown in a modal dialog upon \\show. Non-modal pages are shown in the "
"editor options dock.\n"
) +
method ("show", &lay::EditorOptionsPage::show,
"@brief Shows the page\n"
"Provided the page is selected because the plugin is active, this method will "
"open a dialog to show the page if it is modal, or locate the page in the editor options "
"dock and bring it to the front if it is non-modal."
) +
method ("do_apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"),
"@brief Transfers data from the page to the configuration\n"
@ -72,6 +82,9 @@ Class<lay::EditorOptionsPage> decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWid
"It features some useful methods such as 'view' and provides a slot to call for triggering a data "
"transfer ('edited').\n"
"\n"
"Note that even though the page class is derived from QWidget, you can call QWidget methods "
"but not overload virtual methods from QWidget.\n"
"\n"
"This class has been introduced in version 0.30.4.\n"
);

View File

@ -27,6 +27,9 @@
#include "layEditorOptionsPages.h"
#include "layLayoutViewBase.h"
#include <QApplication>
#include <QKeyEvent>
namespace lay
{
@ -34,15 +37,20 @@ namespace lay
// EditorOptionsPage implementation
EditorOptionsPage::EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher)
: QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view)
: QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), m_modal_page (false), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view)
{
attach_events ();
}
EditorOptionsPage::EditorOptionsPage ()
: QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0)
: QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), m_modal_page (false), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0)
{
// .. nothing here -> call init to set view and dispatcher
// .. nothing yet ..
}
EditorOptionsPage::~EditorOptionsPage ()
{
set_owner (0);
}
void
@ -53,16 +61,69 @@ EditorOptionsPage::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher)
attach_events ();
}
EditorOptionsPage::~EditorOptionsPage ()
void
EditorOptionsPage::edited ()
{
set_owner (0);
apply (dispatcher ());
}
static bool is_parent_widget (QWidget *w, QWidget *parent)
{
while (w && w != parent) {
w = dynamic_cast<QWidget *> (w->parent ());
}
return w == parent;
}
bool
EditorOptionsPage::focusNextPrevChild (bool next)
{
bool res = QWidget::focusNextPrevChild (next);
// Stop making the focus leave the page - this way we can jump back to the
// view on "enter"
if (res && ! is_modal_page () && ! is_parent_widget (QApplication::focusWidget (), this) && focusWidget ()) {
focusWidget ()->setFocus ();
}
return res;
}
void
EditorOptionsPage::make_current ()
EditorOptionsPage::keyPressEvent (QKeyEvent *event)
{
if (! is_modal_page () && event->modifiers () == Qt::NoModifier && event->key () == Qt::Key_Return) {
// The Return key on a non-modal page commits the values and gives back the focus
// to the view
edited ();
if (view ()->canvas ()->widget ()) {
view ()->canvas ()->widget ()->setFocus (Qt::TabFocusReason);
}
event->accept ();
} else {
QWidget::keyPressEvent (event);
}
}
void
EditorOptionsPage::set_focus ()
{
setFocus (Qt::TabFocusReason);
QWidget::focusNextPrevChild (true);
}
void
EditorOptionsPage::show ()
{
if (mp_owner && m_active) {
mp_owner->make_page_current (this);
if (! is_modal_page ()) {
mp_owner->make_page_current (this);
} else {
mp_owner->exec_modal (this);
}
}
}

View File

@ -68,11 +68,16 @@ public:
bool is_focus_page () const { return m_focus_page; }
void set_focus_page (bool f) { m_focus_page = f; }
void set_focus ();
bool is_modal_page () const { return m_modal_page; }
void set_modal_page (bool f) { m_modal_page = f; }
bool active () const { return m_active; }
void activate (bool active);
void set_owner (EditorOptionsPages *owner);
void make_current ();
void show ();
const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; }
void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; }
@ -90,19 +95,20 @@ public:
}
protected slots:
void edited ()
{
apply (dispatcher ());
}
void edited ();
protected:
virtual void active_cellview_changed () { }
virtual void technology_changed (const std::string & /*tech*/) { }
virtual bool focusNextPrevChild (bool next);
virtual void keyPressEvent (QKeyEvent *event);
private:
EditorOptionsPages *mp_owner;
bool m_active;
bool m_focus_page;
bool m_modal_page;
const lay::PluginDeclaration *mp_plugin_declaration;
lay::Dispatcher *mp_dispatcher;
lay::LayoutViewBase *mp_view;

View File

@ -34,6 +34,8 @@
#include <QToolButton>
#include <QCompleter>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QPushButton>
namespace lay
{
@ -52,6 +54,8 @@ struct EOPCompareOp
EditorOptionsPages::EditorOptionsPages (QWidget *parent, const std::vector<lay::EditorOptionsPage *> &pages, lay::Dispatcher *dispatcher)
: QFrame (parent), mp_dispatcher (dispatcher)
{
mp_modal_pages = new EditorOptionsModalPages (this);
QVBoxLayout *ly1 = new QVBoxLayout (this);
ly1->setContentsMargins (0, 0, 0, 0);
@ -73,6 +77,9 @@ EditorOptionsPages::~EditorOptionsPages ()
while (m_pages.size () > 0) {
delete m_pages.front ();
}
delete mp_modal_pages;
mp_modal_pages = 0;
}
void
@ -84,17 +91,51 @@ EditorOptionsPages::focusInEvent (QFocusEvent * /*event*/)
}
}
bool EditorOptionsPages::has_content () const
bool
EditorOptionsPages::has_content () const
{
for (std::vector <lay::EditorOptionsPage *>::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) {
if ((*p)->active ()) {
if ((*p)->active () && ! (*p)->is_modal_page ()) {
return true;
}
}
return false;
}
void EditorOptionsPages::activate (const lay::Plugin *plugin)
bool
EditorOptionsPages::has_modal_content () const
{
for (std::vector <lay::EditorOptionsPage *>::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) {
if ((*p)->active () && (*p)->is_modal_page ()) {
return true;
}
}
return false;
}
bool
EditorOptionsPages::exec_modal (EditorOptionsPage *page)
{
QTabWidget *modal_pages = mp_modal_pages->pages_widget ();
for (int i = 0; i < modal_pages->count (); ++i) {
if (modal_pages->widget (i) == page) {
// found the page - make it current and show the dialog
modal_pages->setCurrentIndex (i);
page->set_focus ();
return mp_modal_pages->exec () != 0;
}
}
return false;
}
void
EditorOptionsPages::activate (const lay::Plugin *plugin)
{
for (auto op = m_pages.begin (); op != m_pages.end (); ++op) {
bool is_active = false;
@ -126,6 +167,7 @@ EditorOptionsPages::make_page_current (lay::EditorOptionsPage *page)
for (int i = 0; i < mp_pages->count (); ++i) {
if (mp_pages->widget (i) == page) {
mp_pages->setCurrentIndex (i);
page->set_focus ();
break;
}
}
@ -151,6 +193,8 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page)
std::vector <lay::EditorOptionsPage *> sorted_pages = m_pages;
std::sort (sorted_pages.begin (), sorted_pages.end (), EOPCompareOp ());
QTabWidget *modal_pages = mp_modal_pages->pages_widget ();
if (! page && m_pages.size () > 0) {
page = m_pages.back ();
}
@ -158,17 +202,39 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page)
while (mp_pages->count () > 0) {
mp_pages->removeTab (0);
}
while (modal_pages->count () > 0) {
modal_pages->removeTab (0);
}
int index = -1;
int modal_index = -1;
for (std::vector <lay::EditorOptionsPage *>::iterator p = sorted_pages.begin (); p != sorted_pages.end (); ++p) {
if ((*p)->active ()) {
if ((*p) == page) {
index = mp_pages->count ();
if (! (*p)->is_modal_page ()) {
if ((*p) == page) {
index = mp_pages->count ();
}
mp_pages->addTab (*p, tl::to_qstring ((*p)->title ()));
} else {
if ((*p) == page) {
modal_index = modal_pages->count ();
}
modal_pages->addTab (*p, tl::to_qstring ((*p)->title ()));
}
mp_pages->addTab (*p, tl::to_qstring ((*p)->title ()));
} else {
(*p)->setParent (0);
}
}
lay::EditorOptionsPage *single_modal_page = modal_pages->count () == 1 ? dynamic_cast<lay::EditorOptionsPage *> (modal_pages->widget (0)) : 0;
if (single_modal_page) {
mp_modal_pages->setWindowTitle (tl::to_qstring (single_modal_page->title ()));
} else {
mp_modal_pages->setWindowTitle (tr ("Editor Options"));
}
if (index < 0) {
index = mp_pages->currentIndex ();
}
@ -177,27 +243,33 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page)
}
mp_pages->setCurrentIndex (index);
if (modal_index < 0) {
modal_index = modal_pages->currentIndex ();
}
if (modal_index >= int (modal_pages->count ())) {
modal_index = modal_pages->count () - 1;
}
modal_pages->setCurrentIndex (modal_index);
setVisible (mp_pages->count () > 0);
}
void
EditorOptionsPages::setup ()
{
try {
BEGIN_PROTECTED
for (std::vector <lay::EditorOptionsPage *>::iterator p = m_pages.begin (); p != m_pages.end (); ++p) {
if ((*p)->active ()) {
(*p)->setup (mp_dispatcher);
}
for (std::vector <lay::EditorOptionsPage *>::iterator p = m_pages.begin (); p != m_pages.end (); ++p) {
if ((*p)->active ()) {
(*p)->setup (mp_dispatcher);
}
// make the display consistent with the status (this is important for
// PCell parameters where the PCell may be asked to modify the parameters)
do_apply ();
} catch (...) {
// catch any errors related to configuration file errors etc.
}
// make the display consistent with the status (this is important for
// PCell parameters where the PCell may be asked to modify the parameters)
do_apply ();
END_PROTECTED_W (this)
}
void
@ -219,6 +291,54 @@ BEGIN_PROTECTED
END_PROTECTED_W (this)
}
// ------------------------------------------------------------------
// EditorOptionsModalPages implementation
EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent)
: QDialog (parent), mp_parent (parent)
{
QVBoxLayout *ly = new QVBoxLayout (this);
mp_pages = new QTabWidget (this);
ly->addWidget (mp_pages, 1);
mp_pages->setTabBarAutoHide (true);
mp_button_box = new QDialogButtonBox (this);
ly->addWidget (mp_button_box);
mp_button_box->setOrientation (Qt::Horizontal);
mp_button_box->setStandardButtons (QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Ok);
connect (mp_button_box, SIGNAL (clicked(QAbstractButton *)), this, SLOT (clicked(QAbstractButton *)));
connect (mp_button_box, SIGNAL (accepted()), this, SLOT (accept()));
connect (mp_button_box, SIGNAL (rejected()), this, SLOT (reject()));
}
EditorOptionsModalPages::~EditorOptionsModalPages ()
{
// .. nothing yet ..
}
void
EditorOptionsModalPages::accept ()
{
mp_parent->apply ();
QDialog::accept ();
}
void
EditorOptionsModalPages::reject ()
{
QDialog::reject ();
}
void
EditorOptionsModalPages::clicked (QAbstractButton *button)
{
if (button == mp_button_box->button (QDialogButtonBox::Apply)) {
mp_parent->apply ();
}
}
}
#endif

View File

@ -28,14 +28,16 @@
#include "laybasicCommon.h"
#include "layEditorOptionsPage.h"
#include <tlVariant.h>
#include <QFrame>
#include <QDialog>
#include <vector>
#include <string>
class QTabWidget;
class QLabel;
class QDialogButtonBox;
class QAbstractButton;
namespace lay
{
@ -43,9 +45,10 @@ namespace lay
class PluginDeclaration;
class Dispatcher;
class Plugin;
class EditorOptionsModalPages;
/**
* @brief The object properties dialog
* @brief The object properties tab widget
*/
class LAYBASIC_PUBLIC EditorOptionsPages
: public QFrame
@ -61,6 +64,7 @@ public:
void activate (const lay::Plugin *plugin);
void focusInEvent (QFocusEvent *event);
void make_page_current (lay::EditorOptionsPage *page);
bool exec_modal (lay::EditorOptionsPage *page);
const std::vector <lay::EditorOptionsPage *> &pages () const
{
@ -68,6 +72,7 @@ public:
}
bool has_content () const;
bool has_modal_content () const;
public slots:
void apply ();
@ -77,11 +82,40 @@ private:
std::vector <lay::EditorOptionsPage *> m_pages;
lay::Dispatcher *mp_dispatcher;
QTabWidget *mp_pages;
EditorOptionsModalPages *mp_modal_pages;
void update (lay::EditorOptionsPage *page);
void do_apply ();
};
/**
* @brief The object properties modal page dialog
*/
class LAYBASIC_PUBLIC EditorOptionsModalPages
: public QDialog
{
Q_OBJECT
public:
EditorOptionsModalPages (EditorOptionsPages *parent);
~EditorOptionsModalPages ();
QTabWidget *pages_widget ()
{
return mp_pages;
}
private slots:
void accept ();
void reject ();
void clicked (QAbstractButton *button);
private:
EditorOptionsPages *mp_parent;
QTabWidget *mp_pages;
QDialogButtonBox *mp_button_box;
};
}
#endif

View File

@ -367,8 +367,7 @@ EditorServiceBase::key_event (unsigned int key, unsigned int buttons)
auto pages = editor_options_pages ();
for (auto p = pages.begin (); p != pages.end (); ++p) {
if ((*p)->is_focus_page ()) {
(*p)->make_current ();
(*p)->setFocus (Qt::TabFocusReason);
(*p)->show ();
return true;
}
}