WIP: Enhancements to EditorOptionsPages API

- Make RBA::LayoutViewBase derived from Dispatcher, so we can pass LayoutView
  to methods asking for a dispatcher
- For this, the Dispatcher needs to be the first base class of LayoutViewBase
  and gsiDeclLayDispatcher is moved to laybasic
- API for editor options pages and message passing (callbacks)
This commit is contained in:
Matthias Koefferlein 2025-09-03 00:02:18 +02:00
parent 0447080d17
commit b9115fc0a2
15 changed files with 139 additions and 38 deletions

View File

@ -59,19 +59,16 @@ Class<lay::EditorOptionsPage> decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWid
) + ) +
method ("show", &lay::EditorOptionsPage::show, method ("show", &lay::EditorOptionsPage::show,
"@brief Shows the page\n" "@brief Shows the page\n"
"@return A value indicating whether the page was opened non-modal (-1), accepted (1) or rejected (0)\n"
"Provided the page is selected because the plugin is active, this method will " "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 " "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." "dock and bring it to the front if it is non-modal."
) + ) +
method ("do_apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"), method ("apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"),
"@brief Transfers data from the page to the configuration\n" "@brief Transfers data from the page to the configuration\n"
"Calling this method will call the actual 'apply' implementation which is "
"provided by a reimplementation - either on C++ or script side."
) + ) +
method ("do_setup", &lay::EditorOptionsPage::setup, gsi::arg ("dispatcher"), method ("setup", &lay::EditorOptionsPage::setup, gsi::arg ("dispatcher"),
"@brief Transfers data from the configuration to the page\n" "@brief Transfers data from the configuration to the page\n"
"Calling this method will call the actual 'setup' implementation which is "
"provided by a reimplementation - either on C++ or script side."
), ),
"@brief The plugin framework's editor options page base class\n" "@brief The plugin framework's editor options page base class\n"
"\n" "\n"
@ -100,6 +97,16 @@ EditorOptionsPageImpl::call_edited ()
lay::EditorOptionsPage::edited (); lay::EditorOptionsPage::edited ();
} }
static void apply_fb (EditorOptionsPageImpl *ep, lay::Dispatcher *root)
{
ep->lay::EditorOptionsPage::apply (root);
}
static void setup_fb (EditorOptionsPageImpl *ep, lay::Dispatcher *root)
{
ep->lay::EditorOptionsPage::setup (root);
}
void void
EditorOptionsPageImpl::apply_impl (lay::Dispatcher *root) EditorOptionsPageImpl::apply_impl (lay::Dispatcher *root)
{ {
@ -148,11 +155,15 @@ Class<EditorOptionsPageImpl> decl_EditorOptionsPage (decl_EditorOptionsPageBase,
"When some entry widget (for example 'editingFinished' slot of a QLineEdit), " "When some entry widget (for example 'editingFinished' slot of a QLineEdit), "
"call this method to initiate a transfer of information from the page to the plugin.\n" "call this method to initiate a transfer of information from the page to the plugin.\n"
) + ) +
// prevents infinite recursion
method_ext ("apply", &apply_fb, gsi::arg ("dispatcher"), "@hide") +
callback ("apply", &EditorOptionsPageImpl::apply, &EditorOptionsPageImpl::f_apply, gsi::arg ("dispatcher"), callback ("apply", &EditorOptionsPageImpl::apply, &EditorOptionsPageImpl::f_apply, gsi::arg ("dispatcher"),
"@brief Reimplement this method to transfer data from the page to the configuration\n" "@brief Reimplement this method to transfer data from the page to the configuration\n"
"In this method, you should transfer all widget data into corresponding configuration updates.\n" "In this method, you should transfer all widget data into corresponding configuration updates.\n"
"Use \\Dispatcher#set_config on the dispatcher object ('dispatcher' argument) to set a configuration parameter.\n" "Use \\Dispatcher#set_config on the dispatcher object ('dispatcher' argument) to set a configuration parameter.\n"
) + ) +
// prevents infinite recursion
method_ext ("setup", &setup_fb, gsi::arg ("dispatcher"), "@hide") +
callback ("setup", &EditorOptionsPageImpl::setup, &EditorOptionsPageImpl::f_setup, gsi::arg ("dispatcher"), callback ("setup", &EditorOptionsPageImpl::setup, &EditorOptionsPageImpl::f_setup, gsi::arg ("dispatcher"),
"@brief Reimplement this method to transfer data from the configuration to the page\n" "@brief Reimplement this method to transfer data from the configuration to the page\n"
"In this method, you should transfer all configuration data to the widgets.\n" "In this method, you should transfer all configuration data to the widgets.\n"

View File

@ -51,9 +51,7 @@ public:
} }
void call_edited (); void call_edited ();
void apply_impl (lay::Dispatcher *root);
virtual void apply (lay::Dispatcher *root); virtual void apply (lay::Dispatcher *root);
void setup_impl (lay::Dispatcher *root);
virtual void setup (lay::Dispatcher *root); virtual void setup (lay::Dispatcher *root);
gsi::Callback f_apply; gsi::Callback f_apply;
@ -64,6 +62,9 @@ private:
tl::weak_ptr<lay::Dispatcher> mp_dispatcher; tl::weak_ptr<lay::Dispatcher> mp_dispatcher;
std::string m_title; std::string m_title;
int m_index; int m_index;
void apply_impl (lay::Dispatcher *root);
void setup_impl (lay::Dispatcher *root);
}; };
} }

View File

@ -646,6 +646,15 @@ PluginImpl::tracking_position () const
} }
} }
int PluginImpl::focus_page_open(lay::EditorOptionsPage *fp)
{
if (f_focus_page_open.can_issue ()) {
return f_focus_page_open.issue<lay::EditorServiceBase, int, lay::EditorOptionsPage *> (&lay::EditorServiceBase::focus_page_open, fp);
} else {
return lay::EditorServiceBase::focus_page_open (fp);
}
}
lay::angle_constraint_type lay::angle_constraint_type
PluginImpl::connect_ac (lay::angle_constraint_type ac) const PluginImpl::connect_ac (lay::angle_constraint_type ac) const
{ {
@ -970,6 +979,22 @@ Class<gsi::PluginImpl> decl_Plugin (decl_PluginBase, "lay", "Plugin",
"\n" "\n"
"This method has been added in version 0.30.4." "This method has been added in version 0.30.4."
) + ) +
gsi::method ("focus_page", &gsi::PluginImpl::focus_page,
"@brief Gets the (first) focus page\n"
"Focus pages are editor options pages that have a true value for \\EditorOptionsPage#is_focus_page.\n"
"The pages can be navigated to quickly or can be shown in a modal dialog from the editor function.\n"
"This method returns the first focus page present in the editor options pages stack.\n"
"\n"
"This method has been added in version 0.30.4."
) +
callback ("focus_page_open", &gsi::PluginImpl::focus_page_open, &gsi::PluginImpl::f_focus_page_open, gsi::arg ("focus_page"),
"@brief Gets called when the focus page wants to be opened - i.e. if 'Tab' is pressed during editing\n"
"The default implementation calls \\EditorOptionsPage#show. This method can be overloaded to provide certain actions before "
"or after the page is shown, specifically if the page is a modal one. For example, it can update the page with current "
"dimensions of a shape that is created and after committing the page, adjust the shape accordingly.\n"
"\n"
"This method has been added in version 0.30.4."
) +
#endif #endif
gsi::method ("view", &gsi::PluginImpl::view, gsi::method ("view", &gsi::PluginImpl::view,
"@brief Gets the view object the plugin is associated with\n" "@brief Gets the view object the plugin is associated with\n"

View File

@ -95,6 +95,8 @@ public:
db::DPoint tracking_position_test () const; db::DPoint tracking_position_test () const;
virtual db::DPoint tracking_position () const; virtual db::DPoint tracking_position () const;
virtual int focus_page_open (lay::EditorOptionsPage *fp);
virtual lay::ViewService *view_service_interface () virtual lay::ViewService *view_service_interface ()
{ {
return this; return this;
@ -128,6 +130,7 @@ public:
gsi::Callback f_update; gsi::Callback f_update;
gsi::Callback f_has_tracking_position; gsi::Callback f_has_tracking_position;
gsi::Callback f_tracking_position; gsi::Callback f_tracking_position;
gsi::Callback f_focus_page_open;
private: private:
tl::weak_ptr<lay::LayoutViewBase> mp_view; tl::weak_ptr<lay::LayoutViewBase> mp_view;

View File

@ -121,7 +121,6 @@ FORMS = \
SOURCES = \ SOURCES = \
gsiDeclLayApplication.cc \ gsiDeclLayApplication.cc \
gsiDeclLayConfigPage.cc \ gsiDeclLayConfigPage.cc \
gsiDeclLayDispatcher.cc \
gsiDeclLayEditorOptionsPage.cc \ gsiDeclLayEditorOptionsPage.cc \
gsiDeclLayHelpDialog.cc \ gsiDeclLayHelpDialog.cc \
gsiDeclLayMainWindow.cc \ gsiDeclLayMainWindow.cc \

View File

@ -497,7 +497,9 @@ static bool view_is_dirty (lay::LayoutViewBase *view)
return view->is_dirty (); return view->is_dirty ();
} }
LAYBASIC_PUBLIC Class<lay::LayoutViewBase> decl_LayoutViewBase ("lay", "LayoutViewBase", extern Class<lay::Dispatcher> decl_Dispatcher;
LAYBASIC_PUBLIC Class<lay::LayoutViewBase> decl_LayoutViewBase (decl_Dispatcher, "lay", "LayoutViewBase",
gsi::constant ("LV_NoLayers", (unsigned int) lay::LayoutViewBase::LV_NoLayers, gsi::constant ("LV_NoLayers", (unsigned int) lay::LayoutViewBase::LV_NoLayers,
"@brief With this option, no layers view will be provided (see \\layer_control_frame)\n" "@brief With this option, no layers view will be provided (see \\layer_control_frame)\n"
"Use this value with the constructor's 'options' argument.\n" "Use this value with the constructor's 'options' argument.\n"

View File

@ -26,6 +26,7 @@
#include "layEditorOptionsPage.h" #include "layEditorOptionsPage.h"
#include "layEditorOptionsPages.h" #include "layEditorOptionsPages.h"
#include "layLayoutViewBase.h" #include "layLayoutViewBase.h"
#include "tlExceptions.h"
#include <QApplication> #include <QApplication>
#include <QKeyEvent> #include <QKeyEvent>
@ -92,20 +93,19 @@ EditorOptionsPage::focusNextPrevChild (bool next)
void void
EditorOptionsPage::keyPressEvent (QKeyEvent *event) EditorOptionsPage::keyPressEvent (QKeyEvent *event)
{ {
BEGIN_PROTECTED
if (! is_modal_page () && event->modifiers () == Qt::NoModifier && event->key () == Qt::Key_Return) { 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 // The Return key on a non-modal page commits the values and gives back the focus
// to the view // to the view
edited (); apply (dispatcher ());
if (view ()->canvas ()->widget ()) { if (view ()->canvas ()->widget ()) {
view ()->canvas ()->widget ()->setFocus (Qt::TabFocusReason); view ()->canvas ()->widget ()->setFocus (Qt::TabFocusReason);
} }
event->accept (); event->accept ();
} else { } else {
QWidget::keyPressEvent (event); QWidget::keyPressEvent (event);
} }
END_PROTECTED
} }
void void
@ -115,14 +115,15 @@ EditorOptionsPage::set_focus ()
QWidget::focusNextPrevChild (true); QWidget::focusNextPrevChild (true);
} }
void int
EditorOptionsPage::show () EditorOptionsPage::show ()
{ {
if (mp_owner && m_active) { if (mp_owner && m_active) {
if (! is_modal_page ()) { if (! is_modal_page ()) {
mp_owner->make_page_current (this); mp_owner->make_page_current (this);
return -1;
} else { } else {
mp_owner->exec_modal (this); return mp_owner->exec_modal (this) ? 1 : 0;
} }
} }
} }

View File

@ -77,7 +77,11 @@ public:
void activate (bool active); void activate (bool active);
void set_owner (EditorOptionsPages *owner); void set_owner (EditorOptionsPages *owner);
void show (); /**
* @brief Shows the editor page
* @return -1, if the page is shown non-modal, otherwise 1 or 0 if the dialog was accepted (1) or rejected (0)
*/
int show ();
const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; } const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; }
void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; } void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; }

View File

@ -122,6 +122,7 @@ EditorOptionsPages::exec_modal (EditorOptionsPage *page)
// found the page - make it current and show the dialog // found the page - make it current and show the dialog
mp_modal_pages->set_current_index (i); mp_modal_pages->set_current_index (i);
page->setup (mp_dispatcher);
page->set_focus (); page->set_focus ();
return mp_modal_pages->exec () != 0; return mp_modal_pages->exec () != 0;
@ -165,6 +166,7 @@ EditorOptionsPages::make_page_current (lay::EditorOptionsPage *page)
for (int i = 0; i < mp_pages->count (); ++i) { for (int i = 0; i < mp_pages->count (); ++i) {
if (mp_pages->widget (i) == page) { if (mp_pages->widget (i) == page) {
mp_pages->setCurrentIndex (i); mp_pages->setCurrentIndex (i);
page->setup (mp_dispatcher);
page->set_focus (); page->set_focus ();
break; break;
} }
@ -256,16 +258,17 @@ BEGIN_PROTECTED
// make the display consistent with the status (this is important for // make the display consistent with the status (this is important for
// PCell parameters where the PCell may be asked to modify the parameters) // PCell parameters where the PCell may be asked to modify the parameters)
do_apply (); do_apply (false);
do_apply (true);
END_PROTECTED_W (this) END_PROTECTED_W (this)
} }
void void
EditorOptionsPages::do_apply () EditorOptionsPages::do_apply (bool modal)
{ {
for (std::vector <lay::EditorOptionsPage *>::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { for (std::vector <lay::EditorOptionsPage *>::iterator p = m_pages.begin (); p != m_pages.end (); ++p) {
if ((*p)->active ()) { if ((*p)->active () && modal == (*p)->is_modal_page ()) {
// NOTE: we apply to the root dispatcher, so other dispatchers (views) get informed too. // NOTE: we apply to the root dispatcher, so other dispatchers (views) get informed too.
(*p)->apply (mp_dispatcher->dispatcher ()); (*p)->apply (mp_dispatcher->dispatcher ());
} }
@ -276,7 +279,7 @@ void
EditorOptionsPages::apply () EditorOptionsPages::apply ()
{ {
BEGIN_PROTECTED BEGIN_PROTECTED
do_apply (); do_apply (false);
END_PROTECTED_W (this) END_PROTECTED_W (this)
} }
@ -417,8 +420,10 @@ EditorOptionsModalPages::widget (int index)
void void
EditorOptionsModalPages::accept () EditorOptionsModalPages::accept ()
{ {
mp_parent->apply (); BEGIN_PROTECTED
mp_parent->do_apply (true);
QDialog::accept (); QDialog::accept ();
END_PROTECTED
} }
void void
@ -430,9 +435,11 @@ EditorOptionsModalPages::reject ()
void void
EditorOptionsModalPages::clicked (QAbstractButton *button) EditorOptionsModalPages::clicked (QAbstractButton *button)
{ {
BEGIN_PROTECTED
if (button == mp_button_box->button (QDialogButtonBox::Apply)) { if (button == mp_button_box->button (QDialogButtonBox::Apply)) {
mp_parent->apply (); mp_parent->do_apply (true);
} }
END_PROTECTED
} }
} }

View File

@ -73,6 +73,7 @@ public:
bool has_content () const; bool has_content () const;
bool has_modal_content () const; bool has_modal_content () const;
void do_apply (bool modal);
public slots: public slots:
void apply (); void apply ();
@ -85,7 +86,6 @@ private:
EditorOptionsModalPages *mp_modal_pages; EditorOptionsModalPages *mp_modal_pages;
void update (lay::EditorOptionsPage *page); void update (lay::EditorOptionsPage *page);
void do_apply ();
}; };
/** /**

View File

@ -344,6 +344,7 @@ EditorServiceBase::activated ()
} }
#if defined(HAVE_QT) #if defined(HAVE_QT)
std::vector<lay::EditorOptionsPage *> std::vector<lay::EditorOptionsPage *>
EditorServiceBase::editor_options_pages () EditorServiceBase::editor_options_pages ()
{ {
@ -360,23 +361,52 @@ EditorServiceBase::editor_options_pages ()
return pages; return pages;
} }
} }
#endif
lay::EditorOptionsPage *
EditorServiceBase::focus_page ()
{
auto pages = editor_options_pages ();
for (auto p = pages.begin (); p != pages.end (); ++p) {
if ((*p)->is_focus_page ()) {
return *p;
}
}
return 0;
}
bool bool
EditorServiceBase::key_event (unsigned int key, unsigned int buttons) EditorServiceBase::key_event (unsigned int key, unsigned int buttons)
{ {
#if defined(HAVE_QT)
if (is_active () && key == Qt::Key_Tab && buttons == 0) { if (is_active () && key == Qt::Key_Tab && buttons == 0) {
auto pages = editor_options_pages (); EditorOptionsPage *fp = focus_page ();
for (auto p = pages.begin (); p != pages.end (); ++p) { if (fp) {
if ((*p)->is_focus_page ()) { focus_page_open (fp);
(*p)->show ();
return true;
}
} }
return true;
} else {
return false;
} }
#endif }
int
EditorServiceBase::focus_page_open (EditorOptionsPage *fp)
{
return fp->show ();
}
void
EditorServiceBase::show_error (tl::Exception &ex)
{
tl::error << ex.msg ();
QMessageBox::critical (ui ()->widget (), tr ("Error"), tl::to_qstring (ex.msg ()));
}
#else
bool
EditorServiceBase::key_event (unsigned int key, unsigned int buttons)
{
return false; return false;
} }
@ -384,9 +414,14 @@ void
EditorServiceBase::show_error (tl::Exception &ex) EditorServiceBase::show_error (tl::Exception &ex)
{ {
tl::error << ex.msg (); tl::error << ex.msg ();
#if defined(HAVE_QT)
QMessageBox::critical (ui ()->widget (), tr ("Error"), tl::to_qstring (ex.msg ()));
#endif
} }
void
EditorServiceBase::focus_page_enter ()
{
// .. nothing yet ..
}
#endif
} }

View File

@ -270,6 +270,18 @@ public:
* @brief Gets the editor options pages associated with this plugin * @brief Gets the editor options pages associated with this plugin
*/ */
std::vector<lay::EditorOptionsPage *> editor_options_pages (); std::vector<lay::EditorOptionsPage *> editor_options_pages ();
/**
* @brief Gets the focus page or 0 if there is none
*/
lay::EditorOptionsPage *focus_page ();
/**
* @brief Gets called when the focus page opens
*
* The default implementation will call fp->show() and return its return value.
*/
virtual int focus_page_open (EditorOptionsPage *fp);
#endif #endif
private: private:

View File

@ -157,8 +157,8 @@ struct LAYBASIC_PUBLIC LayerDisplayProperties
* It manages the layer display list, bookmark list etc. * It manages the layer display list, bookmark list etc.
*/ */
class LAYBASIC_PUBLIC LayoutViewBase : class LAYBASIC_PUBLIC LayoutViewBase :
public lay::Editables, public lay::Dispatcher, // needs to be first as it is the GSI base class
public lay::Dispatcher public lay::Editables
{ {
public: public:
typedef lay::CellView::unspecific_cell_path_type cell_path_type; typedef lay::CellView::unspecific_cell_path_type cell_path_type;

View File

@ -28,6 +28,7 @@ DEFINES += MAKE_LAYBASIC_LIBRARY
SOURCES += \ SOURCES += \
gsiDeclLayLayers.cc \ gsiDeclLayLayers.cc \
gsiDeclLayDispatcher.cc \
gsiDeclLayLayoutViewBase.cc \ gsiDeclLayLayoutViewBase.cc \
gsiDeclLayMarker.cc \ gsiDeclLayMarker.cc \
gsiDeclLayMenu.cc \ gsiDeclLayMenu.cc \