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,
"@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 "
"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"),
method ("apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"),
"@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"
"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"
"\n"
@ -100,6 +97,16 @@ EditorOptionsPageImpl::call_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
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), "
"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"),
"@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"
"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"),
"@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"

View File

@ -51,9 +51,7 @@ public:
}
void call_edited ();
void apply_impl (lay::Dispatcher *root);
virtual void apply (lay::Dispatcher *root);
void setup_impl (lay::Dispatcher *root);
virtual void setup (lay::Dispatcher *root);
gsi::Callback f_apply;
@ -64,6 +62,9 @@ private:
tl::weak_ptr<lay::Dispatcher> mp_dispatcher;
std::string m_title;
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
PluginImpl::connect_ac (lay::angle_constraint_type ac) const
{
@ -970,6 +979,22 @@ Class<gsi::PluginImpl> decl_Plugin (decl_PluginBase, "lay", "Plugin",
"\n"
"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
gsi::method ("view", &gsi::PluginImpl::view,
"@brief Gets the view object the plugin is associated with\n"

View File

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

View File

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

View File

@ -497,7 +497,9 @@ static bool view_is_dirty (lay::LayoutViewBase *view)
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,
"@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"

View File

@ -26,6 +26,7 @@
#include "layEditorOptionsPage.h"
#include "layEditorOptionsPages.h"
#include "layLayoutViewBase.h"
#include "tlExceptions.h"
#include <QApplication>
#include <QKeyEvent>
@ -92,20 +93,19 @@ EditorOptionsPage::focusNextPrevChild (bool next)
void
EditorOptionsPage::keyPressEvent (QKeyEvent *event)
{
BEGIN_PROTECTED
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 ();
apply (dispatcher ());
if (view ()->canvas ()->widget ()) {
view ()->canvas ()->widget ()->setFocus (Qt::TabFocusReason);
}
event->accept ();
} else {
QWidget::keyPressEvent (event);
}
END_PROTECTED
}
void
@ -115,14 +115,15 @@ EditorOptionsPage::set_focus ()
QWidget::focusNextPrevChild (true);
}
void
int
EditorOptionsPage::show ()
{
if (mp_owner && m_active) {
if (! is_modal_page ()) {
mp_owner->make_page_current (this);
return -1;
} 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 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; }
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
mp_modal_pages->set_current_index (i);
page->setup (mp_dispatcher);
page->set_focus ();
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) {
if (mp_pages->widget (i) == page) {
mp_pages->setCurrentIndex (i);
page->setup (mp_dispatcher);
page->set_focus ();
break;
}
@ -256,16 +258,17 @@ BEGIN_PROTECTED
// 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 ();
do_apply (false);
do_apply (true);
END_PROTECTED_W (this)
}
void
EditorOptionsPages::do_apply ()
EditorOptionsPages::do_apply (bool modal)
{
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.
(*p)->apply (mp_dispatcher->dispatcher ());
}
@ -276,7 +279,7 @@ void
EditorOptionsPages::apply ()
{
BEGIN_PROTECTED
do_apply ();
do_apply (false);
END_PROTECTED_W (this)
}
@ -417,8 +420,10 @@ EditorOptionsModalPages::widget (int index)
void
EditorOptionsModalPages::accept ()
{
mp_parent->apply ();
BEGIN_PROTECTED
mp_parent->do_apply (true);
QDialog::accept ();
END_PROTECTED
}
void
@ -430,9 +435,11 @@ EditorOptionsModalPages::reject ()
void
EditorOptionsModalPages::clicked (QAbstractButton *button)
{
BEGIN_PROTECTED
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_modal_content () const;
void do_apply (bool modal);
public slots:
void apply ();
@ -85,7 +86,6 @@ private:
EditorOptionsModalPages *mp_modal_pages;
void update (lay::EditorOptionsPage *page);
void do_apply ();
};
/**

View File

@ -344,6 +344,7 @@ EditorServiceBase::activated ()
}
#if defined(HAVE_QT)
std::vector<lay::EditorOptionsPage *>
EditorServiceBase::editor_options_pages ()
{
@ -360,23 +361,52 @@ EditorServiceBase::editor_options_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
EditorServiceBase::key_event (unsigned int key, unsigned int buttons)
{
#if defined(HAVE_QT)
if (is_active () && key == Qt::Key_Tab && buttons == 0) {
auto pages = editor_options_pages ();
for (auto p = pages.begin (); p != pages.end (); ++p) {
if ((*p)->is_focus_page ()) {
(*p)->show ();
return true;
}
EditorOptionsPage *fp = focus_page ();
if (fp) {
focus_page_open (fp);
}
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;
}
@ -384,9 +414,14 @@ void
EditorServiceBase::show_error (tl::Exception &ex)
{
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
*/
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
private:

View File

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

View File

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