From ac9a589d6f41e855bd82a1491a278b08f2233197 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 Aug 2025 22:29:47 +0200 Subject: [PATCH 01/52] WIP: two convenience methods for plugin API --- src/laybasic/laybasic/gsiDeclLayPlugin.cc | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index e35b72631..157b7366c 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -48,7 +48,8 @@ class PluginBase { public: PluginBase () - : lay::Plugin (sp_dispatcher), lay::ViewService (sp_view ? sp_view->canvas () : 0) + : lay::Plugin (sp_dispatcher), lay::ViewService (sp_view ? sp_view->canvas () : 0), + mp_view (sp_view), mp_dispatcher (sp_dispatcher) { if (! s_in_create_plugin) { throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); @@ -271,6 +272,16 @@ public: } } + lay::LayoutViewBase *view () + { + return mp_view.get (); + } + + lay::Dispatcher *dispatcher () + { + return mp_dispatcher.get (); + } + gsi::Callback f_menu_activated; gsi::Callback f_configure; gsi::Callback f_config_finalize; @@ -289,6 +300,10 @@ public: gsi::Callback f_update; gsi::Callback f_has_tracking_position; gsi::Callback f_tracking_position; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; }; static std::map s_factories; @@ -837,6 +852,22 @@ Class decl_Plugin ("lay", "Plugin", "See \\has_tracking_position for details.\n" "\n" "This method has been added in version 0.27.6." + ) + + gsi::method ("view", &gsi::PluginBase::view, + "@brief Gets the view object the plugin is associated with\n" + "This method returns the view object that the plugin is associated with.\n" + "\n" + "This convenience method has been added in version 0.30.4." + ) + + gsi::method ("dispatcher", &gsi::PluginBase::dispatcher, + "@brief Gets the dispatcher object the plugin is associated with\n" + "This method returns the dispatcher object that the plugin is associated with.\n" + "The dispatcher object manages the configuration parameters. 'set_config', 'get_config' and 'commit_config' " + "can be used on this object to get or set configuration parameters. " + "Configuration parameters are a way to persist information and the preferred way of communicating with " + "editor option pages and configuration pages.\n" + "\n" + "This convenience method has been added in version 0.30.4." ), "@brief The plugin object\n" "\n" From acfc2aae09c4e791ac15bf67ea925792f7dcde8e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 Aug 2025 00:04:44 +0200 Subject: [PATCH 02/52] WIP: preparing integration of editor options pages and config pages into plugin API --- src/laybasic/laybasic/gsiDeclLayPlugin.cc | 280 ++++++++++++++++-- .../laybasic}/layEditorOptionsPage.cc | 14 + .../laybasic}/layEditorOptionsPage.h | 19 +- .../laybasic}/layEditorOptionsPages.cc | 17 -- .../laybasic}/layEditorOptionsPages.h | 4 +- src/laybasic/laybasic/layPlugin.cc | 18 +- src/laybasic/laybasic/layPlugin.h | 12 + src/laybasic/laybasic/layViewObject.cc | 9 +- src/laybasic/laybasic/layViewObject.h | 5 + src/laybasic/laybasic/laybasic.pro | 4 + src/layui/layui/layui.pro | 4 - 11 files changed, 331 insertions(+), 55 deletions(-) rename src/{layui/layui => laybasic/laybasic}/layEditorOptionsPage.cc (86%) rename src/{layui/layui => laybasic/laybasic}/layEditorOptionsPage.h (93%) rename src/{layui/layui => laybasic/laybasic}/layEditorOptionsPages.cc (90%) rename src/{layui/layui => laybasic/laybasic}/layEditorOptionsPages.h (96%) diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index 157b7366c..6fea78df1 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -24,38 +24,180 @@ #include "gsiDecl.h" #include "gsiDeclBasic.h" #include "layPlugin.h" +#include "layPluginConfigPage.h" +#include "layEditorOptionsPage.h" +#include "layEditorOptionsPages.h" #include "layViewObject.h" #include "layLayoutViewBase.h" #include "layCursor.h" namespace gsi { - class PluginFactoryBase; - class PluginBase; -// TODO: these static variables are a bad hack! -// However it's not easy to pass parameters to a C++ classes constructor in Ruby without -// compromising the capability to derive from that class (at least I have not learned how -// to create a "new" in the super class *and* allow a "new" of the derived class). Anyway, -// since PluginBase object are only allowed to be created inside the create_plugin method -// of the factory, this hack is a quick but dirty workaround. +class PluginFactoryBase; +class PluginBase; + +#if defined(HAVE_QTBINDINGS) +class EditorOptionsPageImpl + : public lay::EditorOptionsPage +{ +public: + EditorOptionsPageImpl (const std::string &title, int index) + : lay::EditorOptionsPage (), m_title (title), m_index (index) + { + // .. nothing yet .. + } + + virtual std::string title () const + { + return m_title; + } + + virtual int order () const + { + return m_index; + } + + void apply_impl (lay::Dispatcher *root) + { + lay::EditorOptionsPage::apply (root); + } + + virtual void apply (lay::Dispatcher *root) + { + if (f_apply.can_issue ()) { + f_apply.issue (&EditorOptionsPageImpl::apply_impl, root); + } else { + EditorOptionsPageImpl::apply_impl (root); + } + } + + void setup_impl (lay::Dispatcher *root) + { + lay::EditorOptionsPage::setup (root); + } + + virtual void setup (lay::Dispatcher *root) + { + if (f_setup.can_issue ()) { + f_setup.issue (&EditorOptionsPageImpl::setup_impl, root); + } else { + EditorOptionsPageImpl::setup_impl (root); + } + } + + gsi::Callback f_apply; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + int m_index; +}; + +EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int index) +{ + return new EditorOptionsPageImpl (title, index); +} + +// @@@ methods: +// constructor new_editor_options_page +// view() +// edited() +// callback apply(dispatcher) +// callback setup(dispatcher) +// base: QWidget + +class ConfigPageImpl + : public lay::ConfigPage +{ +public: + ConfigPageImpl (const std::string &title) + : lay::ConfigPage (0), m_title (title) + { + // .. nothing yet .. + } + + virtual std::string title () const + { + return m_title; + } + + void commit_impl (lay::Dispatcher *root) + { + lay::ConfigPage::commit (root); + } + + virtual void commit (lay::Dispatcher *root) + { + if (f_commit.can_issue ()) { + f_commit.issue (&ConfigPageImpl::commit_impl, root); + } else { + ConfigPageImpl::commit_impl (root); + } + } + + void setup_impl (lay::Dispatcher *root) + { + lay::ConfigPage::setup (root); + } + + virtual void setup (lay::Dispatcher *root) + { + if (f_setup.can_issue ()) { + f_setup.issue (&ConfigPageImpl::setup_impl, root); + } else { + ConfigPageImpl::setup_impl (root); + } + } + + gsi::Callback f_commit; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + std::string m_index; +}; + +ConfigPageImpl *new_config_page (const std::string &title) +{ + return new ConfigPageImpl (title); +} + +// @@@ methods: +// constructor new_config_page +// callback apply(dispatcher) = commit +// callback setup(dispatcher) +// base: QFrame +#endif + +// HACK: used to track if we're inside a create_plugin method and can be sure that "init" is called static bool s_in_create_plugin = false; -static lay::LayoutViewBase *sp_view = 0; -static lay::Dispatcher *sp_dispatcher = 0; class PluginBase : public lay::Plugin, public lay::ViewService { public: PluginBase () - : lay::Plugin (sp_dispatcher), lay::ViewService (sp_view ? sp_view->canvas () : 0), - mp_view (sp_view), mp_dispatcher (sp_dispatcher) + : lay::Plugin (), lay::ViewService (), + mp_view (0), mp_dispatcher (0) { if (! s_in_create_plugin) { throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); } } + void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) + { + mp_view = view; + mp_dispatcher = dispatcher; + lay::Plugin::init (dispatcher); + lay::ViewService::init (view ? view->canvas () : 0); + } + void grab_mouse () { if (ui ()) { @@ -282,6 +424,18 @@ public: return mp_dispatcher.get (); } +#if defined(HAVE_QTBINDINGS) + std::vector editor_option_pages () + { + lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); + if (!eo_pages) { + return std::vector (); + } else { + return eo_pages->pages (); + } + } +#endif + gsi::Callback f_menu_activated; gsi::Callback f_configure; gsi::Callback f_config_finalize; @@ -416,8 +570,79 @@ public: } } +#if defined(HAVE_QTBINDINGS) + std::vector get_editor_options_pages_impl () const + { + return std::vector (); + } + + virtual void get_editor_options_pages (std::vector &pages_out, lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) const + { + try { + + std::vector pages; + if (f_get_editor_options_pages.can_issue ()) { + pages = f_get_editor_options_pages.issue > (&PluginFactoryBase::get_editor_options_pages_impl); + } else { + pages = get_editor_options_pages_impl (); + } + + pages_out.clear (); + for (auto i = pages.begin (); i != pages.end (); ++i) { + if (*i) { + (*i)->init (view, dispatcher); + pages_out.push_back (*i); + } + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + } + + std::vector get_config_pages_impl () const + { + return std::vector (); + } + + virtual std::vector > config_pages (QWidget *parent) const + { + std::vector > pages_out; + + try { + + std::vector pages; + + if (f_config_pages.can_issue ()) { + pages = f_config_pages.issue > (&PluginFactoryBase::get_config_pages_impl); + } else { + pages = get_config_pages_impl (); + } + + pages_out.clear (); + for (auto i = pages.begin (); i != pages.end (); ++i) { + if (*i) { + (*i)->setParent (parent); + pages_out.push_back (std::make_pair ((*i)->title (), *i)); + } + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + + return pages_out; + } +#endif + virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const - { + { if (f_create_plugin.can_issue ()) { return create_plugin_gsi (manager, root, view); } else { @@ -427,22 +652,25 @@ public: virtual gsi::PluginBase *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const { - // TODO: this is a hack. See notes above at s_in_create_plugin s_in_create_plugin = true; - sp_view = view; - sp_dispatcher = root; + gsi::PluginBase *ret = 0; try { + ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); - s_in_create_plugin = false; - sp_view = 0; - sp_dispatcher = 0; + if (ret) { + ret->init (view, root); + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); } catch (...) { - s_in_create_plugin = false; - sp_view = 0; - sp_dispatcher = 0; } + s_in_create_plugin = false; + return ret; } @@ -512,6 +740,8 @@ public: gsi::Callback f_configure; gsi::Callback f_config_finalize; gsi::Callback f_menu_activated; + gsi::Callback f_get_editor_options_pages; + gsi::Callback f_config_pages; private: std::vector > m_options; @@ -521,6 +751,12 @@ private: tl::RegisteredClass *mp_registration; }; +// @@@ +#if defined(HAVE_QTBINDINGS) +// get_editor_options_pages -> "create_editor_option_pages" +// config_pages -> "create_config_pages" +#endif + Class decl_PluginFactory ("lay", "PluginFactory", method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), "@brief Registers the plugin factory\n" diff --git a/src/layui/layui/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc similarity index 86% rename from src/layui/layui/layEditorOptionsPage.cc rename to src/laybasic/laybasic/layEditorOptionsPage.cc index 2da105f86..3f6a3046c 100644 --- a/src/layui/layui/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -39,6 +39,20 @@ EditorOptionsPage::EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher attach_events (); } +EditorOptionsPage::EditorOptionsPage () + : QWidget (0), mp_owner (0), m_active (true), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0) +{ + // .. nothing here -> call init to set view and dispatcher +} + +void +EditorOptionsPage::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) +{ + mp_view = view; + mp_dispatcher = dispatcher; + attach_events (); +} + EditorOptionsPage::~EditorOptionsPage () { set_owner (0); diff --git a/src/layui/layui/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h similarity index 93% rename from src/layui/layui/layEditorOptionsPage.h rename to src/laybasic/laybasic/layEditorOptionsPage.h index 08598784e..9bbfae510 100644 --- a/src/layui/layui/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -25,7 +25,7 @@ #ifndef HDR_layEditorOptionsPage #define HDR_layEditorOptionsPage -#include "layuiCommon.h" +#include "laybasicCommon.h" #include "tlObject.h" @@ -44,13 +44,14 @@ class EditorOptionsPages; /** * @brief The base class for a object properties page */ -class LAYUI_PUBLIC EditorOptionsPage +class LAYBASIC_PUBLIC EditorOptionsPage : public QWidget, public tl::Object { Q_OBJECT public: EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); + EditorOptionsPage (); virtual ~EditorOptionsPage (); virtual std::string title () const = 0; @@ -66,13 +67,8 @@ public: const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; } void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; } -protected slots: - void edited () - { - apply (dispatcher ()); - } + void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); -protected: lay::Dispatcher *dispatcher () const { return mp_dispatcher; @@ -83,6 +79,13 @@ protected: return mp_view; } +protected slots: + void edited () + { + apply (dispatcher ()); + } + +protected: virtual void active_cellview_changed () { } virtual void technology_changed (const std::string & /*tech*/) { } diff --git a/src/layui/layui/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc similarity index 90% rename from src/layui/layui/layEditorOptionsPages.cc rename to src/laybasic/laybasic/layEditorOptionsPages.cc index 1a154d029..9daad1b5a 100644 --- a/src/layui/layui/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -27,7 +27,6 @@ #include "tlExceptions.h" #include "layPlugin.h" #include "layLayoutViewBase.h" -#include "layQtTools.h" #include #include @@ -197,22 +196,6 @@ BEGIN_PROTECTED END_PROTECTED_W (this) } -// ------------------------------------------------------------------ -// Indicates an error on a line edit - -template -static void configure_from_line_edit (lay::Dispatcher *dispatcher, QLineEdit *le, const std::string &cfg_name) -{ - try { - Value value = Value (0); - tl::from_string_ext (tl::to_string (le->text ()), value); - dispatcher->config_set (cfg_name, tl::to_string (value)); - lay::indicate_error (le, (tl::Exception *) 0); - } catch (tl::Exception &ex) { - lay::indicate_error (le, &ex); - } -} - } #endif diff --git a/src/layui/layui/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h similarity index 96% rename from src/layui/layui/layEditorOptionsPages.h rename to src/laybasic/laybasic/layEditorOptionsPages.h index 07c172e6b..1cd66753c 100644 --- a/src/layui/layui/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -25,7 +25,7 @@ #ifndef HDR_layEditorOptionsPages #define HDR_layEditorOptionsPages -#include "layuiCommon.h" +#include "laybasicCommon.h" #include "layEditorOptionsPage.h" #include @@ -47,7 +47,7 @@ class Plugin; /** * @brief The object properties dialog */ -class LAYUI_PUBLIC EditorOptionsPages +class LAYBASIC_PUBLIC EditorOptionsPages : public QFrame { Q_OBJECT diff --git a/src/laybasic/laybasic/layPlugin.cc b/src/laybasic/laybasic/layPlugin.cc index 6965283d5..0184e2f6b 100644 --- a/src/laybasic/laybasic/layPlugin.cc +++ b/src/laybasic/laybasic/layPlugin.cc @@ -308,8 +308,23 @@ PluginDeclaration::register_plugin () // Plugin implementation Plugin::Plugin (Plugin *parent, bool standalone) - : mp_parent (parent), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (standalone) + : mp_parent (0), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (false) { + init (parent, standalone); +} + +Plugin::Plugin () + : mp_parent (0), mp_plugin_declaration (0), dm_finalize_config (this, &lay::Plugin::config_end), m_standalone (false) +{ + // .. nothing yet (waiting for init) .. +} + +void +Plugin::init (Plugin *parent, bool standalone) +{ + mp_parent = parent; + m_standalone = standalone; + if (! parent) { if (! standalone) { // load the root with the default configuration @@ -324,6 +339,7 @@ Plugin::Plugin (Plugin *parent, bool standalone) } } + Plugin::~Plugin () { if (mp_parent) { diff --git a/src/laybasic/laybasic/layPlugin.h b/src/laybasic/laybasic/layPlugin.h index 989bd233b..f334252cd 100644 --- a/src/laybasic/laybasic/layPlugin.h +++ b/src/laybasic/laybasic/layPlugin.h @@ -504,6 +504,18 @@ public: */ Plugin (Plugin *parent, bool standalone = false); + /** + * @brief The default constructor + * + * This constructor needs to be followed by init() + */ + Plugin (); + + /** + * @brief Initialization, following the default constructor + */ + void init (Plugin *parent, bool standalone = false); + /** * @brief The destructor */ diff --git a/src/laybasic/laybasic/layViewObject.cc b/src/laybasic/laybasic/layViewObject.cc index fe9329fcd..3cfa61c04 100644 --- a/src/laybasic/laybasic/layViewObject.cc +++ b/src/laybasic/laybasic/layViewObject.cc @@ -191,8 +191,15 @@ ViewObject::freeze () // ViewService implementation ViewService::ViewService (ViewObjectUI *widget) - : mp_widget (widget), m_abs_grab (false), m_enabled (true) + : mp_widget (0), m_abs_grab (false), m_enabled (true) { + init (widget); +} + +void +ViewService::init (ViewObjectUI *widget) +{ + mp_widget = widget; if (widget) { widget->register_service (this); } diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index 889eae89b..7664b8f78 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -104,6 +104,11 @@ public: */ ViewService (ViewObjectUI *widget = 0); + /** + * @brief Initialization, can follow default constructor + */ + void init (ViewObjectUI *widget); + /** * @brief Destructor */ diff --git a/src/laybasic/laybasic/laybasic.pro b/src/laybasic/laybasic/laybasic.pro index 4262ca687..293b456b7 100644 --- a/src/laybasic/laybasic/laybasic.pro +++ b/src/laybasic/laybasic/laybasic.pro @@ -35,6 +35,8 @@ SOURCES += \ gsiDeclLayTlAdded.cc \ gsiDeclLayRdbAdded.cc \ layAbstractMenu.cc \ + layEditorOptionsPage.cc \ + layEditorOptionsPages.cc \ layLayoutViewConfig.cc \ layMargin.cc \ laybasicForceLink.cc \ @@ -87,6 +89,8 @@ SOURCES += \ layUtils.cc \ HEADERS += \ + layEditorOptionsPage.h \ + layEditorOptionsPages.h \ layMargin.h \ laybasicConfig.h \ laybasicForceLink.h \ diff --git a/src/layui/layui/layui.pro b/src/layui/layui/layui.pro index 094e58410..cf30329d0 100644 --- a/src/layui/layui/layui.pro +++ b/src/layui/layui/layui.pro @@ -104,8 +104,6 @@ SOURCES = \ layEditStippleWidget.cc \ layEditStipplesForm.cc \ layEditorOptionsFrame.cc \ - layEditorOptionsPage.cc \ - layEditorOptionsPages.cc \ layFileDialog.cc \ layGenericSyntaxHighlighter.cc \ layHierarchyControlPanel.cc \ @@ -164,8 +162,6 @@ HEADERS = \ layEditStippleWidget.h \ layEditStipplesForm.h \ layEditorOptionsFrame.h \ - layEditorOptionsPage.h \ - layEditorOptionsPages.h \ layFileDialog.h \ layGenericSyntaxHighlighter.h \ layHierarchyControlPanel.h \ From 9e0d1d7ea7853ff1f022f8c82bf4743d9d89423d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 Aug 2025 23:06:18 +0200 Subject: [PATCH 03/52] WIP: new GSI declarations --- src/laybasic/laybasic/gsiDeclLayPlugin.cc | 138 +++++++++++++++++++--- 1 file changed, 124 insertions(+), 14 deletions(-) diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index 6fea78df1..9bafa0caa 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -31,6 +31,11 @@ #include "layLayoutViewBase.h" #include "layCursor.h" +#if defined(HAVE_QTBINDINGS) +# include "gsiQtGuiExternals.h" +# include "gsiQtWidgetsExternals.h" // for Qt5 +#endif + namespace gsi { @@ -58,6 +63,11 @@ public: return m_index; } + void call_edited () + { + lay::EditorOptionsPage::edited (); + } + void apply_impl (lay::Dispatcher *root) { lay::EditorOptionsPage::apply (root); @@ -101,13 +111,46 @@ EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int in return new EditorOptionsPageImpl (title, index); } -// @@@ methods: -// constructor new_editor_options_page -// view() -// edited() -// callback apply(dispatcher) -// callback setup(dispatcher) -// base: QWidget +Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPage", + constructor ("new", &new_editor_options_page, gsi::arg ("title"), gsi::arg ("index"), + "@brief Creates a new EditorOptionsPage object\n" + "@param title The title of the page\n" + "@param index The position of the page in the tab bar\n" + ) + + method ("view", &EditorOptionsPageImpl::view, + "@brief Gets the view object this page is associated with\n" + ) + + method ("edited", &EditorOptionsPageImpl::call_edited, + "@brief Call this method when some entry widget has changed\n" + "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" + ) + + 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" + ) + + 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" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's editor options page\n" + "\n" + "This object provides a way to establish plugin-specific editor options pages.\n" + "\n" + "The preferred way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted.\n" + "\n" + "For this purpose, the editor options page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); class ConfigPageImpl : public lay::ConfigPage @@ -167,6 +210,42 @@ ConfigPageImpl *new_config_page (const std::string &title) return new ConfigPageImpl (title); } +Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigPage", + constructor ("new", &new_config_page, gsi::arg ("title"), + "@brief Creates a new ConfigPage object\n" + "@param title The title of the page and also the position in the configuration page tree\n" + "\n" + "The title has the form 'Group|Page' - e.g. 'Application|Macro Development IDE' will place " + "the configuration page in the 'Application' group and into the 'Macro Development IDE' page." + ) + + callback ("apply", &ConfigPageImpl::commit, &ConfigPageImpl::f_commit, 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" + ) + + callback ("setup", &ConfigPageImpl::setup, &ConfigPageImpl::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" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's configuration page\n" + "\n" + "This object provides a way to establish plugin-specific configuration pages.\n" + "\n" + "The only way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted. Configuration parameters can be obtained by the plugin " + "directly from the \\Dispatcher object) or by listening to 'configure' calls.\n" + "\n" + "For the purpose of data transfer, the configuration page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + // @@@ methods: // constructor new_config_page // callback apply(dispatcher) = commit @@ -425,7 +504,7 @@ public: } #if defined(HAVE_QTBINDINGS) - std::vector editor_option_pages () + std::vector editor_options_pages () { lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); if (!eo_pages) { @@ -751,12 +830,6 @@ private: tl::RegisteredClass *mp_registration; }; -// @@@ -#if defined(HAVE_QTBINDINGS) -// get_editor_options_pages -> "create_editor_option_pages" -// config_pages -> "create_config_pages" -#endif - Class decl_PluginFactory ("lay", "PluginFactory", method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), "@brief Registers the plugin factory\n" @@ -885,6 +958,34 @@ Class decl_PluginFactory ("lay", "PluginFactory", "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." "\n\n" ) + +#if defined(HAVE_QTBINDINGS) + method ("create_editor_option_pages", &PluginFactoryBase::get_editor_options_pages, + "@brief Creates the editor option pages\n" + "The editor option pages are widgets of type \\EditorOptionsPage. These QFrame-type widgets " + "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " + "its mode is selected. Use this method to provide customized pages that will be displayed in the " + "editor options dock.\n" + "\n" + "This method is a factory. This means it will create objects and the ownership is taken " + "by the receiver.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + method ("create_config_pages", &PluginFactoryBase::config_pages, + "@brief Creates the configuration widgets\n" + "The configuration pages are widgets that are displayed in the " + "configuration dialog ('File/Setup'). Every plugin can create multiple such " + "widgets and specify, where these widgets are displayed. The widgets are of type \\ConfigPage.\n" + "\n" + "The title string also specifies the location of the widget in the " + "configuration page hierarchy. See \\ConfigPage for more details.\n" + "\n" + "This method is a factory. This means it will create objects and the ownership is taken " + "by the receiver.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + +#endif method ("has_tool_entry=", &gsi::PluginFactoryBase::has_tool_entry, gsi::arg ("f"), "@brief Enables or disables the tool bar entry\n" "Initially this property is set to true. This means that the plugin will have a visible entry in the toolbar. " @@ -1089,6 +1190,15 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + +#if defined(HAVE_QTBINDINGS) + gsi::method ("editor_options_pages", &gsi::PluginBase::editor_options_pages, + "@brief Gets the editor options pages which are associated with the view\n" + "The editor options pages are created by the plugin factory class and are associated with this plugin.\n" + "This method allows locating them and using them for plugin-specific purposes.\n" + "\n" + "This method has been added in version 0.30.4." + ) + +#endif gsi::method ("view", &gsi::PluginBase::view, "@brief Gets the view object the plugin is associated with\n" "This method returns the view object that the plugin is associated with.\n" From 88178fabd9b634e09e8a98431b3e53980a177cee Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 23 Aug 2025 17:33:11 +0200 Subject: [PATCH 04/52] [consider merging] Bugfix on Plugin registration This bug happened when a plugin package was installed. In this case, the plugin was installed dynamically. The effect was for example that the grid in a view became invisible. Problem was, that the plugin registration re-generated all plugins from the view, but did not reconfigure them. Hence the grid had the default setting which was invisible grid. --- src/laybasic/laybasic/layPlugin.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/laybasic/laybasic/layPlugin.cc b/src/laybasic/laybasic/layPlugin.cc index 0184e2f6b..f8c995c10 100644 --- a/src/laybasic/laybasic/layPlugin.cc +++ b/src/laybasic/laybasic/layPlugin.cc @@ -301,6 +301,7 @@ PluginDeclaration::register_plugin () if (Dispatcher::instance ()) { Dispatcher::instance ()->plugin_registered (this); initialize (Dispatcher::instance ()); + Dispatcher::instance ()->config_setup (); } } From c5d55570725d1658b5da1e107029dca5bc7b5e57 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 26 Aug 2025 22:36:48 +0200 Subject: [PATCH 05/52] WIP: debugging config page setup in plugins --- src/lay/lay/layMainWindow.cc | 5 +++ src/laybasic/laybasic/gsiDeclLayPlugin.cc | 51 +++++++++++++---------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 7b52b97b9..11f9d9f55 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -4381,6 +4381,11 @@ MainWindow::plugin_registered (lay::PluginDeclaration *cls) for (std::vector ::iterator vp = mp_views.begin (); vp != mp_views.end (); ++vp) { (*vp)->view ()->create_plugins (); } + + // regenerate the setup form + delete mp_setup_form; + mp_setup_form = new SettingsForm (0, dispatcher (), "setup_form"), + mp_setup_form->setup (); } void diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index 9bafa0caa..4be9b56b8 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -153,7 +153,7 @@ Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) ); class ConfigPageImpl - : public lay::ConfigPage + : public lay::ConfigPage, public gsi::ObjectBase { public: ConfigPageImpl (const std::string &title) @@ -245,12 +245,6 @@ Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigP "\n" "This class has been introduced in version 0.30.4.\n" ); - -// @@@ methods: -// constructor new_config_page -// callback apply(dispatcher) = commit -// callback setup(dispatcher) -// base: QFrame #endif // HACK: used to track if we're inside a create_plugin method and can be sure that "init" is called @@ -577,12 +571,11 @@ public: // remove an existing factory with the same name std::map ::iterator f = s_factories.find (name); - if (f != s_factories.end ()) { + if (f != s_factories.end () && f->second != this) { + // NOTE: this also removes the plugin from the s_factories list delete f->second; - f->second = this; - } else { - s_factories.insert (std::make_pair (std::string (name), this)); } + s_factories[name] = this; // cancel any previous registration and register (again) delete mp_registration; @@ -682,9 +675,15 @@ public: } } - std::vector get_config_pages_impl () const + void add_config_page (ConfigPageImpl *page) const { - return std::vector (); + page->keep (); + m_config_pages.push_back (page); + } + + void get_config_pages_impl () const + { + // .. nothing here .. } virtual std::vector > config_pages (QWidget *parent) const @@ -693,22 +692,24 @@ public: try { - std::vector pages; + m_config_pages.clear (); if (f_config_pages.can_issue ()) { - pages = f_config_pages.issue > (&PluginFactoryBase::get_config_pages_impl); + f_config_pages.issue (&PluginFactoryBase::get_config_pages_impl); } else { - pages = get_config_pages_impl (); + get_config_pages_impl (); } pages_out.clear (); - for (auto i = pages.begin (); i != pages.end (); ++i) { + for (auto i = m_config_pages.begin (); i != m_config_pages.end (); ++i) { if (*i) { (*i)->setParent (parent); pages_out.push_back (std::make_pair ((*i)->title (), *i)); } } + m_config_pages.clear (); + } catch (tl::Exception &ex) { tl::error << ex.msg (); } catch (std::exception &ex) { @@ -828,6 +829,7 @@ private: bool m_implements_mouse_mode; std::string m_mouse_mode_title; tl::RegisteredClass *mp_registration; + mutable std::vector m_config_pages; }; Class decl_PluginFactory ("lay", "PluginFactory", @@ -959,7 +961,7 @@ Class decl_PluginFactory ("lay", "PluginFactory", "\n\n" ) + #if defined(HAVE_QTBINDINGS) - method ("create_editor_option_pages", &PluginFactoryBase::get_editor_options_pages, + callback ("create_editor_option_pages", &PluginFactoryBase::get_editor_options_pages, &PluginFactoryBase::f_get_editor_options_pages, "@brief Creates the editor option pages\n" "The editor option pages are widgets of type \\EditorOptionsPage. These QFrame-type widgets " "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " @@ -971,7 +973,14 @@ Class decl_PluginFactory ("lay", "PluginFactory", "\n" "This method has been introduced in version 0.30.4." ) + - method ("create_config_pages", &PluginFactoryBase::config_pages, + method ("add_config_page", &PluginFactoryBase::add_config_page, gsi::arg ("page"), + "@brief Adds the given configuration page\n" + "See \\create_config_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_config_pages", &PluginFactoryBase::get_config_pages_impl, &PluginFactoryBase::f_config_pages, "@brief Creates the configuration widgets\n" "The configuration pages are widgets that are displayed in the " "configuration dialog ('File/Setup'). Every plugin can create multiple such " @@ -980,8 +989,8 @@ Class decl_PluginFactory ("lay", "PluginFactory", "The title string also specifies the location of the widget in the " "configuration page hierarchy. See \\ConfigPage for more details.\n" "\n" - "This method is a factory. This means it will create objects and the ownership is taken " - "by the receiver.\n" + "In order to create config pages, instantiate a \\ConfigPage object and " + "call \\add_config_page to register it.\n" "\n" "This method has been introduced in version 0.30.4." ) + From 90e25a7b75ddc06b5cd015c73750a3e82231b245 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 27 Aug 2025 00:12:55 +0200 Subject: [PATCH 06/52] WIP: debugging and polishing editor options pages interface for plugins --- src/laybasic/laybasic/gsiDeclLayPlugin.cc | 41 ++++++++++++++++------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index 4be9b56b8..a71fc72be 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -44,7 +44,7 @@ class PluginBase; #if defined(HAVE_QTBINDINGS) class EditorOptionsPageImpl - : public lay::EditorOptionsPage + : public lay::EditorOptionsPage, public gsi::ObjectBase { public: EditorOptionsPageImpl (const std::string &title, int index) @@ -267,7 +267,7 @@ public: { mp_view = view; mp_dispatcher = dispatcher; - lay::Plugin::init (dispatcher); + lay::Plugin::init (view); lay::ViewService::init (view ? view->canvas () : 0); } @@ -643,30 +643,39 @@ public: } #if defined(HAVE_QTBINDINGS) - std::vector get_editor_options_pages_impl () const + void add_editor_options_page (EditorOptionsPageImpl *page) const { - return std::vector (); + page->keep (); + m_editor_options_pages.push_back (page); + } + + void get_editor_options_pages_impl () const + { + // .. nothing here .. } virtual void get_editor_options_pages (std::vector &pages_out, lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) const { try { - std::vector pages; + m_editor_options_pages.clear (); + if (f_get_editor_options_pages.can_issue ()) { - pages = f_get_editor_options_pages.issue > (&PluginFactoryBase::get_editor_options_pages_impl); + f_get_editor_options_pages.issue (&PluginFactoryBase::get_editor_options_pages_impl); } else { - pages = get_editor_options_pages_impl (); + get_editor_options_pages_impl (); } - pages_out.clear (); - for (auto i = pages.begin (); i != pages.end (); ++i) { + for (auto i = m_editor_options_pages.begin (); i != m_editor_options_pages.end (); ++i) { if (*i) { (*i)->init (view, dispatcher); + (*i)->set_plugin_declaration (this); pages_out.push_back (*i); } } + m_editor_options_pages.clear (); + } catch (tl::Exception &ex) { tl::error << ex.msg (); } catch (std::exception &ex) { @@ -830,6 +839,7 @@ private: std::string m_mouse_mode_title; tl::RegisteredClass *mp_registration; mutable std::vector m_config_pages; + mutable std::vector m_editor_options_pages; }; Class decl_PluginFactory ("lay", "PluginFactory", @@ -961,15 +971,22 @@ Class decl_PluginFactory ("lay", "PluginFactory", "\n\n" ) + #if defined(HAVE_QTBINDINGS) - callback ("create_editor_option_pages", &PluginFactoryBase::get_editor_options_pages, &PluginFactoryBase::f_get_editor_options_pages, + method ("add_editor_options_page", &PluginFactoryBase::add_editor_options_page, gsi::arg ("page"), + "@brief Adds the given editor options page\n" + "See \\create_editor_options_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_editor_options_pages", &PluginFactoryBase::get_editor_options_pages_impl, &PluginFactoryBase::f_get_editor_options_pages, "@brief Creates the editor option pages\n" "The editor option pages are widgets of type \\EditorOptionsPage. These QFrame-type widgets " "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " "its mode is selected. Use this method to provide customized pages that will be displayed in the " "editor options dock.\n" "\n" - "This method is a factory. This means it will create objects and the ownership is taken " - "by the receiver.\n" + "In order to create config pages, instantiate a \\EditorOptionsPage object and " + "call \\add_editor_options_page to register it.\n" "\n" "This method has been introduced in version 0.30.4." ) + From c77a7328752645d428681f76d573de1e66094589 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 28 Aug 2025 23:31:15 +0200 Subject: [PATCH 07/52] WIP: more features exposed for plugins (snapping) --- src/edt/edt/edtConfig.cc | 5 + src/edt/edt/edtConfig.h | 2 + src/edt/edt/edtService.cc | 6 +- .../laybasic => lay/lay}/gsiDeclLayPlugin.cc | 282 +++++++++++++++++- src/lay/lay/lay.pro | 1 + src/laybasic/laybasic/layEditable.cc | 9 + src/laybasic/laybasic/layEditable.h | 5 + src/laybasic/laybasic/layEditorServiceBase.cc | 11 +- src/laybasic/laybasic/layEditorServiceBase.h | 7 +- src/laybasic/laybasic/laybasic.pro | 1 - 10 files changed, 308 insertions(+), 21 deletions(-) rename src/{laybasic/laybasic => lay/lay}/gsiDeclLayPlugin.cc (84%) diff --git a/src/edt/edt/edtConfig.cc b/src/edt/edt/edtConfig.cc index 255b9ccbd..9de28292d 100644 --- a/src/edt/edt/edtConfig.cc +++ b/src/edt/edt/edtConfig.cc @@ -28,6 +28,11 @@ namespace edt { +int snap_range_pixels () +{ + return 8; // TODO: make variable +} + // ----------------------------------------------------------------------------- std::string cfg_edit_grid ("edit-grid"); diff --git a/src/edt/edt/edtConfig.h b/src/edt/edt/edtConfig.h index 97964a9e0..fd8d0bd8f 100644 --- a/src/edt/edt/edtConfig.h +++ b/src/edt/edt/edtConfig.h @@ -118,6 +118,8 @@ struct EDT_PUBLIC VAlignConverter void from_string (const std::string &s, db::VAlign &a); }; +int EDT_PUBLIC snap_range_pixels (); + } #endif diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index e17f5ee71..ce561a90a 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -298,12 +298,10 @@ Service::snap (const db::DPoint &p, const db::DPoint &plast, bool connect) const return snap (ps); } -const int sr_pixels = 8; // TODO: make variable - lay::PointSnapToObjectResult Service::snap2_details (const db::DPoint &p) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (sr_pixels); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); } @@ -316,7 +314,7 @@ Service::snap2 (const db::DPoint &p) const db::DPoint Service::snap2 (const db::DPoint &p, const db::DPoint &plast, bool connect) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (sr_pixels); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac () : move_ac (), snap_range).snapped_point; } diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc similarity index 84% rename from src/laybasic/laybasic/gsiDeclLayPlugin.cc rename to src/lay/lay/gsiDeclLayPlugin.cc index a71fc72be..1dc0efcec 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -23,13 +23,16 @@ #include "gsiDecl.h" #include "gsiDeclBasic.h" +#include "gsiEnums.h" #include "layPlugin.h" #include "layPluginConfigPage.h" #include "layEditorOptionsPage.h" #include "layEditorOptionsPages.h" +#include "layEditorServiceBase.h" #include "layViewObject.h" #include "layLayoutViewBase.h" #include "layCursor.h" +#include "edtConfig.h" #if defined(HAVE_QTBINDINGS) # include "gsiQtGuiExternals.h" @@ -251,12 +254,15 @@ Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigP static bool s_in_create_plugin = false; class PluginBase - : public lay::Plugin, public lay::ViewService + : public lay::EditorServiceBase { public: PluginBase () - : lay::Plugin (), lay::ViewService (), - mp_view (0), mp_dispatcher (0) + : lay::EditorServiceBase (), + mp_view (0), mp_dispatcher (0), + m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), + m_snap_to_objects (true), + m_snap_objects_to_grid (true) { if (! s_in_create_plugin) { throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); @@ -267,8 +273,7 @@ public: { mp_view = view; mp_dispatcher = dispatcher; - lay::Plugin::init (view); - lay::ViewService::init (view ? view->canvas () : 0); + lay::EditorServiceBase::init (view); } void grab_mouse () @@ -306,16 +311,118 @@ public: } } - virtual bool configure (const std::string &name, const std::string &value) + db::DPoint snap (db::DPoint p) const + { + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + p = lay::snap_xy (p, m_global_grid); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + p = lay::snap_xy (p, m_edit_grid); + } + + return p; + } + + db::DVector snap_vector (db::DVector v) const + { + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + v = lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint (); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + v = lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint (); + } + + return v; + } + + db::DPoint snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const + { + db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac (ac) : move_ac (ac)); + return snap (ps); + } + + db::DVector snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const + { + return snap_vector (lay::snap_angle (v, connect ? connect_ac (ac) : move_ac (ac))); + } + + db::DPoint snap2 (const db::DPoint &p, bool visualize) + { + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; + } + + db::DPoint snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) + { + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; + } + + /** + * @brief Captures some edt space configuration events for convencience + */ + void configure_edt (const std::string &name, const std::string &value) + { + edt::EditGridConverter egc; + edt::ACConverter acc; + + if (name == edt::cfg_edit_global_grid) { + egc.from_string (value, m_global_grid); + } else if (name == edt::cfg_edit_grid) { + egc.from_string (value, m_edit_grid); + } else if (name == edt::cfg_edit_snap_to_objects) { + tl::from_string (value, m_snap_to_objects); + } else if (name == edt::cfg_edit_snap_objects_to_grid) { + tl::from_string (value, m_snap_objects_to_grid); + } else if (name == edt::cfg_edit_move_angle_mode) { + acc.from_string (value, m_move_ac); + } else if (name == edt::cfg_edit_connect_angle_mode) { + acc.from_string (value, m_connect_ac); + } else { + lay::EditorServiceBase::configure (name, value); + } + } + + /** + * @brief The implementation does not allow to bypass the base class configuration call + */ + bool configure_impl (const std::string &name, const std::string &value) { return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); } - virtual void config_finalize () + virtual bool configure (const std::string &name, const std::string &value) + { + configure_edt (name, value); + return configure_impl (name, value); + } + + /** + * @brief The implementation does not allow to bypass the base class configuration call + */ + virtual void config_finalize_impl () { f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginBase::config_finalize) : lay::Plugin::config_finalize (); } + virtual void config_finalize () + { + lay::EditorServiceBase::config_finalize (); + config_finalize_impl (); + } + virtual bool key_event (unsigned int key, unsigned int buttons) { if (f_key_event.can_issue ()) { @@ -487,14 +594,14 @@ public: } } - lay::LayoutViewBase *view () + lay::LayoutViewBase *view () const { - return mp_view.get (); + return const_cast (mp_view.get ()); } - lay::Dispatcher *dispatcher () + lay::Dispatcher *dispatcher () const { - return mp_dispatcher.get (); + return const_cast (mp_dispatcher.get ()); } #if defined(HAVE_QTBINDINGS) @@ -531,6 +638,25 @@ public: private: tl::weak_ptr mp_view; tl::weak_ptr mp_dispatcher; + + // Angle constraints and grids + lay::angle_constraint_type m_connect_ac, m_move_ac; + db::DVector m_edit_grid; + bool m_snap_to_objects; + bool m_snap_objects_to_grid; + db::DVector m_global_grid; + + lay::angle_constraint_type connect_ac (lay::angle_constraint_type ac) const + { + // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint + return ac != lay::AC_Global ? ac : m_connect_ac; + } + + lay::angle_constraint_type move_ac (lay::angle_constraint_type ac) const + { + // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint + return ac != lay::AC_Global ? ac : m_move_ac; + } }; static std::map s_factories; @@ -968,7 +1094,6 @@ Class decl_PluginFactory ("lay", "PluginFactory", "\\MainWindow or listening to \\configure callbacks (either in the factory or the plugin instance). Configuration variables can " "be set using \"set_config\" from \\MainWindow. This scheme also works without registering the configuration options, but " "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." - "\n\n" ) + #if defined(HAVE_QTBINDINGS) method ("add_editor_options_page", &PluginFactoryBase::add_editor_options_page, gsi::arg ("page"), @@ -1093,19 +1218,28 @@ Class decl_PluginFactory ("lay", "PluginFactory", "This class has been introduced in version 0.22.\n" ); +/*@@@ to add: +void add_mouse_cursor (const db::DPoint &pt, bool emphasize = false); +void add_mouse_cursor (const db::Point &pt, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); +void add_edge_marker (const db::DEdge &e, bool emphasize = false); +void add_edge_marker (const db::Edge &e, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); +void clear_mouse_cursors (); +void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details); +@@@*/ + Class decl_Plugin ("lay", "Plugin", callback ("menu_activated", &gsi::PluginBase::menu_activated, &gsi::PluginBase::f_menu_activated, gsi::arg ("symbol"), "@brief Gets called when a custom menu item is selected\n" "When a menu item is clicked which was registered with the plugin factory, the plugin's 'menu_activated' method is " "called for the current view. The symbol registered for the menu item is passed in the 'symbol' argument." ) + - callback ("configure", &gsi::PluginBase::configure, &gsi::PluginBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), + callback ("configure", &gsi::PluginBase::configure_impl, &gsi::PluginBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), "@brief Sends configuration requests to the plugin\n" "@param name The name of the configuration variable as registered in the plugin factory\n" "@param value The value of the configuration variable\n" "When a configuration variable is changed, the new value is reported to the plugin by calling the 'configure' method." ) + - callback ("config_finalize", &gsi::PluginBase::config_finalize, &gsi::PluginBase::f_config_finalize, + callback ("config_finalize", &gsi::PluginBase::config_finalize_impl, &gsi::PluginBase::f_config_finalize, "@brief Sends the post-configuration request to the plugin\n" "After all configuration parameters have been sent, 'config_finalize' is called to given the plugin a chance to " "update its internal state according to the new configuration.\n" @@ -1216,6 +1350,100 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + + method ("snap", &gsi::PluginBase::snap, gsi::arg ("p"), + "@brief Snaps a point to the edit grid\n" + "\n" + "@param p The point to snap\n" + "\n" + "If the edit grid is given, the point's x and y components\n" + "are snapped to the edit grid. Otherwise the global grid is used.\n" + "Edit and global grid are set by configuration options.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginBase::snap_vector, gsi::arg ("v"), + "@brief Snaps a vector to the edit grid\n" + "\n" + "@param v The vector to snap\n" + "\n" + "If the edit grid is given, the vector's x and y components\n" + "are snapped to the edit grid. Otherwise the global grid is used.\n" + "Edit and global grid are set by configuration options.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginBase::snap_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + "@brief Snaps a point to the edit grid with an angle constraint\n" + "\n" + "@param p The point to snap\n" + "@param plast The last point of the connection/move vector\n" + "@param connect true, if the point is an connection vertex, false if it is a move target point\n" + "@param ac Overrides the connect or move angle constraint unless it is \\Plugin#AC_Global\n" + "\n" + "This method snaps point \"p\" relative to the initial point \"plast\". This method\n" + "tries to snap \"p\" to the edit or global grid (edit grid with higher priority), while\n" + "trying to observe the angle constraint that imposes a constraint on the way \"p\"\n" + "can move relative to \"plast\".\n" + "\n" + "The \"connect\" parameter will decide which angle constraint to use, unless \"ac\" specifies\n" + "an angle constraint already. If \"connect\" is true, the line between \"p\" and \"plast\" is regarded a connection\n" + "between points (e.g. a polygon edge) and the connection angle constraint applies. Otherwise\n" + "the move constraint applies.\n" + "\n" + "The angle constraint determines how \"p\" can move in relation to \"plast\" - for example,\n" + "if the angle constraint is \\Plugin#AC_Ortho, \"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap", &gsi::PluginBase::snap_delta, gsi::arg ("v"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + "@brief Snaps a move vector to the edit grid with and implies an angle constraint\n" + "\n" + "@param v The vector to snap\n" + "@param connect true, if the vector is an connection vector, false if it is a move vector\n" + "@param ac Overrides the connect or move angle constraint unless it is AC_Global\n" + "\n" + "The \"connect\" parameter will decide which angle constraint to use, unless \"ac\" specifies\n" + "an angle constraint already. If \"connect\" is true, the vector is regarded a connection line\n" + "between points (e.g. a polygon edge) and the connection angle constraint applies. Otherwise\n" + "the move constraint applies.\n" + "\n" + "The angle constraint determines how \"p\" can move in relation to \"plast\" - for example,\n" + "if the angle constraint is \\Plugin#AC_Ortho, \"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap2", &gsi::PluginBase::snap2, gsi::arg ("p"), gsi::arg ("visualize", false), + "@brief Snaps a point to the edit grid with advanced snapping (including object snapping)\n" + "\n" + "@param p The point to snap\n" + "@param visualize If true, a cursor shape is added to the scene indicating the snap details\n" + "\n" + "This method behaves like the other \"snap2\" variant, but does not allow to specify an\n" + "angle constraint. Only grid constraints and snapping to objects is supported.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("snap2", &gsi::PluginBase::snap2_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), gsi::arg ("visualize", false), + "@brief Snaps a point to the edit grid with an angle constraint with advanced snapping (including object snapping)\n" + "\n" + "@param p The point to snap\n" + "@param plast The last point of the connection or move start point\n" + "@param connect true, if the point is an connection, false if it is a move target point\n" + "@param ac Overrides the connect or move angle constraint unless it is AC_Global\n" + "@param visualize If true, a cursor shape is added to the scene indicating the snap details\n" + "\n" + "This method will snap the point p, given an initial point \"plast\". This includes an angle constraint.\n" + "If \"connect\" is true, the line between \"plast\" and \"p\" is regarded a connection (e.g. a polygon edge).\n" + "If not, the line is regarded a move vector. If \"ac\" is \\Plugin#AC_Global, the angle constraint is \n" + "taken from the connect or move angle constraint, depending on the value of \"connect\". The angle constraint\n" + "determines how \"p\" can move in relation to \"plast\" - for example, if the angle constraint is \\Plugin#AC_Ortho, \n" + "\"p\" can only move away from \"plast\" in horizontal or vertical direction.\n" + "\n" + "This method considers options like global or editing grid or whether the target point\n" + "will snap to another object. The behavior is given by the respective configuration.\n" + "\n" + "This method has been added in version 0.30.4." + ) + #if defined(HAVE_QTBINDINGS) gsi::method ("editor_options_pages", &gsi::PluginBase::editor_options_pages, "@brief Gets the editor options pages which are associated with the view\n" @@ -1250,6 +1478,32 @@ Class decl_Plugin ("lay", "Plugin", "This class has been introduced in version 0.22.\n" ); +gsi::Enum decl_AngleConstraintType ("lay", "AngleConstraintType", + gsi::enum_const ("AC_Global", lay::AC_Global, + "@brief Specifies to use the global angle constraint.\n" + ) + + gsi::enum_const ("AC_Any", lay::AC_Any, + "@brief Specifies to use any angle and not snap to a specific direction.\n" + ) + + gsi::enum_const ("AC_Diagonal", lay::AC_Diagonal, + "@brief Specifies to use multiples of 45 degree.\n" + ) + + gsi::enum_const ("AC_Ortho", lay::AC_Ortho, + "@brief Specifies to use multiples of 90 degree.\n" + ) + + gsi::enum_const ("AC_Horizontal", lay::AC_Horizontal, + "@brief Specifies to use horizontal direction only.\n" + ) + + gsi::enum_const ("AC_Vertical", lay::AC_Vertical, + "@brief Specifies to use vertical direction only.\n" + ), + "@brief Specifies angle constraints during snapping.\n" + "\n" + "This enum has been introduced in version 0.30.4." +); + +gsi::ClassExt inject_AngleConstraintType_in_parent (decl_AngleConstraintType.defs ()); + class CursorNamespace { }; static int cursor_shape_none () { return int (lay::Cursor::none); } diff --git a/src/lay/lay/lay.pro b/src/lay/lay/lay.pro index 23295c1f8..d3cfcdaf6 100644 --- a/src/lay/lay/lay.pro +++ b/src/lay/lay/lay.pro @@ -119,6 +119,7 @@ SOURCES = \ gsiDeclLayApplication.cc \ gsiDeclLayHelpDialog.cc \ gsiDeclLayMainWindow.cc \ + gsiDeclLayPlugin.cc \ layApplication.cc \ layClipDialog.cc \ layControlWidgetStack.cc \ diff --git a/src/laybasic/laybasic/layEditable.cc b/src/laybasic/laybasic/layEditable.cc index 0c96df3c3..fefbe3d26 100644 --- a/src/laybasic/laybasic/layEditable.cc +++ b/src/laybasic/laybasic/layEditable.cc @@ -54,6 +54,15 @@ Editable::Editable (lay::Editables *editables) } } +void +Editable::init (lay::Editables *editables) +{ + mp_editables = editables; + if (editables) { + editables->m_editables.push_back (this); + } +} + Editable::~Editable () { // Reasoning for reset (): on MSVC, virtual functions must not be called inside diff --git a/src/laybasic/laybasic/layEditable.h b/src/laybasic/laybasic/layEditable.h index 4d1ba5da5..cb696600c 100644 --- a/src/laybasic/laybasic/layEditable.h +++ b/src/laybasic/laybasic/layEditable.h @@ -71,6 +71,11 @@ public: */ Editable (Editables *editables = 0); + /** + * @brief Initializes after constructor with a null pointer was called + */ + void init (Editables *editables); + /** * @brief The constructor */ diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index dfe6b27cb..1a3e4c86c 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -207,7 +207,7 @@ private: // -------------------------------------------------------------------------------------- EditorServiceBase::EditorServiceBase (LayoutViewBase *view) - : lay::ViewService (view->canvas ()), + : lay::ViewService (view ? view->canvas () : 0), lay::Editable (view), lay::Plugin (view), mp_view (view), @@ -217,6 +217,15 @@ EditorServiceBase::EditorServiceBase (LayoutViewBase *view) // .. nothing yet .. } +void +EditorServiceBase::init (LayoutViewBase *view) +{ + mp_view = view; + lay::Plugin::init (view); + lay::ViewService::init (view ? view->canvas () : 0); + lay::Editable::init (view); +} + EditorServiceBase::~EditorServiceBase () { clear_mouse_cursors (); diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 0b8cbd854..d17c77255 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -46,7 +46,12 @@ public: /** * @brief Constructor */ - EditorServiceBase (lay::LayoutViewBase *view); + EditorServiceBase (lay::LayoutViewBase *view = 0); + + /** + * @brief Initialize after constructor was called with null view pointer + */ + void init (lay::LayoutViewBase *view); /** * @brief Destructor diff --git a/src/laybasic/laybasic/laybasic.pro b/src/laybasic/laybasic/laybasic.pro index 293b456b7..8c5ba030d 100644 --- a/src/laybasic/laybasic/laybasic.pro +++ b/src/laybasic/laybasic/laybasic.pro @@ -31,7 +31,6 @@ SOURCES += \ gsiDeclLayLayoutViewBase.cc \ gsiDeclLayMarker.cc \ gsiDeclLayMenu.cc \ - gsiDeclLayPlugin.cc \ gsiDeclLayTlAdded.cc \ gsiDeclLayRdbAdded.cc \ layAbstractMenu.cc \ From 3eff75433c1246c903cb16e9757fd1164249a4d3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 29 Aug 2025 17:50:03 +0200 Subject: [PATCH 08/52] WIP --- src/edt/edt/edtService.cc | 21 ---- src/edt/edt/edtService.h | 16 --- src/edt/edt/edtUtils.cc | 19 ++++ src/edt/edt/edtUtils.h | 11 ++ src/lay/lay/gsiDeclLayPlugin.cc | 178 ++++++++++++++++++++++++++------ 5 files changed, 175 insertions(+), 70 deletions(-) diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index ce561a90a..e6be20c37 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -39,27 +39,6 @@ namespace edt { -// ------------------------------------------------------------- -// Convert buttons to an angle constraint - -lay::angle_constraint_type -ac_from_buttons (unsigned int buttons) -{ - if ((buttons & lay::ShiftButton) != 0) { - if ((buttons & lay::ControlButton) != 0) { - return lay::AC_Any; - } else { - return lay::AC_Ortho; - } - } else { - if ((buttons & lay::ControlButton) != 0) { - return lay::AC_Diagonal; - } else { - return lay::AC_Global; - } - } -} - // ------------------------------------------------------------- Service::Service (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIterator::flags_type flags) diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index e782bbfb5..979090bc1 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -55,22 +55,6 @@ class PluginDeclarationBase; // ------------------------------------------------------------- -extern lay::angle_constraint_type ac_from_buttons (unsigned int buttons); - -// ------------------------------------------------------------- - -/** - * @brief Utility function: serialize PCell parameters into a string - */ -std::string pcell_parameters_to_string (const std::map ¶meters); - -/** - * @brief Utility: deserialize PCell parameters from a string - */ -std::map pcell_parameters_from_string (const std::string &s); - -// ------------------------------------------------------------- - /** * @brief A utility class to implement a selection iterator across all editor services */ diff --git a/src/edt/edt/edtUtils.cc b/src/edt/edt/edtUtils.cc index 4a84aa661..40ef08e87 100644 --- a/src/edt/edt/edtUtils.cc +++ b/src/edt/edt/edtUtils.cc @@ -37,6 +37,25 @@ namespace edt { // ------------------------------------------------------------- +// Convert buttons to an angle constraint +lay::angle_constraint_type +ac_from_buttons (unsigned int buttons) +{ + if ((buttons & lay::ShiftButton) != 0) { + if ((buttons & lay::ControlButton) != 0) { + return lay::AC_Any; + } else { + return lay::AC_Ortho; + } + } else { + if ((buttons & lay::ControlButton) != 0) { + return lay::AC_Diagonal; + } else { + return lay::AC_Global; + } + } +} + std::string pcell_parameters_to_string (const std::map ¶meters) { std::string param; diff --git a/src/edt/edt/edtUtils.h b/src/edt/edt/edtUtils.h index de083b39a..a978246c6 100644 --- a/src/edt/edt/edtUtils.h +++ b/src/edt/edt/edtUtils.h @@ -30,6 +30,7 @@ #include #include "layObjectInstPath.h" +#include "laySnap.h" #include "dbInstElement.h" #include "dbClipboardData.h" @@ -45,6 +46,16 @@ namespace edt { class Service; +// ------------------------------------------------------------- + +/** + * @brief Convert a button flag set to an angle constraint + * + * This implements the standard modifiers for angle constraints - i.e. + * ortho for "Shift". + */ +extern lay::angle_constraint_type ac_from_buttons (unsigned int buttons); + /** * @brief Serializes PCell parameters to a string */ diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 1dc0efcec..d5b17bb58 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -33,6 +33,7 @@ #include "layLayoutViewBase.h" #include "layCursor.h" #include "edtConfig.h" +#include "edtUtils.h" #if defined(HAVE_QTBINDINGS) # include "gsiQtGuiExternals.h" @@ -428,7 +429,7 @@ public: if (f_key_event.can_issue ()) { return f_key_event.issue (&lay::ViewService::key_event, key, buttons); } else { - return lay::ViewService::key_event (key, buttons); + return lay::EditorServiceBase::key_event (key, buttons); } } @@ -437,7 +438,7 @@ public: if (f_mouse_press_event.can_issue ()) { return f_mouse_press_event.issue (&PluginBase::mouse_press_event_noref, p, buttons, prio); } else { - return lay::ViewService::mouse_press_event (p, buttons, prio); + return lay::EditorServiceBase::mouse_press_event (p, buttons, prio); } } @@ -452,7 +453,7 @@ public: if (f_mouse_click_event.can_issue ()) { return f_mouse_click_event.issue (&PluginBase::mouse_click_event_noref, p, buttons, prio); } else { - return lay::ViewService::mouse_click_event (p, buttons, prio); + return lay::EditorServiceBase::mouse_click_event (p, buttons, prio); } } @@ -467,7 +468,7 @@ public: if (f_mouse_double_click_event.can_issue ()) { return f_mouse_double_click_event.issue (&PluginBase::mouse_double_click_event_noref, p, buttons, prio); } else { - return lay::ViewService::mouse_double_click_event (p, buttons, prio); + return lay::EditorServiceBase::mouse_double_click_event (p, buttons, prio); } } @@ -480,18 +481,18 @@ public: virtual bool leave_event (bool prio) { if (f_leave_event.can_issue ()) { - return f_leave_event.issue (&lay::ViewService::leave_event, prio); + return f_leave_event.issue (&lay::ViewService::leave_event, prio); } else { - return lay::ViewService::leave_event (prio); + return lay::EditorServiceBase::leave_event (prio); } } virtual bool enter_event (bool prio) { if (f_enter_event.can_issue ()) { - return f_enter_event.issue (&lay::ViewService::enter_event, prio); + return f_enter_event.issue (&lay::ViewService::enter_event, prio); } else { - return lay::ViewService::enter_event (prio); + return lay::EditorServiceBase::enter_event (prio); } } @@ -500,7 +501,7 @@ public: if (f_mouse_move_event.can_issue ()) { return f_mouse_move_event.issue (&PluginBase::mouse_move_event_noref, p, buttons, prio); } else { - return lay::ViewService::mouse_move_event (p, buttons, prio); + return lay::EditorServiceBase::mouse_move_event (p, buttons, prio); } } @@ -543,54 +544,110 @@ public: virtual void activated () { if (f_activated.can_issue ()) { - f_activated.issue (&lay::ViewService::activated); + f_activated.issue (&lay::EditorServiceBase::activated); } else { - lay::ViewService::activated (); + lay::EditorServiceBase::activated (); + } + } + + void deactivated_impl () + { + if (f_deactivated.can_issue ()) { + f_deactivated.issue (&PluginBase::deactivated_impl); } } virtual void deactivated () { - if (f_deactivated.can_issue ()) { - f_deactivated.issue (&lay::ViewService::deactivated); - } else { - lay::ViewService::deactivated (); - } + lay::EditorServiceBase::deactivated (); + deactivated_impl (); } virtual void drag_cancel () { if (f_drag_cancel.can_issue ()) { - f_drag_cancel.issue (&lay::ViewService::drag_cancel); + f_drag_cancel.issue (&lay::EditorServiceBase::drag_cancel); } else { - lay::ViewService::drag_cancel (); + lay::EditorServiceBase::drag_cancel (); } } virtual void update () { if (f_update.can_issue ()) { - f_update.issue (&lay::ViewService::update); + f_update.issue (&lay::EditorServiceBase::update); } else { - lay::ViewService::update (); + lay::EditorServiceBase::update (); } } + void add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) + { + lay::EditorServiceBase::add_mouse_cursor (p, emphasize); + } + + void add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) + { + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; + } + + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; + } + + edt::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; + } + + lay::EditorServiceBase::add_mouse_cursor (p, cv_index, cv.context_trans (), *tv_list, emphasize); + } + + void add_edge_marker_dedge (const db::DEdge &p, bool emphasize) + { + lay::EditorServiceBase::add_edge_marker (p, emphasize); + } + + void add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) + { + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; + } + + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; + } + + edt::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; + } + + lay::EditorServiceBase::add_edge_marker (p, cv_index, cv.context_trans (), *tv_list, emphasize); + } + virtual bool has_tracking_position () const { if (f_has_tracking_position.can_issue ()) { - return f_has_tracking_position.issue (&lay::ViewService::has_tracking_position); + return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); } else { - return lay::ViewService::has_tracking_position (); + return lay::EditorServiceBase::has_tracking_position (); } } virtual db::DPoint tracking_position () const { if (f_tracking_position.can_issue ()) { - return f_tracking_position.issue (&lay::ViewService::tracking_position); + return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); } else { - return lay::ViewService::tracking_position (); + return lay::EditorServiceBase::tracking_position (); } } @@ -1218,15 +1275,6 @@ Class decl_PluginFactory ("lay", "PluginFactory", "This class has been introduced in version 0.22.\n" ); -/*@@@ to add: -void add_mouse_cursor (const db::DPoint &pt, bool emphasize = false); -void add_mouse_cursor (const db::Point &pt, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); -void add_edge_marker (const db::DEdge &e, bool emphasize = false); -void add_edge_marker (const db::Edge &e, unsigned int cv_index, const db::ICplxTrans >, const std::vector &tv, bool emphasize = false); -void clear_mouse_cursors (); -void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details); -@@@*/ - Class decl_Plugin ("lay", "Plugin", callback ("menu_activated", &gsi::PluginBase::menu_activated, &gsi::PluginBase::f_menu_activated, gsi::arg ("symbol"), "@brief Gets called when a custom menu item is selected\n" @@ -1350,6 +1398,62 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + + method ("clear_mouse_cursors", &gsi::PluginBase::clear_mouse_cursors, + "@brief Clears all existing mouse cursors\n" + "Use this function to remove exisiting mouse cursors (see \\add_mouse_cursor and \\add_edge_marker).\n" + "This method is automatically called when the plugin becomes deactivated.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_mouse_cursor", &gsi::PluginBase::add_mouse_cursor_dpoint, gsi::arg ("p"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate the mouse position\n" + "This function will create a marker that indicates the (for example snapped) mouse position. " + "In addition to this, it will establish the position for the tracking cursor, if mouse " + "tracking is enabled in the application. Multiple cursors can be created. In that case, the " + "tracking position is given by the last cursor.\n" + "\n" + "If 'emphasize' is true, the cursor is displayed in a 'stronger' style - i.e. with a double circle instead of a single one.\n" + "\n" + "Before you use this method, clear existing cursors with \\clear_mouse_cursors.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_mouse_cursor", &gsi::PluginBase::add_mouse_cursor_point, gsi::arg ("p"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate the mouse position\n" + "This version of this method creates a mouse cursor based on the integer-unit point and\n" + "a source cellview index plus a layer info.\n" + "The cellview index and layer info is used to derive the transformation rules to apply to the " + "point and to compute the final position.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_edge_marker", &gsi::PluginBase::add_edge_marker_dedge, gsi::arg ("e"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate an edge\n" + "This function will create a marker that indicates an edge - for example the edge that a point is snapping to. " + "\n" + "If 'emphasize' is true, the cursor is displayed in a 'stronger' style.\n" + "\n" + "Before you use this method, clear existing edge markers and cursors with \\clear_mouse_cursors.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("add_edge_marker", &gsi::PluginBase::add_edge_marker_edge, gsi::arg ("e"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + "@brief Creates a cursor to indicate an edge\n" + "This version of this method creates an edge marker based on the integer-unit edge and\n" + "a source cellview index plus a layer info.\n" + "The cellview index and layer info is used to derive the transformation rules to apply to the " + "edge and to compute the final position.\n" + "\n" + "This method has been added in version 0.30.4." + ) + + method ("ac_from_buttons", &edt::ac_from_buttons, gsi::arg ("buttons"), + "@brief Creates an angle constraint from a button combination\n" + "This method provides the angle constraints implied by a specific modifier combination, i.e. " + "'Shift' will render ortho snapping. Use this function to generate angle constraints following " + "the established conventions.\n" + "\n" + "This method has been added in version 0.30.4." + ) + method ("snap", &gsi::PluginBase::snap, gsi::arg ("p"), "@brief Snaps a point to the edit grid\n" "\n" @@ -1421,6 +1525,10 @@ Class decl_Plugin ("lay", "Plugin", "This method behaves like the other \"snap2\" variant, but does not allow to specify an\n" "angle constraint. Only grid constraints and snapping to objects is supported.\n" "\n" + "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " + "provide a visualization of the edges or vertexes that the point is snapping to. If you use this feature, " + "make sure you call \\clear_mouse_cursors before to remove existing cursors.\n" + "\n" "This method has been added in version 0.30.4." ) + method ("snap2", &gsi::PluginBase::snap2_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), gsi::arg ("visualize", false), @@ -1442,6 +1550,10 @@ Class decl_Plugin ("lay", "Plugin", "This method considers options like global or editing grid or whether the target point\n" "will snap to another object. The behavior is given by the respective configuration.\n" "\n" + "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " + "provide a visualization of the edges or vertexes that the point is snapping to. If you use this feature, " + "make sure you call \\clear_mouse_cursors before to remove existing cursors.\n" + "\n" "This method has been added in version 0.30.4." ) + #if defined(HAVE_QTBINDINGS) From 7d2113ffe1289b9d98f33e544d4ce94da8010128 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 00:34:35 +0200 Subject: [PATCH 09/52] Some debugging, tests added --- src/edt/edt/edtUtils.h | 14 +- src/lay/lay/gsiDeclLayPlugin.cc | 47 +++++- src/laybasic/laybasic/layEditorServiceBase.h | 8 +- src/laybasic/laybasic/layLayoutViewBase.cc | 19 ++- src/rba/unit_tests/rbaTests.cc | 1 + testdata/ruby/layPluginTests.rb | 145 +++++++++++++++++++ 6 files changed, 215 insertions(+), 19 deletions(-) create mode 100644 testdata/ruby/layPluginTests.rb diff --git a/src/edt/edt/edtUtils.h b/src/edt/edt/edtUtils.h index a978246c6..ba5cab1a6 100644 --- a/src/edt/edt/edtUtils.h +++ b/src/edt/edt/edtUtils.h @@ -29,6 +29,8 @@ #include #include +#include "edtCommon.h" + #include "layObjectInstPath.h" #include "laySnap.h" @@ -54,17 +56,17 @@ class Service; * This implements the standard modifiers for angle constraints - i.e. * ortho for "Shift". */ -extern lay::angle_constraint_type ac_from_buttons (unsigned int buttons); +EDT_PUBLIC lay::angle_constraint_type ac_from_buttons (unsigned int buttons); /** * @brief Serializes PCell parameters to a string */ -std::string pcell_parameters_to_string (const std::map ¶meters); +EDT_PUBLIC std::string pcell_parameters_to_string (const std::map ¶meters); /** * @brief Deserializes PCell parameters from a string */ -std::map pcell_parameters_from_string (const std::string &s); +EDT_PUBLIC std::map pcell_parameters_from_string (const std::string &s); /** * @brief Fetch PCell parameters from a cell and merge the guiding shapes into them @@ -74,13 +76,13 @@ std::map pcell_parameters_from_string (const std::stri * @param parameters_for_pcell Will receive the parameters * @return true, if the cell is a PCell and parameters have been fetched */ -bool +EDT_PUBLIC bool get_parameters_from_pcell_and_guiding_shapes (db::Layout *layout, db::cell_index_type cell_index, db::pcell_parameters_type ¶meters_for_pcell); /** * @brief A helper class that identifies clipboard data for edt:: */ -class ClipboardData +class EDT_PUBLIC ClipboardData : public db::ClipboardData { public: @@ -90,7 +92,7 @@ public: /** * @brief A cache for the transformation variants for a certain layer and cell view index for a lay::LayoutView */ -class TransformationVariants +class EDT_PUBLIC TransformationVariants { public: TransformationVariants (const lay::LayoutViewBase *view, bool per_cv_and_layer = true, bool per_cv = true); diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index d5b17bb58..d0847ebb6 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -404,6 +404,12 @@ public: return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); } + // for testing + void configure_test (const std::string &name, const std::string &value) + { + configure_edt (name, value); + } + virtual bool configure (const std::string &name, const std::string &value) { configure_edt (name, value); @@ -633,21 +639,43 @@ public: lay::EditorServiceBase::add_edge_marker (p, cv_index, cv.context_trans (), *tv_list, emphasize); } + // for testing + bool has_tracking_position_test () const + { + return has_tracking_position (); + } + + bool has_tracking_position_base () const + { + return lay::EditorServiceBase::has_tracking_position (); + } + virtual bool has_tracking_position () const { if (f_has_tracking_position.can_issue ()) { return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); } else { - return lay::EditorServiceBase::has_tracking_position (); + return has_tracking_position_base (); } } + // for testing + db::DPoint tracking_position_test () const + { + return tracking_position (); + } + + db::DPoint tracking_position_base () const + { + return lay::EditorServiceBase::tracking_position (); + } + virtual db::DPoint tracking_position () const { if (f_tracking_position.can_issue ()) { return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); } else { - return lay::EditorServiceBase::tracking_position (); + return tracking_position_base (); } } @@ -662,13 +690,17 @@ public: } #if defined(HAVE_QTBINDINGS) - std::vector editor_options_pages () + std::vector editor_options_pages () { lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); if (!eo_pages) { - return std::vector (); + return std::vector (); } else { - return eo_pages->pages (); + std::vector pages; + for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { + pages.push_back (*p); + } + return pages; } } #endif @@ -1281,6 +1313,7 @@ Class decl_Plugin ("lay", "Plugin", "When a menu item is clicked which was registered with the plugin factory, the plugin's 'menu_activated' method is " "called for the current view. The symbol registered for the menu item is passed in the 'symbol' argument." ) + + method ("configure_test", &gsi::PluginBase::configure_test, gsi::arg ("name"), gsi::arg ("value"), "@hide") + callback ("configure", &gsi::PluginBase::configure_impl, &gsi::PluginBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), "@brief Sends configuration requests to the plugin\n" "@param name The name of the configuration variable as registered in the plugin factory\n" @@ -1384,6 +1417,8 @@ Class decl_Plugin ("lay", "Plugin", "\n" "The cursor type is one of the cursor constants in the \\Cursor class, i.e. 'CursorArrow' for the normal cursor." ) + + method ("has_tracking_position_test", &gsi::PluginBase::has_tracking_position_test, "@hide") + + method ("has_tracking_position", &gsi::PluginBase::has_tracking_position_base, "@hide") + callback ("has_tracking_position", &gsi::PluginBase::has_tracking_position, &gsi::PluginBase::f_has_tracking_position, "@brief Gets a value indicating whether the plugin provides a tracking position\n" "The tracking position is shown in the lower-left corner of the layout window to indicate the current position.\n" @@ -1392,6 +1427,8 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + + method ("tracking_position_test", &gsi::PluginBase::tracking_position_test, "@hide") + + method ("tracking_position", &gsi::PluginBase::tracking_position_base, "@hide") + callback ("tracking_position", &gsi::PluginBase::tracking_position, &gsi::PluginBase::f_tracking_position, "@brief Gets the tracking position\n" "See \\has_tracking_position for details.\n" diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index d17c77255..f41afbf85 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -141,8 +141,14 @@ public: */ void show_error (tl::Exception &ex); -protected: + /** + * @brief Sets a configuration option + */ virtual bool configure (const std::string &name, const std::string &value); + + /** + * @brief Called when the plugin is deactivated + */ virtual void deactivated (); private: diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index a11ddf17a..960e1df97 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -602,20 +602,25 @@ void LayoutViewBase::create_plugins (const lay::PluginDeclaration *except_this) clear_plugins (); // create the plugins - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ) { - if (&*cls != except_this) { + // NOTE: during "create_plugin" a plugin may be unregistered, so don't increment the iterator after + auto current = cls.operator-> (); + std::string current_name = cls.current_name (); + ++cls; + + if (current != except_this) { // TODO: clean solution. The following is a HACK: - if (cls.current_name () == "ant::Plugin" || cls.current_name () == "img::Plugin") { + if (current_name == "ant::Plugin" || current_name == "img::Plugin") { // ant and img are created always - create_plugin (&*cls); + create_plugin (current); } else if ((options () & LV_NoPlugins) == 0) { // others: only create unless LV_NoPlugins is set - create_plugin (&*cls); - } else if ((options () & LV_NoGrid) == 0 && cls.current_name () == "GridNetPlugin") { + create_plugin (current); + } else if ((options () & LV_NoGrid) == 0 && current_name == "GridNetPlugin") { // except grid net plugin which is created on request - create_plugin (&*cls); + create_plugin (current); } } diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index e2ff18d3a..aa4aedb71 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -152,6 +152,7 @@ RUBYTEST (layMainWindow, "layMainWindow.rb") RUBYTEST (layMarkers, "layMarkers.rb") RUBYTEST (layMacro, "layMacro.rb") RUBYTEST (layMenuTest, "layMenuTest.rb") +RUBYTEST (layPluginTests, "layPluginTests.rb") RUBYTEST (layPixelBuffer, "layPixelBuffer.rb") RUBYTEST (laySession, "laySession.rb") RUBYTEST (laySaveLayoutOptions, "laySaveLayoutOptions.rb") diff --git a/testdata/ruby/layPluginTests.rb b/testdata/ruby/layPluginTests.rb new file mode 100644 index 000000000..cdd5638b6 --- /dev/null +++ b/testdata/ruby/layPluginTests.rb @@ -0,0 +1,145 @@ +# encoding: UTF-8 + +# KLayout Layout Viewer +# Copyright (C) 2006-2025 Matthias Koefferlein +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +class PluginFactory < RBA::PluginFactory + def initialize() + register(1000, "plugin_for_test", "Plugin") + @pi = nil + end + def create_plugin(manager, unused, view) + @pi = RBA::Plugin::new + @pi + end + def pi + @pi + end +end + +class Plugin2 < RBA::Plugin + def set_tp(tp) + @tp = tp + end + def has_tracking_position + !!@tp + end + def tracking_position + @tp || RBA::DPoint::new + end +end + +class PluginFactory2 < RBA::PluginFactory + def initialize() + register(1001, "plugin_for_test2", "Plugin2") + @pi = nil + end + def create_plugin(manager, unused, view) + @pi = Plugin2::new + @pi + end + def pi + @pi + end +end + +class LayPlugin_TestClass < TestBase + + def test_1 + + assert_equal(RBA::Plugin::AC_Global.to_s, "AC_Global") + assert_equal(RBA::Plugin::AC_Any.to_s, "AC_Any") + assert_equal(RBA::Plugin::AC_Diagonal.to_s, "AC_Diagonal") + assert_equal(RBA::Plugin::AC_Horizontal.to_s, "AC_Horizontal") + assert_equal(RBA::Plugin::AC_Vertical.to_s, "AC_Vertical") + + assert_equal(RBA::Plugin::ac_from_buttons(0), RBA::Plugin::AC_Global) + assert_equal(RBA::Plugin::ac_from_buttons(1), RBA::Plugin::AC_Ortho) + assert_equal(RBA::Plugin::ac_from_buttons(2), RBA::Plugin::AC_Diagonal) + + begin + + dpi = PluginFactory::new + dpi2 = PluginFactory2::new + + # Create a new layout + main_window = RBA::MainWindow.instance() + main_window.close_all + main_window.create_layout(2) + + pi = dpi.pi + pi2 = dpi2.pi + + # smoke test + pi.grab_mouse + pi.ungrab_mouse + pi.set_cursor(RBA::Cursor::Wait) + pi.add_edge_marker(RBA::DEdge::new) + pi.add_mouse_cursor(RBA::DPoint::new) + pi.clear_mouse_cursors + + # virtual methods + assert_equal(pi.has_tracking_position_test, false) + pi.clear_mouse_cursors + pi.add_mouse_cursor(RBA::DPoint::new(1, 2)) + assert_equal(pi.has_tracking_position_test, true) + assert_equal(pi.tracking_position_test.to_s, "1,2") + pi.clear_mouse_cursors + assert_equal(pi.has_tracking_position_test, false) + + assert_equal(pi2.has_tracking_position_test, false) + pi2.set_tp(RBA::DPoint::new(2, 3)) + assert_equal(pi2.has_tracking_position_test, true) + assert_equal(pi2.tracking_position_test.to_s, "2,3") + pi2.set_tp(nil) + assert_equal(pi2.has_tracking_position_test, false) + + pi.configure_test("edit-grid", "0.0") + assert_equal(pi.snap(RBA::DPoint::new(0.01, 0.02)).to_s, "0.01,0.02") + assert_equal(pi.snap(RBA::DVector::new(0.01, 0.02)).to_s, "0.01,0.02") + pi.configure_test("edit-grid", "0.1") + assert_equal(pi.snap(RBA::DPoint::new(0.11, 0.18)).to_s, "0.1,0.2") + assert_equal(pi.snap(RBA::DVector::new(0.11, 0.18)).to_s, "0.1,0.2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), true).to_s, "1.5,2") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false).to_s, "1.5,2.1") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false, RBA::Plugin::AC_Ortho).to_s, "1.5,2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), true).to_s, "0,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false).to_s, "0.5,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false, RBA::Plugin::AC_Ortho).to_s, "0,2.1") + + ensure + main_window.close_all + dpi._destroy + dpi2._destroy + end + + end + +end + +load("test_epilogue.rb") + From b133bde1c76b6da4f05fd19deb7e822dce3dc321 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 13:24:30 +0200 Subject: [PATCH 10/52] WIP: doc updates --- src/lay/lay/gsiDeclLayPlugin.cc | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index d0847ebb6..458e6954f 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -1194,7 +1194,7 @@ Class decl_PluginFactory ("lay", "PluginFactory", ) + callback ("create_editor_options_pages", &PluginFactoryBase::get_editor_options_pages_impl, &PluginFactoryBase::f_get_editor_options_pages, "@brief Creates the editor option pages\n" - "The editor option pages are widgets of type \\EditorOptionsPage. These QFrame-type widgets " + "The editor option pages are widgets of type \\EditorOptionsPage. These Qt widgets " "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " "its mode is selected. Use this method to provide customized pages that will be displayed in the " "editor options dock.\n" @@ -1378,6 +1378,9 @@ Class decl_Plugin ("lay", "Plugin", callback ("mouse_moved_event", &gsi::PluginBase::mouse_move_event_noref, &gsi::PluginBase::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse move event\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse is moved in the canvas area.\n" + "\n" + "The mouse move event is important for a number of background jobs, such as coordinate display in the status bar.\n" + "Hence, you should not consume the event - i.e. you should return 'false' from this method.\n" ) + callback ("mouse_button_released_event", &gsi::PluginBase::mouse_release_event_noref, &gsi::PluginBase::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button release event\n" @@ -1412,8 +1415,8 @@ Class decl_Plugin ("lay", "Plugin", ) + method ("set_cursor", &gsi::PluginBase::set_cursor, gsi::arg ("cursor_type"), "@brief Sets the cursor in the view area to the given type\n" - "Setting the cursor has an effect only inside event handlers, i.e. mouse_press_event. The cursor is not set permanently. Is is reset " - "in the mouse move handler unless a button is pressed or the cursor is explicitly set again in the mouse_move_event.\n" + "Setting the cursor has an effect only inside event handlers, i.e. \\mouse_button_pressed_event. The cursor is not set permanently. Is is reset " + "in the mouse move handler unless a button is pressed or the cursor is explicitly set again in \\mouse_moved_event.\n" "\n" "The cursor type is one of the cursor constants in the \\Cursor class, i.e. 'CursorArrow' for the normal cursor." ) + @@ -1425,6 +1428,10 @@ Class decl_Plugin ("lay", "Plugin", "If this method returns true for the active service, the application will fetch the position by calling \\tracking_position " "rather than displaying the original mouse position.\n" "\n" + "The default implementation enables tracking if a mouse cursor has been set using \\add_mouse_cursor.\n" + "When enabling tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" "This method has been added in version 0.27.6." ) + method ("tracking_position_test", &gsi::PluginBase::tracking_position_test, "@hide") + @@ -1433,6 +1440,11 @@ Class decl_Plugin ("lay", "Plugin", "@brief Gets the tracking position\n" "See \\has_tracking_position for details.\n" "\n" + "The default implementation takes the tracking position from a mouse cursor, if you have created one using " + "\\add_mouse_cursor.\n" + "When enabling tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" "This method has been added in version 0.27.6." ) + method ("clear_mouse_cursors", &gsi::PluginBase::clear_mouse_cursors, @@ -1444,10 +1456,15 @@ Class decl_Plugin ("lay", "Plugin", ) + method ("add_mouse_cursor", &gsi::PluginBase::add_mouse_cursor_dpoint, gsi::arg ("p"), gsi::arg ("emphasize", false), "@brief Creates a cursor to indicate the mouse position\n" - "This function will create a marker that indicates the (for example snapped) mouse position. " - "In addition to this, it will establish the position for the tracking cursor, if mouse " - "tracking is enabled in the application. Multiple cursors can be created. In that case, the " - "tracking position is given by the last cursor.\n" + "This function will create a marker that indicates the (for example snapped) mouse position.\n" + "In addition to this, it will establish the position for the tracking cursor, if mouse\n" + "tracking is enabled in the application. You can override the tracking position by reimplementing\n" + "\\tracking_position and \\has_tracking_position.\n" + "\n" + "To enable tracking, make sure a reimplementation of \\mouse_moved_event does not consume the\n" + "event and returns 'false'.\n" + "\n" + "Multiple cursors can be created. In that case, the tracking position is given by the last cursor.\n" "\n" "If 'emphasize' is true, the cursor is displayed in a 'stronger' style - i.e. with a double circle instead of a single one.\n" "\n" From e396b6ec29e58a636cb0ca70a0e398013afcf8e7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 13:35:01 +0200 Subject: [PATCH 11/52] WIP: bug fixes and more tests --- src/lay/lay/gsiDeclLayPlugin.cc | 4 +++- testdata/ruby/layPluginTests.rb | 35 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 458e6954f..5ac999e8a 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -698,7 +698,9 @@ public: } else { std::vector pages; for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { - pages.push_back (*p); + if ((*p)->plugin_declaration () == plugin_declaration ()) { + pages.push_back (*p); + } } return pages; } diff --git a/testdata/ruby/layPluginTests.rb b/testdata/ruby/layPluginTests.rb index cdd5638b6..41b91c053 100644 --- a/testdata/ruby/layPluginTests.rb +++ b/testdata/ruby/layPluginTests.rb @@ -37,6 +37,18 @@ class PluginFactory < RBA::PluginFactory end end +class Plugin2EditorOptionsPage < RBA::EditorOptionsPage + def initialize + super("title", 1) + end +end + +class Plugin2ConfigPage < RBA::ConfigPage + def initialize + super("title") + end +end + class Plugin2 < RBA::Plugin def set_tp(tp) @tp = tp @@ -51,16 +63,32 @@ end class PluginFactory2 < RBA::PluginFactory def initialize() - register(1001, "plugin_for_test2", "Plugin2") + @ep = 0 + @cp = 0 @pi = nil + register(1001, "plugin_for_test2", "Plugin2") end def create_plugin(manager, unused, view) @pi = Plugin2::new @pi end + def create_editor_options_pages + add_editor_options_page(Plugin2EditorOptionsPage::new) + @ep += 1 + end + def create_config_pages + add_config_page(Plugin2ConfigPage::new) + @cp += 1 + end def pi @pi end + def ep + @ep + end + def cp + @cp + end end class LayPlugin_TestClass < TestBase @@ -114,6 +142,11 @@ class LayPlugin_TestClass < TestBase pi2.set_tp(nil) assert_equal(pi2.has_tracking_position_test, false) + assert_equal(dpi2.ep, 1) + assert_equal(dpi2.cp, 1) + assert_equal(pi2.editor_options_pages.size, 1) + assert_equal(pi2.editor_options_pages[0].class.to_s, "Plugin2EditorOptionsPage") + pi.configure_test("edit-grid", "0.0") assert_equal(pi.snap(RBA::DPoint::new(0.01, 0.02)).to_s, "0.01,0.02") assert_equal(pi.snap(RBA::DVector::new(0.01, 0.02)).to_s, "0.01,0.02") From f1c16a02426b8e9507b901bb57d46f7e9e53491d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 14:53:46 +0200 Subject: [PATCH 12/52] Adding samples for new plugin features, doc update. --- src/doc/doc/programming/application_api.xml | 30 +- src/lay/lay/layMacroTemplates.qrc | 2 + .../lay/macro_templates/drag_box_sample.lym | 258 ++++++++++++++++++ .../drag_box_sample_python.lym | 222 +++++++++++++++ src/lay/lay/macro_templates/index.txt | 2 + 5 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 src/lay/lay/macro_templates/drag_box_sample.lym create mode 100644 src/lay/lay/macro_templates/drag_box_sample_python.lym diff --git a/src/doc/doc/programming/application_api.xml b/src/doc/doc/programming/application_api.xml index 836178481..d4fcf1ebe 100644 --- a/src/doc/doc/programming/application_api.xml +++ b/src/doc/doc/programming/application_api.xml @@ -976,7 +976,7 @@ marker.destroy

The PluginFactory itself acts as a singleton per plugin class and provides not only the ability to create Plugin objects but also a couple of configuration options and a global handler for configuration and menu - events. The configuration includes: + events. The PluginFactory provides:

    @@ -991,6 +991,10 @@ marker.destroy After an option is configured, the individual Plugin objects and the PluginFactory receives "configure" calls when a configuration option changes or for the initial configuration. +
  • Widgets: The plugin factory can provide widgets for the configuration dialog ('File/Setup') and the + editor options dock. Respective callbacks are + and . +

@@ -1048,5 +1052,29 @@ marker.destroy over the mouse in certain circumstances and is supposed to put the plugin into a "watching" instead of "dragging" state.

+

+ A plugin may also create markers for visual feedback and highlights. This can be done explicitly + using marker objects () or in a application-defined fashion by generating + mouse cursors. The API functions for this purpose are , + and . These + functions provide cursors and highlights that match the visual effects of other plugins and + interface with the mouse tracking feature of the application. +

+ +

+ Another service the Plugin class provides is snapping: + there exist a number of global configuration options which control snapping (grids, snapping to + objects, angle constraints). The plugin offers a number of snap functions that follow the + application's current configuration and implement snapping accordingly. These methods are + and . While the first + method provides grid and angle snapping, the second also implements snapping to layout objects. +

+ +

+ The "drag box" sample macro demonstrates many of these features. + The sample is available from the macro templates when you create a new + macro in the macro IDE. +

+ diff --git a/src/lay/lay/layMacroTemplates.qrc b/src/lay/lay/layMacroTemplates.qrc index 79bdbc8ad..cae26b9ed 100644 --- a/src/lay/lay/layMacroTemplates.qrc +++ b/src/lay/lay/layMacroTemplates.qrc @@ -5,6 +5,8 @@ macro_templates/new_macro.lym macro_templates/new_text_file.txt macro_templates/new_ruby_file.rb + macro_templates/drag_box_sample.lym + macro_templates/drag_box_sample_python.lym macro_templates/pcell.lym macro_templates/pcell_sample.lym macro_templates/qt_designer.lym diff --git a/src/lay/lay/macro_templates/drag_box_sample.lym b/src/lay/lay/macro_templates/drag_box_sample.lym new file mode 100644 index 000000000..7d29c2be1 --- /dev/null +++ b/src/lay/lay/macro_templates/drag_box_sample.lym @@ -0,0 +1,258 @@ + + + A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping + general + true + false + false + + ruby + # Sample plugin +# +# This plugin implements a box that can be drawn by +# clicking at the first and then at the second point. +# There is one box which is replacing the previous one. +# Line color and line width of the box can be configured +# by editor options (line width) or configuration pages +# (color). + +module DragBox + +# Register this macro as "autorun" to enable the plugin + +CFG_COLOR = "drag-box-color" +CFG_WIDTH = "drag-box-width" + +# The widget placed into the editor options dock + +class DragBoxEditorOptionsPage < RBA::EditorOptionsPage + + def initialize + + # Creates a new page with title "Options" and at + # position 1 (second from left) + super("Options", 1) + + layout2 = RBA::QVBoxLayout::new(self) + layout = RBA::QHBoxLayout::new(self) + layout2.addLayout(layout) + label = RBA::QLabel::new("Line width", self) + layout.addWidget(label) + @spin_box = RBA::QSpinBox::new(self) + @spin_box.setMinimum(1) + @spin_box.setMaximum(16) + layout.addWidget(@spin_box) + layout.addStretch(1) + layout2.addStretch(1) + @spin_box.valueChanged = lambda { |x| self.edited } + + end + + def setup(dispatcher) + begin + @spin_box.setValue(dispatcher.get_config(CFG_WIDTH).to_i) + rescue + @spin_box.setValue(1) + end + end + + def apply(dispatcher) + dispatcher.set_config(CFG_WIDTH, @spin_box.value.to_s) + end + +end + +# The widget placed into the configuration page + +class DragBoxConfigPage < RBA::ConfigPage + + def initialize + + # places the widget on a new section ("Drag Box") + # and "Configure" page + super("Drag Box|Configure") + + # Qt user interfact setup + layout = RBA::QHBoxLayout::new(self) + label = RBA::QLabel::new("Color (hex, rrggbb)", self) + layout.addWidget(label) + @line_edit = RBA::QLineEdit::new(self) + layout.addWidget(@line_edit) + layout.addStretch(1) + + end + + def setup(dispatcher) + @line_edit.setText(dispatcher.get_config(CFG_COLOR)) + end + + def apply(dispatcher) + dispatcher.set_config(CFG_COLOR, @line_edit.text) + end + +end + +class DragBoxPlugin < RBA::Plugin + + def initialize(view) + super() + @marker = nil + @last_box = nil + @box = nil + @start_point = nil + @view = view + @color = nil + @width = 1 + end + + def configure(name, value) + # receives configuration callbacks + if name == CFG_COLOR + # configure marker color + begin + @color = value != "" ? value.to_i(16) : nil + rescue + self.color = nil + end + self._configure_marker + elsif name == CFG_WIDTH + # configure marker line width + begin + @width = value.to_i + rescue + @width = nil + end + self._configure_marker + end + end + + def _clear_marker + # clears all markers + [ @marker, @last_box ].each { |m| m && m._destroy } + @marker = nil + @last_box = nil + end + + def _update_marker + # updates the marker with the current box + if !@marker + @marker = RBA::Marker::new(self.view) + self._configure_marker + end + @marker.set(@box) + end + + def freeze_marker + # stop dragging the marker and copy to a static one + if @last_box + @last_box._destroy() + end + @last_box = @marker + @marker = nil + end + + def _configure_marker + # change the marker's appearance + if @marker + @marker.line_style = 2 # short-dashed + @marker.vertex_size = 0 # no vertexes + @marker.line_width = @width + @marker.color = @color ? (@color | 0xff000000) : 0 + end + end + + def activated + # plugin is activated - i.e. the mode is selected + RBA::MainWindow.instance.message("Click on point to start dragging a box", 10000) + end + + def deactivated + # plugin is deactivated - i.e. the mode is unselected + self._clear_marker + RBA::MainWindow.instance.message("", 0) + end + + def mouse_click_event(p, buttons, prio) + if prio + # first-level event: start a new box or + # stop dragging it and freeze the box + if !@marker + p = self.snap2(p) + @box = RBA::DBox::new(p, p) + @start_point = p + self._clear_marker + self._update_marker + self.grab_mouse + RBA::MainWindow.instance.message("Drag the box and click again", 10000) + else + p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons)) + self.freeze_marker + self.ungrab_mouse + RBA::MainWindow.instance.message("Box finished: " + @box.to_s, 10000) + end + # consume event + return true + else + return false + end + end + + def mouse_moved_event(p, buttons, prio) + if prio + # first-level event: if not dragging, provide a + # mouse cursor for tracking. If dragging, update + # the box and provide a mouse cursor. + if !@marker + self.clear_mouse_cursors + p = self.snap2(p, true) + self.add_mouse_cursor(p) + else + self.clear_mouse_cursors + p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons), true) + self.add_mouse_cursor(p) + @box = RBA::DBox::new(@start_point, p) + self._update_marker + end + end + # NOTE: we must not digest this event (i.e. return true) + # to allow the mouse tracker to receive the events as well + return false + end + +end + +# Implements a "plugin factory". +# Purpose of this object is to create a plugin object +# and corresponding UI objects. + +class DragBoxPluginFactory < RBA::PluginFactory + + def initialize + super() + self.has_tool_entry = true + # NOTE: it's a good practice to register configuration options + self.add_option(CFG_WIDTH, "1") + self.add_option(CFG_COLOR, "") + self.register(-1000, "drag_box", "Drag Box") + end + + def create_config_pages + self.add_config_page(DragBoxConfigPage::new) + end + + def create_editor_options_pages + self.add_editor_options_page(DragBoxEditorOptionsPage::new) + end + + def create_plugin(manager, root, view) + return DragBoxPlugin::new(view) + end + +end + +# Create the singleton instance - as we register it, +# it is not garbage collected +DragBoxPluginFactory::new + +end + diff --git a/src/lay/lay/macro_templates/drag_box_sample_python.lym b/src/lay/lay/macro_templates/drag_box_sample_python.lym new file mode 100644 index 000000000..17d2b4a7b --- /dev/null +++ b/src/lay/lay/macro_templates/drag_box_sample_python.lym @@ -0,0 +1,222 @@ + + + A plugin sample\nThis sample provides a box drawing feature and demonstrates UI components and snapping + general + true + false + false + + python + # Sample plugin +# +# This plugin implements a box that can be drawn by +# clicking at the first and then at the second point. +# There is one box which is replacing the previous one. +# Line color and line width of the box can be configured +# by editor options (line width) or configuration pages +# (color). + +# Register this macro as "autorun" to enable the plugin + +cfg_color = "drag-box-color" +cfg_width = "drag-box-width" + +# The widget placed into the editor options dock + +class DragBoxEditorOptionsPage(pya.EditorOptionsPage): + + def __init__(self): + + # Creates a new page with title "Options" and at + # position 1 (second from left) + super(DragBoxEditorOptionsPage, self).__init__("Options", 1) + + layout2 = pya.QVBoxLayout(self) + layout = pya.QHBoxLayout(self) + layout2.addLayout(layout) + label = pya.QLabel("Line width", self) + layout.addWidget(label) + self.spin_box = pya.QSpinBox(self) + self.spin_box.setMinimum(1) + self.spin_box.setMaximum(16) + layout.addWidget(self.spin_box) + layout.addStretch(1) + layout2.addStretch(1) + + self.spin_box.valueChanged = lambda x: self.edited() + + def setup(self, dispatcher): + try: + self.spin_box.setValue(int(dispatcher.get_config(cfg_width))) + except: + self.spin_box.setValue(1) + + def apply(self, dispatcher): + dispatcher.set_config(cfg_width, str(self.spin_box.value)) + +# The widget placed into the configuration page + +class DragBoxConfigPage(pya.ConfigPage): + + def __init__(self): + + # places the widget on a new section ("Drag Box") + # and "Configure" page + super(DragBoxConfigPage, self).__init__("Drag Box|Configure") + + layout = pya.QHBoxLayout(self) + label = pya.QLabel("Color (hex, rrggbb)", self) + layout.addWidget(label) + self.line_edit = pya.QLineEdit(self) + layout.addWidget(self.line_edit) + layout.addStretch(1) + + def setup(self, dispatcher): + self.line_edit.setText(dispatcher.get_config(cfg_color)) + + def apply(self, dispatcher): + dispatcher.set_config(cfg_color, self.line_edit.text) + +class DragBoxPlugin(pya.Plugin): + + def __init__(self, view): + super(DragBoxPlugin, self).__init__() + self.marker = None + self.last_box = None + self.box = None + self.start_point = None + self.view = view + self.color = None + self.width = 1 + + def configure(self, name, value): + # receives configuration callbacks + needs_update = False + if name == cfg_color: + # configure marker color + try: + if value != "": + self.color = int(value, 16) + else: + self.color = None + except: + self.color = None + self._configure_marker() + elif name == cfg_width: + # configure marker line width + try: + self.width = int(value) + except: + self.width = None + self._configure_marker() + + def _clear_marker(self): + # clears all markers + for marker in [ self.marker, self.last_box ]: + if marker is not None: + marker._destroy() + self.marker = None + self.last_box = None + + def _update_marker(self): + # updates the marker with the current box + if self.marker is None: + self.marker = pya.Marker(self.view) + self._configure_marker() + self.marker.set(self.box) + + def freeze_marker(self): + # stop dragging the marker and copy to a static one + if self.last_box is not None: + self.last_box._destroy() + self.last_box = self.marker + self.marker = None + + def _configure_marker(self): + # change the marker's appearance + if self.marker is not None: + self.marker.line_style = 2 # short-dashed + self.marker.vertex_size = 0 # no vertexes + self.marker.line_width = self.width + if self.color is not None: + self.marker.color = self.color | 0xff000000 + else: + self.marker.color = 0 # auto + + def activated(self): + # plugin is activated - i.e. the mode is selected + pya.MainWindow.instance().message("Click on point to start dragging a box", 10000) + + def deactivated(self): + # plugin is deactivated - i.e. the mode is unselected + self._clear_marker() + pya.MainWindow.instance().message("", 0) + + def mouse_click_event(self, p, buttons, prio): + if prio: + # first-level event: start a new box or + # stop dragging it and freeze the box + if self.marker is None: + p = self.snap2(p) + self.box = pya.DBox(p, p) + self.start_point = p + self._clear_marker() + self._update_marker() + self.grab_mouse() + pya.MainWindow.instance().message("Drag the box and click again", 10000) + else: + p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons)) + self.freeze_marker() + self.ungrab_mouse() + pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000) + return True + return False + + def mouse_moved_event(self, p, buttons, prio): + if prio: + # first-level event: if not dragging, provide a + # mouse cursor for tracking. If dragging, update + # the box and provide a mouse cursor. + if self.marker is None: + self.clear_mouse_cursors() + p = self.snap2(p, visualize=True) + self.add_mouse_cursor(p) + else: + self.clear_mouse_cursors() + p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons), visualize=True) + self.add_mouse_cursor(p) + self.box = pya.DBox(self.start_point, p) + self._update_marker() + # NOTE: we must not digest this event (i.e. return True) + # to allow the mouse tracker to receive the events as well + return False + + +# Implements a "plugin factory". +# Purpose of this object is to create a plugin object +# and corresponding UI objects. + +class DragBoxPluginFactory(pya.PluginFactory): + + def __init__(self): + super(DragBoxPluginFactory, self).__init__() + self.has_tool_entry = True + # NOTE: it's a good practice to register configuration options + self.add_option(cfg_width, "1") + self.add_option(cfg_color, "") + self.register(-1000, "drag_box", "Drag Box") + + def create_config_pages(self): + self.add_config_page(DragBoxConfigPage()) + + def create_editor_options_pages(self): + self.add_editor_options_page(DragBoxEditorOptionsPage()) + + def create_plugin(self, manager, root, view): + return DragBoxPlugin(view) + +# Create the singleton instance - as we register it, +# it is not garbage collected +DragBoxPluginFactory() + + diff --git a/src/lay/lay/macro_templates/index.txt b/src/lay/lay/macro_templates/index.txt index a446546d7..ae40bdd3d 100644 --- a/src/lay/lay/macro_templates/index.txt +++ b/src/lay/lay/macro_templates/index.txt @@ -23,6 +23,7 @@ pcell_sample.lym qt_designer.lym qt_dialog.lym qt_server.lym +drag_box_sample.lym [pymacros] # General group @@ -41,4 +42,5 @@ pcell_sample_python.lym qt_designer_python.lym qt_dialog_python.lym qt_server_python.lym +drag_box_sample_python.lym From 340c1ef6e9e08ec0669c181efbf80524a5af1777 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 18:39:41 +0200 Subject: [PATCH 13/52] Some refactoring, editor options are shown more consistently now Specifically, in move mode, now the editor options are shown too. This makes sense as some of these options there are also effective in move mode. --- src/lay/lay/layMainWindow.cc | 49 ++++++++++-------- src/lay/lay/layMainWindow.h | 1 + .../laybasic/layEditorOptionsPages.cc | 18 +++++-- src/laybasic/laybasic/layEditorOptionsPages.h | 1 + src/laybasic/laybasic/layLayoutViewBase.cc | 51 ++++++++----------- src/laybasic/laybasic/layMouseTracker.cc | 30 ++++++++++- src/laybasic/laybasic/layMouseTracker.h | 3 +- src/laybasic/laybasic/layMove.cc | 22 +++++++- src/laybasic/laybasic/layMove.h | 5 +- src/laybasic/laybasic/layPlugin.cc | 6 +++ src/laybasic/laybasic/layPlugin.h | 17 +++++++ src/laybasic/laybasic/laySelector.cc | 26 ++++++++++ src/laybasic/laybasic/laySelector.h | 4 +- src/laybasic/laybasic/layZoomBox.cc | 27 +++++++++- src/laybasic/laybasic/layZoomBox.h | 3 +- src/layview/layview/layGridNet.cc | 2 +- src/layview/layview/layLayoutView_qt.cc | 13 +---- src/layview/unit_tests/layLayoutViewTests.cc | 36 +++++++++++++ 18 files changed, 238 insertions(+), 76 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 11f9d9f55..72430e410 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -1664,32 +1664,38 @@ MainWindow::select_mode (int m) } } - // if the current mode supports editing, show the editor options panel + update_editor_options_dock (); - const lay::PluginDeclaration *pd_sel = 0; - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { - const lay::PluginDeclaration *pd = cls.operator-> (); - if (pd->id () == m_mode) { - pd_sel = pd; - } - } + } +} - bool eo_visible = false; - if (mp_eo_stack && pd_sel) { - eo_visible = pd_sel->editable_enabled (); - } - if (current_view () && eo_visible) { - lay::EditorOptionsPages *eo_pages = current_view ()->editor_options_pages (); - if (! eo_pages || ! eo_pages->has_content ()) { - eo_visible = false; - } - } +void +MainWindow::update_editor_options_dock () +{ + // if the current mode supports editing, show the editor options panel - if (eo_visible != m_eo_visible) { - m_eo_visible = eo_visible; - show_dock_widget (mp_eo_dock_widget, m_eo_visible); + const lay::PluginDeclaration *pd_sel = 0; + for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { + const lay::PluginDeclaration *pd = cls.operator-> (); + if (pd->id () == m_mode) { + pd_sel = pd; } + } + bool eo_visible = false; + if (mp_eo_stack && pd_sel) { + eo_visible = pd_sel->editable_enabled (); + } + if (current_view () && eo_visible) { + lay::EditorOptionsPages *eo_pages = current_view ()->editor_options_pages (); + if (! eo_pages || ! eo_pages->has_content ()) { + eo_visible = false; + } + } + + if (eo_visible != m_eo_visible) { + m_eo_visible = eo_visible; + show_dock_widget (mp_eo_dock_widget, m_eo_visible); } } @@ -2439,6 +2445,7 @@ MainWindow::select_view (int index) current_view_changed (); + update_editor_options_dock (); clear_current_pos (); edits_enabled_changed (); clear_message (); diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index 1f6fc2f69..56c3dd597 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -857,6 +857,7 @@ private: void interactive_close_view (int from, int to, bool invert_range, bool all_cellviews); void call_on_current_view (void (lay::LayoutView::*func) (), const std::string &op_desc); void current_view_changed (); + void update_editor_options_dock (); void update_window_title (); void update_tab_title (int i); void add_view (LayoutViewWidget *view); diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 9daad1b5a..eefe25ee9 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -81,20 +81,32 @@ EditorOptionsPages::focusInEvent (QFocusEvent * /*event*/) // Sends the focus to the current page's last focus owner if (mp_pages->currentWidget () && mp_pages->currentWidget ()->focusWidget ()) { mp_pages->currentWidget ()->focusWidget ()->setFocus (); - } + } } bool EditorOptionsPages::has_content () const { for (std::vector ::const_iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - // NOTE: we ignore unspecific pages because they are always visible and don't contribute specific content - if ((*p)->active () && (*p)->plugin_declaration () != 0) { + if ((*p)->active ()) { return true; } } return false; } +void EditorOptionsPages::activate (const lay::Plugin *plugin) +{ + for (auto op = m_pages.begin (); op != m_pages.end (); ++op) { + bool is_active = false; + if ((*op)->plugin_declaration () == 0) { + is_active = (plugin && plugin->plugin_declaration ()->enable_catchall_editor_options_pages ()); + } else if (plugin && plugin->plugin_declaration () == (*op)->plugin_declaration ()) { + is_active = true; + } + (*op)->activate (is_active); + } +} + void EditorOptionsPages::unregister_page (lay::EditorOptionsPage *page) { diff --git a/src/laybasic/laybasic/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h index 1cd66753c..dd4e1b15c 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -58,6 +58,7 @@ public: void unregister_page (lay::EditorOptionsPage *page); void activate_page (lay::EditorOptionsPage *page); + void activate (const lay::Plugin *plugin); void focusInEvent (QFocusEvent *event); const std::vector &pages () const diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 960e1df97..7320fae7e 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -398,21 +398,6 @@ LayoutViewBase::init (db::Manager *mgr) mp_canvas = new lay::LayoutCanvas (this); - // occupy services and editables: - // these services get deleted by the canvas destructor automatically: - if ((m_options & LV_NoTracker) == 0) { - mp_tracker = new lay::MouseTracker (this); - } - if ((m_options & LV_NoZoom) == 0) { - mp_zoom_service = new lay::ZoomService (this); - } - if ((m_options & LV_NoSelection) == 0) { - mp_selection_service = new lay::SelectionService (this); - } - if ((m_options & LV_NoMove) == 0) { - mp_move_service = new lay::MoveService (this); - } - create_plugins (); } @@ -615,10 +600,26 @@ void LayoutViewBase::create_plugins (const lay::PluginDeclaration *except_this) if (current_name == "ant::Plugin" || current_name == "img::Plugin") { // ant and img are created always create_plugin (current); + } else if (current_name == "laybasic::MouseTrackerPlugin") { + if ((m_options & LV_NoTracker) == 0) { + mp_tracker = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::MoveServicePlugin") { + if ((m_options & LV_NoMove) == 0) { + mp_move_service = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::SelectionServicePlugin") { + if ((m_options & LV_NoSelection) == 0) { + mp_selection_service = dynamic_cast (create_plugin (current)); + } + } else if (current_name == "laybasic::ZoomServicePlugin") { + if ((m_options & LV_NoZoom) == 0) { + mp_zoom_service = dynamic_cast (create_plugin (current)); + } } else if ((options () & LV_NoPlugins) == 0) { // others: only create unless LV_NoPlugins is set create_plugin (current); - } else if ((options () & LV_NoGrid) == 0 && current_name == "GridNetPlugin") { + } else if ((options () & LV_NoGrid) == 0 && current_name == "lay::GridNetPlugin") { // except grid net plugin which is created on request create_plugin (current); } @@ -5790,20 +5791,12 @@ LayoutViewBase::mode (int m) m_mode = m; mp_active_plugin = 0; - if (m > 0) { - - for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { - if ((*p)->plugin_declaration ()->id () == m) { - mp_active_plugin = *p; - mp_canvas->activate ((*p)->view_service_interface ()); - break; - } + for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { + if ((*p)->plugin_declaration ()->id () == m) { + mp_active_plugin = *p; + mp_canvas->activate ((*p)->view_service_interface ()); + break; } - - } else if (m == 0 && mp_selection_service) { - mp_canvas->activate (mp_selection_service); - } else if (m == -1 && mp_move_service) { - mp_canvas->activate (mp_move_service); } } diff --git a/src/laybasic/laybasic/layMouseTracker.cc b/src/laybasic/laybasic/layMouseTracker.cc index 981c72c62..11c128299 100644 --- a/src/laybasic/laybasic/layMouseTracker.cc +++ b/src/laybasic/laybasic/layMouseTracker.cc @@ -31,7 +31,7 @@ namespace lay { MouseTracker::MouseTracker (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), mp_view (view), + : lay::ViewService (view->canvas ()), lay::Plugin (view), mp_view (view), m_cursor_color (tl::Color ()), m_cursor_line_style (0), m_cursor_enabled (false) { ui ()->grab_mouse (this, false); @@ -129,5 +129,31 @@ MouseTracker::mouse_move_event (const db::DPoint &p, unsigned int /*buttons*/, b return false; } -} // namespace lay +// ---------------------------------------------------------------------------- +// NOTE: configuration currently is not declared here. +// Same for the configuration pages. + +class MouseTrackerDeclaration + : public lay::PluginDeclaration +{ +public: + MouseTrackerDeclaration () + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new MouseTracker (view); + } + + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass tracker_decl (new MouseTrackerDeclaration (), -1000, "laybasic::MouseTrackerPlugin"); + +} // namespace lay diff --git a/src/laybasic/laybasic/layMouseTracker.h b/src/laybasic/laybasic/layMouseTracker.h index 554da9be6..7be70a9aa 100644 --- a/src/laybasic/laybasic/layMouseTracker.h +++ b/src/laybasic/laybasic/layMouseTracker.h @@ -26,6 +26,7 @@ #include "layViewObject.h" #include "layMarker.h" +#include "layPlugin.h" #include "tlObject.h" class QMouseEvent; @@ -36,7 +37,7 @@ class LayoutCanvas; class LayoutViewBase; class MouseTracker - : public lay::ViewService + : public lay::ViewService, public lay::Plugin { public: MouseTracker (lay::LayoutViewBase *view); diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 7675b95c4..35c86188c 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -36,6 +36,7 @@ namespace lay MoveService::MoveService (lay::LayoutViewBase *view) : lay::ViewService (view->canvas ()), + lay::Plugin (view), m_dragging (false), m_dragging_transient (false), mp_editables (view), @@ -366,5 +367,24 @@ MoveService::finish () } } -} +// ---------------------------------------------------------------------------- +class MoveServiceDeclaration + : public lay::PluginDeclaration +{ +public: + MoveServiceDeclaration () + : lay::PluginDeclaration (-1) + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new MoveService (view); + } +}; + +static tl::RegisteredClass move_service_decl (new MoveServiceDeclaration (), -970, "laybasic::MoveServicePlugin"); + +} diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 589cea374..485bbc199 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -24,8 +24,9 @@ #define HDR_layMove #include "laybasicCommon.h" -#include "dbManager.h" #include "layViewObject.h" +#include "layPlugin.h" +#include "dbManager.h" #include @@ -35,7 +36,7 @@ class Editables; class LayoutViewBase; class LAYBASIC_PUBLIC MoveService : - public lay::ViewService + public lay::ViewService, public lay::Plugin { public: MoveService (lay::LayoutViewBase *view); diff --git a/src/laybasic/laybasic/layPlugin.cc b/src/laybasic/laybasic/layPlugin.cc index f8c995c10..842351536 100644 --- a/src/laybasic/laybasic/layPlugin.cc +++ b/src/laybasic/laybasic/layPlugin.cc @@ -55,6 +55,12 @@ PluginDeclaration::PluginDeclaration () // .. nothing yet .. } +PluginDeclaration::PluginDeclaration (int id) + : m_id (id), m_editable_enabled (true) +{ + // .. nothing yet .. +} + PluginDeclaration::~PluginDeclaration () { if (Dispatcher::instance ()) { diff --git a/src/laybasic/laybasic/layPlugin.h b/src/laybasic/laybasic/layPlugin.h index f334252cd..2d5ee32d4 100644 --- a/src/laybasic/laybasic/layPlugin.h +++ b/src/laybasic/laybasic/layPlugin.h @@ -160,6 +160,11 @@ public: */ PluginDeclaration (); + /** + * @brief Constructor with a fixed ID + */ + PluginDeclaration (int id); + /** * @brief Destructor */ @@ -331,6 +336,18 @@ public: { // .. no pages in the default implementation .. } + + /** + * @brief Gets a value indicating whether "catchall" editor options pages shall be included + * + * "catchall" editor options pages are ones that are unspecific and render a null "plugin_declaration". + * A plugin can choose to include these pages if it listens to global configuration events. + * Otherwise it should return false here to suppress these pages. + */ + virtual bool enable_catchall_editor_options_pages () const + { + return true; + } #endif /** diff --git a/src/laybasic/laybasic/laySelector.cc b/src/laybasic/laybasic/laySelector.cc index c3c292d99..8a86a6048 100644 --- a/src/laybasic/laybasic/laySelector.cc +++ b/src/laybasic/laybasic/laySelector.cc @@ -43,6 +43,7 @@ SelectionService::SelectionService (lay::LayoutViewBase *view) : QObject (), #endif lay::ViewService (view->canvas ()), + lay::Plugin (view), mp_view (view), mp_box (0), m_color (0), @@ -317,4 +318,29 @@ SelectionService::begin (const db::DPoint &pos) ui ()->grab_mouse (this, true); } +// ---------------------------------------------------------------------------- + +class SelectionServiceDeclaration + : public lay::PluginDeclaration +{ +public: + SelectionServiceDeclaration () + : lay::PluginDeclaration (0) + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new SelectionService (view); + } + + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass selection_service_decl (new SelectionServiceDeclaration (), -980, "laybasic::SelectionServicePlugin"); + } diff --git a/src/laybasic/laybasic/laySelector.h b/src/laybasic/laybasic/laySelector.h index b15fe0400..7fd2036f4 100644 --- a/src/laybasic/laybasic/laySelector.h +++ b/src/laybasic/laybasic/laySelector.h @@ -29,6 +29,7 @@ #include "layViewObject.h" #include "layEditable.h" +#include "layPlugin.h" #if defined (HAVE_QT) # include @@ -45,7 +46,8 @@ class LAYBASIC_PUBLIC SelectionService : #if defined (HAVE_QT) public QObject, #endif - public lay::ViewService + public lay::ViewService, + public lay::Plugin { #if defined (HAVE_QT) Q_OBJECT diff --git a/src/laybasic/laybasic/layZoomBox.cc b/src/laybasic/laybasic/layZoomBox.cc index 221849169..064209ee2 100644 --- a/src/laybasic/laybasic/layZoomBox.cc +++ b/src/laybasic/laybasic/layZoomBox.cc @@ -32,7 +32,7 @@ namespace lay // ZoomService implementation ZoomService::ZoomService (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), + : lay::ViewService (view->canvas ()), lay::Plugin (view), mp_view (view), mp_box (0), m_color (0) @@ -282,5 +282,28 @@ ZoomService::begin (const db::DPoint &pos) ui ()->grab_mouse (this, true); } -} +// ---------------------------------------------------------------------------- +class ZoomServiceDeclaration + : public lay::PluginDeclaration +{ +public: + ZoomServiceDeclaration () + { + // .. nothing yet .. + } + + virtual lay::Plugin *create_plugin (db::Manager * /*manager*/, lay::Dispatcher * /*dispatcher*/, lay::LayoutViewBase *view) const + { + return new ZoomService (view); + } + + virtual bool enable_catchall_editor_options_pages () const + { + return false; + } +}; + +static tl::RegisteredClass zoom_service_decl (new ZoomServiceDeclaration (), -990, "laybasic::ZoomServicePlugin"); + +} diff --git a/src/laybasic/laybasic/layZoomBox.h b/src/laybasic/laybasic/layZoomBox.h index 2530eec98..ff2c5e866 100644 --- a/src/laybasic/laybasic/layZoomBox.h +++ b/src/laybasic/laybasic/layZoomBox.h @@ -26,6 +26,7 @@ #define HDR_layZoomBox #include "layViewObject.h" +#include "layPlugin.h" namespace lay { @@ -35,7 +36,7 @@ class LayoutCanvas; class RubberBox; class LAYBASIC_PUBLIC ZoomService - : public lay::ViewService + : public lay::ViewService, public lay::Plugin { public: ZoomService (lay::LayoutViewBase *view); diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index 92fc3b6ab..c6ecb634c 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -129,7 +129,7 @@ GridNetPluginDeclaration::create_plugin (db::Manager *, Dispatcher *, lay::Layou return new lay::GridNet (view); } -static tl::RegisteredClass config_decl (new GridNetPluginDeclaration (), 2010, "GridNetPlugin"); +static tl::RegisteredClass config_decl (new GridNetPluginDeclaration (), 2010, "lay::GridNetPlugin"); // ------------------------------------------------------------ // Implementation of the GridNet object diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index d361b4601..692ca8e2b 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -1591,18 +1591,7 @@ LayoutView::activate_editor_option_pages () { lay::EditorOptionsPages *eo_pages = editor_options_pages (); if (eo_pages) { - - // TODO: this is very inefficient as each "activate" will regenerate the tabs - for (std::vector::const_iterator op = eo_pages->pages ().begin (); op != eo_pages->pages ().end (); ++op) { - bool is_active = false; - if ((*op)->plugin_declaration () == 0) { - is_active = true; - } else if (active_plugin () && active_plugin ()->plugin_declaration () == (*op)->plugin_declaration ()) { - is_active = true; - } - (*op)->activate (is_active); - } - + eo_pages->activate (active_plugin ()); } } diff --git a/src/layview/unit_tests/layLayoutViewTests.cc b/src/layview/unit_tests/layLayoutViewTests.cc index 4cdf3b498..64b7b0cc0 100644 --- a/src/layview/unit_tests/layLayoutViewTests.cc +++ b/src/layview/unit_tests/layLayoutViewTests.cc @@ -173,6 +173,42 @@ TEST(4) EXPECT_EQ ((int) img.height (), 217); } +// options +TEST(5) +{ + std::unique_ptr lv; + + lv.reset (new lay::LayoutView (0, false, 0)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoMove)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, true); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoTracker)); + EXPECT_EQ (lv->mouse_tracker () == 0, true); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoZoom)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, true); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, false); + + lv.reset (new lay::LayoutView (0, false, 0, lay::LayoutView::LV_NoSelection)); + EXPECT_EQ (lv->mouse_tracker () == 0, false); + EXPECT_EQ (lv->zoom_service () == 0, false); + EXPECT_EQ (lv->move_service () == 0, false); + EXPECT_EQ (lv->selection_service () == 0, true); +} + #if defined(HAVE_PNG) TEST(11) { From 7f7a5bd3ac4307c0dbf3c56355d2c1c8429216e9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 18:55:16 +0200 Subject: [PATCH 14/52] Bugfix: properly integrating newborn plugins --- src/laybasic/laybasic/layLayoutViewBase.cc | 23 ---------------------- src/laybasic/laybasic/layMouseTracker.h | 5 +++++ src/laybasic/laybasic/layMove.h | 5 +++++ src/laybasic/laybasic/laySelector.h | 5 +++++ src/laybasic/laybasic/layZoomBox.h | 5 +++++ 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 7320fae7e..571329636 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -769,14 +769,6 @@ LayoutViewBase::configure (const std::string &name, const std::string &value) { lay::Dispatcher::configure (name, value); - if (mp_move_service && mp_move_service->configure (name, value)) { - return true; - } - - if (mp_tracker && mp_tracker->configure (name, value)) { - return true; - } - if (name == cfg_default_lyp_file) { m_def_lyp_file = value; @@ -1425,14 +1417,6 @@ LayoutViewBase::config_finalize () void LayoutViewBase::enable_edits (bool enable) { - // enable or disable these services: - if (mp_selection_service) { - mp_selection_service->enable (enable); - } - if (mp_move_service) { - mp_move_service->enable (enable); - } - // enable or disable the services that implement "lay::ViewService" for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { lay::ViewService *svc = (*p)->view_service_interface (); @@ -4843,13 +4827,6 @@ LayoutViewBase::background_color (tl::Color c) do_set_background_color (c, contrast); - if (mp_selection_service) { - mp_selection_service->set_colors (c, contrast); - } - if (mp_zoom_service) { - mp_zoom_service->set_colors (c, contrast); - } - // Set the color for all ViewService interfaces for (std::vector::iterator p = mp_plugins.begin (); p != mp_plugins.end (); ++p) { lay::ViewService *svc = (*p)->view_service_interface (); diff --git a/src/laybasic/laybasic/layMouseTracker.h b/src/laybasic/laybasic/layMouseTracker.h index 7be70a9aa..d7a8a5934 100644 --- a/src/laybasic/laybasic/layMouseTracker.h +++ b/src/laybasic/laybasic/layMouseTracker.h @@ -46,6 +46,11 @@ public: bool leave_event (bool prio); bool configure (const std::string &name, const std::string &value); + lay::ViewService *view_service_interface () + { + return this; + } + private: lay::LayoutViewBase *mp_view; tl::shared_collection mp_markers; diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 485bbc199..0fe112aef 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -47,6 +47,11 @@ public: void finish (); void cancel (); + lay::ViewService *view_service_interface () + { + return this; + } + private: virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); diff --git a/src/laybasic/laybasic/laySelector.h b/src/laybasic/laybasic/laySelector.h index 7fd2036f4..d4f29dbd3 100644 --- a/src/laybasic/laybasic/laySelector.h +++ b/src/laybasic/laybasic/laySelector.h @@ -57,6 +57,11 @@ public: SelectionService (lay::LayoutViewBase *view); ~SelectionService (); + lay::ViewService *view_service_interface () + { + return this; + } + void set_colors (tl::Color background, tl::Color color); void begin (const db::DPoint &pos); diff --git a/src/laybasic/laybasic/layZoomBox.h b/src/laybasic/laybasic/layZoomBox.h index ff2c5e866..93fe9bbec 100644 --- a/src/laybasic/laybasic/layZoomBox.h +++ b/src/laybasic/laybasic/layZoomBox.h @@ -46,6 +46,11 @@ public: void begin (const db::DPoint &pos); void begin_pan (const db::DPoint &pos); + lay::ViewService *view_service_interface () + { + return this; + } + private: virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio); From 0187abfafcc560f58a5a132c10ba64b454e376cd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 19:37:12 +0200 Subject: [PATCH 15/52] [consider merging] Bugfix: internal error when trying to replace a shape in a standalone Shapes collection while preserving properties --- src/db/db/dbShapes.cc | 24 +++++++++-- src/db/unit_tests/dbShapesTests.cc | 68 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/db/db/dbShapes.cc b/src/db/db/dbShapes.cc index ff2c1bfff..8d396730b 100644 --- a/src/db/db/dbShapes.cc +++ b/src/db/db/dbShapes.cc @@ -1441,8 +1441,10 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ if (! layout ()) { if (needs_translate (tag)) { + return reinsert_member_with_props (tag, ref, sh); - } else { + + } else if (! ref.has_prop_id ()) { // simple replace case @@ -1459,7 +1461,21 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ db::layer_op::queue_or_append (manager (), this, true /*insert*/, sh); } - return ref; + } else { + + if (manager () && manager ()->transacting ()) { + check_is_editable_for_undo_redo (); + db::layer_op, db::stable_layer_tag>::queue_or_append (manager (), this, false /*not insert*/, *ref.basic_ptr (typename db::object_with_properties::tag ())); + } + + invalidate_state (); // HINT: must come before the change is done! + + db::object_with_properties swp (sh, ref.prop_id ()); + get_layer, db::stable_layer_tag> ().replace (ref.basic_iter (typename db::object_with_properties::tag ()), swp); + + if (manager () && manager ()->transacting ()) { + db::layer_op, db::stable_layer_tag>::queue_or_append (manager (), this, true /*insert*/, swp); + } } @@ -1514,9 +1530,9 @@ Shapes::replace_member_with_props (typename db::object_tag tag, const shape_ } - return ref; - } + + return ref; } // explicit instantiations diff --git a/src/db/unit_tests/dbShapesTests.cc b/src/db/unit_tests/dbShapesTests.cc index fd584449e..7fb53898c 100644 --- a/src/db/unit_tests/dbShapesTests.cc +++ b/src/db/unit_tests/dbShapesTests.cc @@ -2404,6 +2404,10 @@ TEST(12A) db::Cell &topcell = layout.cell (*layout.begin_top_down ()); + // standalone copy + db::Shapes copy (true); + copy = topcell.shapes (lindex); + db::Shapes::shape_iterator shape = topcell.shapes (lindex).begin (db::Shapes::shape_iterator::All); while (! shape.at_end ()) { topcell.shapes (lindex).replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); @@ -2436,6 +2440,70 @@ TEST(12A) "box (-1050,150;-150,2150) #112\n" ); + shape = topcell.shapes (lindex).begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + topcell.shapes (lindex).replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, topcell.shapes (lindex)), + "box (-2000,-1000;0,-100) #0\n" + "box (-2100,-1100;-100,-200) #0\n" + "box (-2150,-1050;-150,-150) #0\n" + "box (-2000,-1000;0,-100) #110\n" + "box (-2100,-1100;-100,-200) #111\n" + "box (-2150,-1050;-150,-150) #112\n" + ); + + // on standalone shapes + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + copy.replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-1000,0;-100,2000) #0\n" + "box (-1100,100;-200,2100) #0\n" + "box (-1050,150;-150,2150) #0\n" + "box (-1000,0;-100,2000) #10\n" + "box (-1100,100;-200,2100) #11\n" + "box (-1050,150;-150,2150) #12\n" + ); + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + if (shape->has_prop_id ()) { + copy.replace_prop_id (*shape, shape->prop_id () + 100); + } + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-1000,0;-100,2000) #0\n" + "box (-1100,100;-200,2100) #0\n" + "box (-1050,150;-150,2150) #0\n" + "box (-1000,0;-100,2000) #110\n" + "box (-1100,100;-200,2100) #111\n" + "box (-1050,150;-150,2150) #112\n" + ); + + shape = copy.begin (db::Shapes::shape_iterator::All); + while (! shape.at_end ()) { + copy.replace (*shape, db::Box (shape->box ().transformed (db::Trans (1)))); + ++shape; + } + + EXPECT_EQ (shapes_to_string (_this, copy), + "box (-2000,-1000;0,-100) #0\n" + "box (-2100,-1100;-100,-200) #0\n" + "box (-2150,-1050;-150,-150) #0\n" + "box (-2000,-1000;0,-100) #110\n" + "box (-2100,-1100;-100,-200) #111\n" + "box (-2150,-1050;-150,-150) #112\n" + ); + } } From 6a9269a9ae2e4c82ef8351efd5509b3e97565506 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 21:30:15 +0200 Subject: [PATCH 16/52] [consider merging] Bugfix: internal error when copying while move/copy operation of images and rulers --- src/ant/ant/antService.cc | 1 + src/img/img/imgService.cc | 16 +++++++++++++++- src/lay/lay/gsiDeclLayPlugin.cc | 2 ++ src/layui/layui/layLayoutViewFunctions.cc | 3 ++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 37ead1bac..d4bd0acff 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1766,6 +1766,7 @@ Service::edit_cancel () if (m_move_mode != MoveNone) { m_move_mode = MoveNone; + m_selected.clear (); selection_to_view (); } diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index fc45ffe99..9e27d9449 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -1080,6 +1080,7 @@ Service::edit_cancel () { if (m_move_mode != move_none) { m_move_mode = move_none; + m_selected.clear (); selection_to_view (); } } @@ -1118,14 +1119,27 @@ Service::paste () { if (db::Clipboard::instance ().begin () != db::Clipboard::instance ().end ()) { + std::vector new_objects; + for (db::Clipboard::iterator c = db::Clipboard::instance ().begin (); c != db::Clipboard::instance ().end (); ++c) { const db::ClipboardValue *value = dynamic_cast *> (*c); if (value) { img::Object *image = new img::Object (value->get ()); - mp_view->annotation_shapes ().insert (db::DUserObject (image)); + new_objects.push_back (&mp_view->annotation_shapes ().insert (db::DUserObject (image))); } } + // make new objects selected + + if (! new_objects.empty ()) { + + for (auto r = new_objects.begin (); r != new_objects.end (); ++r) { + m_selected.insert (mp_view->annotation_shapes ().iterator_from_pointer (*r)); + } + + selection_to_view (); + + } } } diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 5ac999e8a..ca58818e8 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -1173,6 +1173,8 @@ Class decl_PluginFactory ("lay", "PluginFactory", "\n" "Menu items created this way will send a configuration request with 'cname' as the configuration parameter name " "and 'cvalue' as the configuration parameter value.\n" + "If 'cvalue' is a string with a single question mark (\"?\"), the item is a check box that reflects the boolean " + "value of the configuration item.\n" "\n" "This method has been introduced in version 0.27." ) + diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index ef359aaea..a0b04f0c8 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -1191,6 +1191,8 @@ LayoutViewFunctions::cm_remove_unused () void LayoutViewFunctions::do_cm_duplicate (bool interactive) { + view ()->cancel_edits (); + // Do duplicate simply by concatenating copy & paste currently. // Save the clipboard state before in order to preserve the current content db::Clipboard saved_clipboard; @@ -1199,7 +1201,6 @@ LayoutViewFunctions::do_cm_duplicate (bool interactive) try { bool transient_mode = ! view ()->has_selection (); view ()->copy_view_objects (); - view ()->cancel_edits (); if (interactive) { view ()->paste_interactive (transient_mode); } else { From 53c173d01ee97f2b33dc81b1ca77a1a16ad976e6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 21:49:27 +0200 Subject: [PATCH 17/52] [consider merging] avoid a warning on writing cells/files in 'under_construction' mode when there is nothing to update --- src/db/db/dbLayout.cc | 10 ++++++++-- src/db/db/dbLayout.h | 8 ++++++++ src/db/db/dbWriter.cc | 2 +- src/db/db/gsiDeclDbLayout.cc | 7 +++++++ src/db/unit_tests/dbLayoutTests.cc | 4 ++++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 1cc992459..bf2a84e0e 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1780,7 +1780,7 @@ Layout::force_update () // NOTE: the assumption is that either one thread is writing or // multiple threads are reading. Hence, we do not need to lock hier_dirty() or bboxes_dirty(). // We still do double checking as another thread might do the update as well. - if (! hier_dirty () && ! bboxes_dirty ()) { + if (! update_needed ()) { return; } @@ -1829,10 +1829,16 @@ Layout::update () const } } +bool +Layout::update_needed () const +{ + return hier_dirty () || bboxes_dirty (); +} + void Layout::do_update () { - if (! hier_dirty () && ! bboxes_dirty ()) { + if (! update_needed ()) { return; } diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index 02b98a593..4c0d0078a 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -1619,6 +1619,14 @@ public: */ top_down_const_iterator end_top_cells () const; + /** + * @brief Gets a value indicating whether an update is needed + * + * If this value is false, update or force_update will not + * do anything. + */ + bool update_needed () const; + /** * @brief Provide a const version of the update method * diff --git a/src/db/db/dbWriter.cc b/src/db/db/dbWriter.cc index bab0f58dd..9cf4cfb9a 100644 --- a/src/db/db/dbWriter.cc +++ b/src/db/db/dbWriter.cc @@ -58,7 +58,7 @@ Writer::write (db::Layout &layout, tl::OutputStream &stream) { tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Writing file: ")) + stream.path ()); - if (layout.under_construction ()) { + if (layout.under_construction () && layout.update_needed ()) { tl::warn << tl::to_string (tr ("Cannot properly write a layout that is under construction - forcing update.")); layout.force_update (); } diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 647dbd921..c222a74ac 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -1758,6 +1758,13 @@ Class decl_Layout ("db", "Layout", "is ongoing or the layout is brought into invalid state by\n" "\"start_changes\".\n" ) + + gsi::method ("update_needed", &db::Layout::update_needed, + "@brief Gets a value indicating whether the Layout object needs an update\n" + "If this method returns false, \\update will not do anything. This is useful to force an update at " + "specific times during 'under_construction' conditions.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + gsi::method ("update", (void (db::Layout::*) ()) &db::Layout::force_update, "@brief Updates the internals of the layout\n" "This method updates the internal state of the layout. Usually this is done automatically\n" diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index e96cb5c28..00fbb2056 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -294,7 +294,9 @@ TEST(2) EXPECT_EQ (g.hier_generation_id (), size_t (3)); g.clear (); + EXPECT_EQ (g.update_needed (), true); g.update (); + EXPECT_EQ (g.update_needed (), false); el.reset (); EXPECT_EQ (g.hier_generation_id (), size_t (4)); @@ -387,7 +389,9 @@ TEST(3) EXPECT_EQ (el.hier_dirty, true); el.reset (); + EXPECT_EQ (g.update_needed (), true); g.update (); + EXPECT_EQ (g.update_needed (), false); top->shapes (0).insert (db::Box (0, 0, 10, 20)); top->shapes (1).insert (db::Box (0, 0, 10, 20)); From 43454962d45357003757823b63e8b477410d697c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 30 Aug 2025 23:20:23 +0200 Subject: [PATCH 18/52] Improving ruler snapping (after move_transform, snap to objects, visual snap details hint ...) --- src/ant/ant/antService.cc | 137 +++++++++++++++++++------------- src/ant/ant/antService.h | 7 +- src/lay/lay/gsiDeclLayPlugin.cc | 8 +- 3 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index d4bd0acff..6f6c0e556 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1431,7 +1431,7 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang const ant::Object *robj = dynamic_cast ((*ri).ptr ()); if (robj && (! robj_min || robj == robj_min)) { - if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index) && m_move_mode != MoveRuler) { + if (dragging_what (robj, search_dbox, m_move_mode, m_p1, m_seg_index)) { // found anything: make the moved ruler the selection clear_selection (); @@ -1516,28 +1516,70 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang } void -Service::move_transform (const db::DPoint &p, db::DFTrans tr, lay::angle_constraint_type /*ac*/) +Service::snap_rulers (lay::angle_constraint_type ac) +{ + if (m_rulers.empty ()) { + return; + } + + lay::PointSnapToObjectResult min_snp; + double min_dist = -1.0; + db::DVector min_delta; + + for (auto r = m_rulers.begin (); r != m_rulers.end (); ++r) { + + const ant::Object *ruler = (*r)->ruler (); + + db::DPoint p1 = m_trans * ruler->p1 (); + db::DPoint p2 = m_trans * ruler->p2 (); + + auto tr = db::DTrans ((m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1); + db::DPoint org1 = tr * ruler->p1 (); + db::DPoint org2 = tr * ruler->p2 (); + + auto snp = snap2_details (org1, p1, ruler, ac); + double dist = p1.distance (snp.snapped_point); + + if (min_dist < 0 || dist < min_dist) { + min_snp = snp; + min_dist = dist; + min_delta = snp.snapped_point - p1; + } + + snp = snap2_details (org2, p2, ruler, ac); + dist = p2.distance (snp.snapped_point); + + if (min_dist < 0 || dist < min_dist) { + min_snp = snp; + min_dist = dist; + min_delta = snp.snapped_point - p2; + } + + } + + if (min_snp.object_snap != lay::PointSnapToObjectResult::NoObject) { + mouse_cursor_from_snap_details (min_snp); + } + + m_trans = db::DTrans (min_delta) * m_trans; +} + +void +Service::move_transform (const db::DPoint & /*p*/, db::DFTrans tr, lay::angle_constraint_type ac) { if (m_rulers.empty () || m_selected.empty ()) { return; } - if (m_move_mode == MoveRuler) { + auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; + clear_mouse_cursors (); - db::DVector dp = p - db::DPoint (); - - m_original.transform (db::DTrans (m_p1 - db::DPoint ()) * db::DTrans (tr) * db::DTrans (db::DPoint () - m_p1)); - m_current.transform (db::DTrans (dp) * db::DTrans (tr) * db::DTrans (-dp)); - - // display current rulers' parameters - show_message (); - - m_rulers [0]->redraw (); - - } else if (m_move_mode == MoveSelected) { + if (m_move_mode == MoveSelected) { m_trans *= db::DTrans (m_p1 - db::DPoint ()) * db::DTrans (tr) * db::DTrans (db::DPoint () - m_p1); + snap_rulers (ac_eff); + for (std::vector::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) { (*r)->transform_by (db::DCplxTrans (m_trans)); } @@ -1553,90 +1595,66 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) return; } + auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; + clear_mouse_cursors (); + if (m_move_mode == MoveP1) { - m_current.seg_p1 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p1 (m_seg_index, snap2_visual (m_p1, p, &m_current, ac)); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2) { - m_current.seg_p2 (m_seg_index, snap2 (m_p1, p, &m_current, ac).second); + m_current.seg_p2 (m_seg_index, snap2_visual (m_p1, p, &m_current, ac)); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP12) { - db::DPoint p12 = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint p12 = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (m_current.seg_p1 (m_seg_index).x(), p12.y ())); m_current.seg_p2 (m_seg_index, db::DPoint (p12.x (), m_current.seg_p2 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP21) { - db::DPoint p21 = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint p21 = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (p21.x (), m_current.seg_p1 (m_seg_index).y ())); m_current.seg_p2 (m_seg_index, db::DPoint (m_current.seg_p2 (m_seg_index).x(), p21.y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP1X) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (pc.x (), m_current.seg_p1 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2X) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p2 (m_seg_index, db::DPoint (pc.x (), m_current.seg_p2 (m_seg_index).y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP1Y) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p1 (m_seg_index, db::DPoint (m_current.seg_p1 (m_seg_index).x (), pc.y ())); m_rulers [0]->redraw (); } else if (m_move_mode == MoveP2Y) { - db::DPoint pc = snap2 (m_p1, p, &m_current, ac).second; + db::DPoint pc = snap2_visual (m_p1, p, &m_current, ac); m_current.seg_p2 (m_seg_index, db::DPoint (m_current.seg_p2 (m_seg_index).x (), pc.y ())); m_rulers [0]->redraw (); - } else if (m_move_mode == MoveRuler) { - - // try two ways of snapping - db::DVector dp = lay::snap_angle (p - m_p1, ac == lay::AC_Global ? m_snap_mode : ac); - - db::DPoint p1 = m_original.p1 () + dp; - db::DPoint p2 = m_original.p2 () + dp; - - std::pair r1 = snap1 (p1, m_obj_snap && m_original.snap ()); - db::DPoint q1 = r1.second; - std::pair r2 = snap1 (p2, m_obj_snap && m_original.snap ()); - db::DPoint q2 = r2.second; - - if ((!r2.first && r1.first) || ((r1.first || (!r1.first && !r2.first)) && q1.distance (p1) < q2.distance (p2))) { - q2 = q1 + (m_original.p2 () - m_original.p1 ()); - } else { - q1 = q2 + (m_original.p1 () - m_original.p2 ()); - } - - m_current.p1 (q1); - m_current.p2 (q2); - - m_rulers [0]->redraw (); - } else if (m_move_mode == MoveSelected) { db::DVector dp = p - m_p1; - // round the drag distance to grid if required: this is the least we can do in this case - if (m_grid_snap) { - dp = db::DVector (lay::snap (dp.x (), m_grid), lay::snap (dp.y (), m_grid)); - } - - dp = lay::snap_angle (dp, ac == lay::AC_Global ? m_snap_mode : ac); + dp = lay::snap_angle (dp, ac_eff); m_trans = db::DTrans (dp + (m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1); + snap_rulers (ac_eff); + for (std::vector::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) { (*r)->transform_by (db::DCplxTrans (m_trans)); } @@ -1646,7 +1664,6 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) if (m_move_mode != MoveSelected) { show_message (); } - } void @@ -1703,6 +1720,7 @@ Service::end_move (const db::DPoint &, lay::angle_constraint_type) // termine the operation m_move_mode = MoveNone; + clear_mouse_cursors (); } void @@ -2040,7 +2058,7 @@ Service::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) // otherwise we risk manipulating p1 too. ant::Object::point_list pts = m_current.points (); if (! pts.empty ()) { - pts.back () = snap2 (m_p1, p, mp_active_ruler->ruler (), ac_from_buttons (buttons)).second; + pts.back () = snap_details.snapped_point; } m_current.set_points_exact (pts); @@ -2095,11 +2113,16 @@ Service::snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::O return lay::obj_snap (m_obj_snap && obj->snap () ? mp_view : 0, p1, p2, g, snap_mode, snap_range); } -std::pair -Service::snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac) +db::DPoint +Service::snap2_visual (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac) { lay::PointSnapToObjectResult res = snap2_details (p1, p2, obj, ac); - return std::make_pair (res.object_snap != lay::PointSnapToObjectResult::NoObject, res.snapped_point); + + if (res.object_snap != lay::PointSnapToObjectResult::NoObject) { + mouse_cursor_from_snap_details (res); + } + + return res.snapped_point; } diff --git a/src/ant/ant/antService.h b/src/ant/ant/antService.h index 509d00289..13086eeb4 100644 --- a/src/ant/ant/antService.h +++ b/src/ant/ant/antService.h @@ -208,10 +208,9 @@ public: * MoveP2X - dragging P2.x (if box-like) * MoveP1Y - dragging P1.y (if box-like) * MoveP2Y - dragging P2.y (if box-like) - * MoveRuler - dragging a whole ruler (one) * MoveSelection - dragging a whole ruler (many) */ - enum MoveMode { MoveNone, MoveP1, MoveP2, MoveP12, MoveP21, MoveP1X, MoveP2X, MoveP1Y, MoveP2Y, MoveRuler, MoveSelected }; + enum MoveMode { MoveNone, MoveP1, MoveP2, MoveP12, MoveP21, MoveP1X, MoveP2X, MoveP1Y, MoveP2Y, MoveSelected }; Service (db::Manager *manager, lay::LayoutViewBase *view); @@ -601,7 +600,7 @@ private: std::pair snap1 (const db::DPoint &p, bool obj_snap); lay::PointSnapToObjectResult snap1_details (const db::DPoint &p, bool obj_snap); - std::pair snap2 (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); + db::DPoint snap2_visual (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::PointSnapToObjectResult snap2_details (const db::DPoint &p1, const db::DPoint &p2, const ant::Object *obj, lay::angle_constraint_type ac); lay::TwoPointSnapToObjectResult auto_measure (const db::DPoint &p, lay::angle_constraint_type ac, const ant::Template &tpl); @@ -620,6 +619,8 @@ private: virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual void deactivated (); + void snap_rulers (lay::angle_constraint_type ac); + /** * @brief Select a certain ruler * diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index ca58818e8..ee4bf3441 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -1584,8 +1584,8 @@ Class decl_Plugin ("lay", "Plugin", "angle constraint. Only grid constraints and snapping to objects is supported.\n" "\n" "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " - "provide a visualization of the edges or vertexes that the point is snapping to. If you use this feature, " - "make sure you call \\clear_mouse_cursors before to remove existing cursors.\n" + "provide a visualization of the edges or vertexes that the point is snapping to. \\clear_mouse_cursors will " + "be called before.\n" "\n" "This method has been added in version 0.30.4." ) + @@ -1609,8 +1609,8 @@ Class decl_Plugin ("lay", "Plugin", "will snap to another object. The behavior is given by the respective configuration.\n" "\n" "If \"visualize\" is true, the function will generate calls to \\add_mouse_cursor or \\add_edge_marker to " - "provide a visualization of the edges or vertexes that the point is snapping to. If you use this feature, " - "make sure you call \\clear_mouse_cursors before to remove existing cursors.\n" + "provide a visualization of the edges or vertexes that the point is snapping to. \\clear_mouse_cursors will " + "be called before.\n" "\n" "This method has been added in version 0.30.4." ) + From 57a49849595127b966ab572e39b6221d3a4acece Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 31 Aug 2025 15:50:26 +0200 Subject: [PATCH 19/52] WIP: refactoring of heavy Plugin GSI binding functions --- src/lay/lay/gsiDeclLayConfigPage.cc | 114 ++ src/lay/lay/gsiDeclLayConfigPage.h | 67 + src/lay/lay/gsiDeclLayDispatcher.cc | 139 ++ src/lay/lay/gsiDeclLayEditorOptionsPage.cc | 125 ++ src/lay/lay/gsiDeclLayEditorOptionsPage.h | 73 + src/lay/lay/gsiDeclLayPlugin.cc | 1827 +++++--------------- src/lay/lay/gsiDeclLayPlugin.h | 161 ++ src/lay/lay/gsiDeclLayPluginFactory.cc | 600 +++++++ src/lay/lay/lay.pro | 7 + 9 files changed, 1756 insertions(+), 1357 deletions(-) create mode 100644 src/lay/lay/gsiDeclLayConfigPage.cc create mode 100644 src/lay/lay/gsiDeclLayConfigPage.h create mode 100644 src/lay/lay/gsiDeclLayDispatcher.cc create mode 100644 src/lay/lay/gsiDeclLayEditorOptionsPage.cc create mode 100644 src/lay/lay/gsiDeclLayEditorOptionsPage.h create mode 100644 src/lay/lay/gsiDeclLayPlugin.h create mode 100644 src/lay/lay/gsiDeclLayPluginFactory.cc diff --git a/src/lay/lay/gsiDeclLayConfigPage.cc b/src/lay/lay/gsiDeclLayConfigPage.cc new file mode 100644 index 000000000..cb245498a --- /dev/null +++ b/src/lay/lay/gsiDeclLayConfigPage.cc @@ -0,0 +1,114 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDeclLayConfigPage.h" + +#include "gsiQtGuiExternals.h" +#include "gsiQtWidgetsExternals.h" // for Qt5 + +namespace gsi +{ + +ConfigPageImpl::ConfigPageImpl (const std::string &title) + : lay::ConfigPage (0), m_title (title) +{ + // .. nothing yet .. +} + +void +ConfigPageImpl::commit_impl (lay::Dispatcher *root) +{ + lay::ConfigPage::commit (root); +} + +void +ConfigPageImpl::commit (lay::Dispatcher *root) +{ + if (f_commit.can_issue ()) { + f_commit.issue (&ConfigPageImpl::commit_impl, root); + } else { + ConfigPageImpl::commit_impl (root); + } +} + +void +ConfigPageImpl::setup_impl (lay::Dispatcher *root) +{ + lay::ConfigPage::setup (root); +} + +void +ConfigPageImpl::setup (lay::Dispatcher *root) +{ + if (f_setup.can_issue ()) { + f_setup.issue (&ConfigPageImpl::setup_impl, root); + } else { + ConfigPageImpl::setup_impl (root); + } +} + +ConfigPageImpl *new_config_page (const std::string &title) +{ + return new ConfigPageImpl (title); +} + +Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigPage", + constructor ("new", &new_config_page, gsi::arg ("title"), + "@brief Creates a new ConfigPage object\n" + "@param title The title of the page and also the position in the configuration page tree\n" + "\n" + "The title has the form 'Group|Page' - e.g. 'Application|Macro Development IDE' will place " + "the configuration page in the 'Application' group and into the 'Macro Development IDE' page." + ) + + callback ("apply", &ConfigPageImpl::commit, &ConfigPageImpl::f_commit, 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" + ) + + callback ("setup", &ConfigPageImpl::setup, &ConfigPageImpl::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" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's configuration page\n" + "\n" + "This object provides a way to establish plugin-specific configuration pages.\n" + "\n" + "The only way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted. Configuration parameters can be obtained by the plugin " + "directly from the \\Dispatcher object) or by listening to 'configure' calls.\n" + "\n" + "For the purpose of data transfer, the configuration page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + +} + +#endif diff --git a/src/lay/lay/gsiDeclLayConfigPage.h b/src/lay/lay/gsiDeclLayConfigPage.h new file mode 100644 index 000000000..4295b2146 --- /dev/null +++ b/src/lay/lay/gsiDeclLayConfigPage.h @@ -0,0 +1,67 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if !defined(_HDR_gsiDeclLayConfigPage) +#define _HDR_gsiDeclLayConfigPage + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layPluginConfigPage.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class ConfigPageImpl + : public lay::ConfigPage, public gsi::ObjectBase +{ +public: + ConfigPageImpl (const std::string &title); + + virtual std::string title () const + { + return m_title; + } + + void commit_impl (lay::Dispatcher *root); + virtual void commit (lay::Dispatcher *root); + void setup_impl (lay::Dispatcher *root); + virtual void setup (lay::Dispatcher *root); + + gsi::Callback f_commit; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + std::string m_index; +}; + +} + +#endif + +#endif diff --git a/src/lay/lay/gsiDeclLayDispatcher.cc b/src/lay/lay/gsiDeclLayDispatcher.cc new file mode 100644 index 000000000..36e2d11a8 --- /dev/null +++ b/src/lay/lay/gsiDeclLayDispatcher.cc @@ -0,0 +1,139 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "layDispatcher.h" + +namespace gsi +{ + +static std::vector +get_config_names (lay::Dispatcher *dispatcher) +{ + std::vector names; + dispatcher->get_config_names (names); + return names; +} + +static lay::Dispatcher *dispatcher_instance () +{ + return lay::Dispatcher::instance (); +} + +static tl::Variant get_config (lay::Dispatcher *dispatcher, const std::string &name) +{ + std::string value; + if (dispatcher->config_get (name, value)) { + return tl::Variant (value); + } else { + return tl::Variant (); + } +} + +/** + * @brief Exposes the Dispatcher interface + * + * This interface is intentionally not derived from Plugin. It is used currently to + * identify the dispatcher node for configuration. The Plugin nature of this interface + * is somewhat artificial and may be removed later. + * + * TODO: this is a duplicate of the respective methods in LayoutView and Application. + * This is intentional since we don't want to spend the only derivation path on this. + * Once there is a mixin concept, provide a path through that concept. + */ +Class decl_Dispatcher ("lay", "Dispatcher", + method ("clear_config", &lay::Dispatcher::clear_config, + "@brief Clears the configuration parameters\n" + ) + + method ("instance", &dispatcher_instance, + "@brief Gets the singleton instance of the Dispatcher object\n" + "\n" + "@return The instance\n" + ) + + method ("write_config", &lay::Dispatcher::write_config, gsi::arg ("file_name"), + "@brief Writes configuration to a file\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "If the configuration file cannot be written, false \n" + "is returned but no exception is thrown.\n" + ) + + method ("read_config", &lay::Dispatcher::read_config, gsi::arg ("file_name"), + "@brief Reads the configuration from a file\n" + "@return A value indicating whether the operation was successful\n" + "\n" + "This method silently does nothing, if the config file does not\n" + "exist. If it does and an error occurred, the error message is printed\n" + "on stderr. In both cases, false is returned.\n" + ) + + method_ext ("get_config", &get_config, gsi::arg ("name"), + "@brief Gets the value of a local configuration parameter\n" + "\n" + "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" + "\n" + "@return The value of the parameter or nil if there is no such parameter\n" + ) + + method ("set_config", (void (lay::Dispatcher::*) (const std::string &, const std::string &)) &lay::Dispatcher::config_set, gsi::arg ("name"), gsi::arg ("value"), + "@brief Set a local configuration parameter with the given name to the given value\n" + "\n" + "@param name The name of the configuration parameter to set\n" + "@param value The value to which to set the configuration parameter\n" + "\n" + "This method sets a configuration parameter with the given name to the given value. " + "Values can only be strings. Numerical values have to be converted into strings first. " + "Local configuration parameters override global configurations for this specific view. " + "This allows for example to override global settings of background colors. " + "Any local settings are not written to the configuration file. " + ) + + method_ext ("get_config_names", &get_config_names, + "@brief Gets the configuration parameter names\n" + "\n" + "@return A list of configuration parameter names\n" + "\n" + "This method returns the names of all known configuration parameters. These names can be used to " + "get and set configuration parameter values.\n" + ) + + method ("commit_config", &lay::Dispatcher::config_end, + "@brief Commits the configuration settings\n" + "\n" + "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " + "After a sequence of \\set_config calls, this method should be called to activate the " + "settings made by these calls.\n" + ), + "@brief Root of the configuration space in the plugin context and menu dispatcher\n" + "\n" + "This class provides access to the root configuration space in the context " + "of plugin programming. You can use this class to obtain configuration parameters " + "from the configuration tree during plugin initialization. However, the " + "preferred way of plugin configuration is through \\Plugin#configure.\n" + "\n" + "Currently, the application object provides an identical entry point for configuration modification. " + "For example, \"Application::instance.set_config\" is identical to \"Dispatcher::instance.set_config\". " + "Hence there is little motivation for the Dispatcher class currently and " + "this interface may be modified or removed in the future." + "\n" + "This class has been introduced in version 0.25 as 'PluginRoot'.\n" + "It is renamed and enhanced as 'Dispatcher' in 0.27." +); + +} diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc new file mode 100644 index 000000000..318aa1385 --- /dev/null +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -0,0 +1,125 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDeclLayEditorOptionsPage.h" + +#include "gsiQtGuiExternals.h" +#include "gsiQtWidgetsExternals.h" // for Qt5 + +namespace gsi +{ + +EditorOptionsPageImpl::EditorOptionsPageImpl (const std::string &title, int index) + : lay::EditorOptionsPage (), m_title (title), m_index (index) +{ + // .. nothing yet .. +} + +void +EditorOptionsPageImpl::call_edited () +{ + lay::EditorOptionsPage::edited (); +} + +void +EditorOptionsPageImpl::apply_impl (lay::Dispatcher *root) +{ + lay::EditorOptionsPage::apply (root); +} + +void +EditorOptionsPageImpl::apply (lay::Dispatcher *root) +{ + if (f_apply.can_issue ()) { + f_apply.issue (&EditorOptionsPageImpl::apply_impl, root); + } else { + EditorOptionsPageImpl::apply_impl (root); + } +} + +void +EditorOptionsPageImpl::setup_impl (lay::Dispatcher *root) +{ + lay::EditorOptionsPage::setup (root); +} + +void +EditorOptionsPageImpl::setup (lay::Dispatcher *root) +{ + if (f_setup.can_issue ()) { + f_setup.issue (&EditorOptionsPageImpl::setup_impl, root); + } else { + EditorOptionsPageImpl::setup_impl (root); + } +} + +EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int index) +{ + return new EditorOptionsPageImpl (title, index); +} + +Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPage", + constructor ("new", &new_editor_options_page, gsi::arg ("title"), gsi::arg ("index"), + "@brief Creates a new EditorOptionsPage object\n" + "@param title The title of the page\n" + "@param index The position of the page in the tab bar\n" + ) + + method ("view", &EditorOptionsPageImpl::view, + "@brief Gets the view object this page is associated with\n" + ) + + method ("edited", &EditorOptionsPageImpl::call_edited, + "@brief Call this method when some entry widget has changed\n" + "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" + ) + + 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" + ) + + 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" + "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " + "and set the editing widget's state accordingly.\n" + ), + "@brief The plugin framework's editor options page\n" + "\n" + "This object provides a way to establish plugin-specific editor options pages.\n" + "\n" + "The preferred way of communication between the page and the plugin is through " + "configuration parameters. One advantage of this approach is that the current state is " + "automatically persisted.\n" + "\n" + "For this purpose, the editor options page has two methods: 'apply' which is supposed to transfer " + "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " + "configuration parameters into editor widget states. Both methods are called by the system when " + "some transfer is needed.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + +} + +#endif diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.h b/src/lay/lay/gsiDeclLayEditorOptionsPage.h new file mode 100644 index 000000000..f778150bd --- /dev/null +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.h @@ -0,0 +1,73 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#if !defined(_HDR_gsiDeclLayEditorOptionsPage) +#define _HDR_gsiDeclLayEditorOptionsPage + +#if defined(HAVE_QTBINDINGS) + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layEditorOptionsPage.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class EditorOptionsPageImpl + : public lay::EditorOptionsPage, public gsi::ObjectBase +{ +public: + EditorOptionsPageImpl (const std::string &title, int index); + + virtual std::string title () const + { + return m_title; + } + + virtual int order () const + { + return m_index; + } + + 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; + gsi::Callback f_setup; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + std::string m_title; + int m_index; +}; + +} + +#endif + +#endif diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index ee4bf3441..5b288c6fb 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -20,1296 +20,517 @@ */ +#include "gsiDeclLayPlugin.h" #include "gsiDecl.h" #include "gsiDeclBasic.h" #include "gsiEnums.h" -#include "layPlugin.h" -#include "layPluginConfigPage.h" -#include "layEditorOptionsPage.h" #include "layEditorOptionsPages.h" -#include "layEditorServiceBase.h" -#include "layViewObject.h" -#include "layLayoutViewBase.h" #include "layCursor.h" #include "edtConfig.h" #include "edtUtils.h" -#if defined(HAVE_QTBINDINGS) -# include "gsiQtGuiExternals.h" -# include "gsiQtWidgetsExternals.h" // for Qt5 -#endif - namespace gsi { -class PluginFactoryBase; -class PluginBase; - -#if defined(HAVE_QTBINDINGS) -class EditorOptionsPageImpl - : public lay::EditorOptionsPage, public gsi::ObjectBase -{ -public: - EditorOptionsPageImpl (const std::string &title, int index) - : lay::EditorOptionsPage (), m_title (title), m_index (index) - { - // .. nothing yet .. - } - - virtual std::string title () const - { - return m_title; - } - - virtual int order () const - { - return m_index; - } - - void call_edited () - { - lay::EditorOptionsPage::edited (); - } - - void apply_impl (lay::Dispatcher *root) - { - lay::EditorOptionsPage::apply (root); - } - - virtual void apply (lay::Dispatcher *root) - { - if (f_apply.can_issue ()) { - f_apply.issue (&EditorOptionsPageImpl::apply_impl, root); - } else { - EditorOptionsPageImpl::apply_impl (root); - } - } - - void setup_impl (lay::Dispatcher *root) - { - lay::EditorOptionsPage::setup (root); - } - - virtual void setup (lay::Dispatcher *root) - { - if (f_setup.can_issue ()) { - f_setup.issue (&EditorOptionsPageImpl::setup_impl, root); - } else { - EditorOptionsPageImpl::setup_impl (root); - } - } - - gsi::Callback f_apply; - gsi::Callback f_setup; - -private: - tl::weak_ptr mp_view; - tl::weak_ptr mp_dispatcher; - std::string m_title; - int m_index; -}; - -EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int index) -{ - return new EditorOptionsPageImpl (title, index); -} - -Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPage", - constructor ("new", &new_editor_options_page, gsi::arg ("title"), gsi::arg ("index"), - "@brief Creates a new EditorOptionsPage object\n" - "@param title The title of the page\n" - "@param index The position of the page in the tab bar\n" - ) + - method ("view", &EditorOptionsPageImpl::view, - "@brief Gets the view object this page is associated with\n" - ) + - method ("edited", &EditorOptionsPageImpl::call_edited, - "@brief Call this method when some entry widget has changed\n" - "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" - ) + - 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" - ) + - 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" - "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " - "and set the editing widget's state accordingly.\n" - ), - "@brief The plugin framework's editor options page\n" - "\n" - "This object provides a way to establish plugin-specific editor options pages.\n" - "\n" - "The preferred way of communication between the page and the plugin is through " - "configuration parameters. One advantage of this approach is that the current state is " - "automatically persisted.\n" - "\n" - "For this purpose, the editor options page has two methods: 'apply' which is supposed to transfer " - "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " - "configuration parameters into editor widget states. Both methods are called by the system when " - "some transfer is needed.\n" - "\n" - "This class has been introduced in version 0.30.4.\n" -); - -class ConfigPageImpl - : public lay::ConfigPage, public gsi::ObjectBase -{ -public: - ConfigPageImpl (const std::string &title) - : lay::ConfigPage (0), m_title (title) - { - // .. nothing yet .. - } - - virtual std::string title () const - { - return m_title; - } - - void commit_impl (lay::Dispatcher *root) - { - lay::ConfigPage::commit (root); - } - - virtual void commit (lay::Dispatcher *root) - { - if (f_commit.can_issue ()) { - f_commit.issue (&ConfigPageImpl::commit_impl, root); - } else { - ConfigPageImpl::commit_impl (root); - } - } - - void setup_impl (lay::Dispatcher *root) - { - lay::ConfigPage::setup (root); - } - - virtual void setup (lay::Dispatcher *root) - { - if (f_setup.can_issue ()) { - f_setup.issue (&ConfigPageImpl::setup_impl, root); - } else { - ConfigPageImpl::setup_impl (root); - } - } - - gsi::Callback f_commit; - gsi::Callback f_setup; - -private: - tl::weak_ptr mp_view; - tl::weak_ptr mp_dispatcher; - std::string m_title; - std::string m_index; -}; - -ConfigPageImpl *new_config_page (const std::string &title) -{ - return new ConfigPageImpl (title); -} - -Class decl_ConfigPage (QT_EXTERNAL_BASE (QFrame) "lay", "ConfigPage", - constructor ("new", &new_config_page, gsi::arg ("title"), - "@brief Creates a new ConfigPage object\n" - "@param title The title of the page and also the position in the configuration page tree\n" - "\n" - "The title has the form 'Group|Page' - e.g. 'Application|Macro Development IDE' will place " - "the configuration page in the 'Application' group and into the 'Macro Development IDE' page." - ) + - callback ("apply", &ConfigPageImpl::commit, &ConfigPageImpl::f_commit, 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" - ) + - callback ("setup", &ConfigPageImpl::setup, &ConfigPageImpl::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" - "Use \\Dispatcher#get_config on the dispatcher object ('dispatcher' argument) to get a configuration parameter " - "and set the editing widget's state accordingly.\n" - ), - "@brief The plugin framework's configuration page\n" - "\n" - "This object provides a way to establish plugin-specific configuration pages.\n" - "\n" - "The only way of communication between the page and the plugin is through " - "configuration parameters. One advantage of this approach is that the current state is " - "automatically persisted. Configuration parameters can be obtained by the plugin " - "directly from the \\Dispatcher object) or by listening to 'configure' calls.\n" - "\n" - "For the purpose of data transfer, the configuration page has two methods: 'apply' which is supposed to transfer " - "the editor widget's state into configuration parameters. 'setup' does the inverse and transfer " - "configuration parameters into editor widget states. Both methods are called by the system when " - "some transfer is needed.\n" - "\n" - "This class has been introduced in version 0.30.4.\n" -); -#endif - // HACK: used to track if we're inside a create_plugin method and can be sure that "init" is called -static bool s_in_create_plugin = false; +bool s_in_create_plugin = false; -class PluginBase - : public lay::EditorServiceBase +PluginBase::PluginBase () + : lay::EditorServiceBase (), + mp_view (0), mp_dispatcher (0), + m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), + m_snap_to_objects (true), + m_snap_objects_to_grid (true) { -public: - PluginBase () - : lay::EditorServiceBase (), - mp_view (0), mp_dispatcher (0), - m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), - m_snap_to_objects (true), - m_snap_objects_to_grid (true) - { - if (! s_in_create_plugin) { - throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); - } + if (! s_in_create_plugin) { + throw tl::Exception (tl::to_string (tr ("A PluginBase object can only be created in the PluginFactory's create_plugin method"))); } +} - void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) - { - mp_view = view; - mp_dispatcher = dispatcher; - lay::EditorServiceBase::init (view); - } - - void grab_mouse () - { - if (ui ()) { - ui ()->grab_mouse (this, false); - } - } - - void ungrab_mouse () - { - if (ui ()) { - ui ()->ungrab_mouse (this); - } - } - - void set_cursor (int c) - { - if (ui ()) { - lay::ViewService::set_cursor ((enum lay::Cursor::cursor_shape) c); - } - } - - virtual lay::ViewService *view_service_interface () - { - return this; - } - - virtual void menu_activated (const std::string &symbol) - { - if (f_menu_activated.can_issue ()) { - f_menu_activated.issue (&lay::Plugin::menu_activated, symbol); - } else { - lay::Plugin::menu_activated (symbol); - } - } - - db::DPoint snap (db::DPoint p) const - { - // snap according to the grid - if (m_edit_grid == db::DVector ()) { - p = lay::snap_xy (p, m_global_grid); - } else if (m_edit_grid.x () < 1e-6) { - ; // nothing - } else { - p = lay::snap_xy (p, m_edit_grid); - } - - return p; - } - - db::DVector snap_vector (db::DVector v) const - { - // snap according to the grid - if (m_edit_grid == db::DVector ()) { - v = lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint (); - } else if (m_edit_grid.x () < 1e-6) { - ; // nothing - } else { - v = lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint (); - } - - return v; - } - - db::DPoint snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const - { - db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac (ac) : move_ac (ac)); - return snap (ps); - } - - db::DVector snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const - { - return snap_vector (lay::snap_angle (v, connect ? connect_ac (ac) : move_ac (ac))); - } - - db::DPoint snap2 (const db::DPoint &p, bool visualize) - { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); - auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); - if (visualize) { - mouse_cursor_from_snap_details (details); - } - return details.snapped_point; - } - - db::DPoint snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) - { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); - auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); - if (visualize) { - mouse_cursor_from_snap_details (details); - } - return details.snapped_point; - } - - /** - * @brief Captures some edt space configuration events for convencience - */ - void configure_edt (const std::string &name, const std::string &value) - { - edt::EditGridConverter egc; - edt::ACConverter acc; - - if (name == edt::cfg_edit_global_grid) { - egc.from_string (value, m_global_grid); - } else if (name == edt::cfg_edit_grid) { - egc.from_string (value, m_edit_grid); - } else if (name == edt::cfg_edit_snap_to_objects) { - tl::from_string (value, m_snap_to_objects); - } else if (name == edt::cfg_edit_snap_objects_to_grid) { - tl::from_string (value, m_snap_objects_to_grid); - } else if (name == edt::cfg_edit_move_angle_mode) { - acc.from_string (value, m_move_ac); - } else if (name == edt::cfg_edit_connect_angle_mode) { - acc.from_string (value, m_connect_ac); - } else { - lay::EditorServiceBase::configure (name, value); - } - } - - /** - * @brief The implementation does not allow to bypass the base class configuration call - */ - bool configure_impl (const std::string &name, const std::string &value) - { - return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); - } - - // for testing - void configure_test (const std::string &name, const std::string &value) - { - configure_edt (name, value); - } - - virtual bool configure (const std::string &name, const std::string &value) - { - configure_edt (name, value); - return configure_impl (name, value); - } - - /** - * @brief The implementation does not allow to bypass the base class configuration call - */ - virtual void config_finalize_impl () - { - f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginBase::config_finalize) : lay::Plugin::config_finalize (); - } - - virtual void config_finalize () - { - lay::EditorServiceBase::config_finalize (); - config_finalize_impl (); - } - - virtual bool key_event (unsigned int key, unsigned int buttons) - { - if (f_key_event.can_issue ()) { - return f_key_event.issue (&lay::ViewService::key_event, key, buttons); - } else { - return lay::EditorServiceBase::key_event (key, buttons); - } - } - - virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_press_event.can_issue ()) { - return f_mouse_press_event.issue (&PluginBase::mouse_press_event_noref, p, buttons, prio); - } else { - return lay::EditorServiceBase::mouse_press_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_press_event (p, buttons, prio); - } - - virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_click_event.can_issue ()) { - return f_mouse_click_event.issue (&PluginBase::mouse_click_event_noref, p, buttons, prio); - } else { - return lay::EditorServiceBase::mouse_click_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_click_event (p, buttons, prio); - } - - virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_double_click_event.can_issue ()) { - return f_mouse_double_click_event.issue (&PluginBase::mouse_double_click_event_noref, p, buttons, prio); - } else { - return lay::EditorServiceBase::mouse_double_click_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_double_click_event (p, buttons, prio); - } - - virtual bool leave_event (bool prio) - { - if (f_leave_event.can_issue ()) { - return f_leave_event.issue (&lay::ViewService::leave_event, prio); - } else { - return lay::EditorServiceBase::leave_event (prio); - } - } - - virtual bool enter_event (bool prio) - { - if (f_enter_event.can_issue ()) { - return f_enter_event.issue (&lay::ViewService::enter_event, prio); - } else { - return lay::EditorServiceBase::enter_event (prio); - } - } - - virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_move_event.can_issue ()) { - return f_mouse_move_event.issue (&PluginBase::mouse_move_event_noref, p, buttons, prio); - } else { - return lay::EditorServiceBase::mouse_move_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_move_event (p, buttons, prio); - } - - virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_mouse_release_event.can_issue ()) { - return f_mouse_release_event.issue (&PluginBase::mouse_release_event_noref, p, buttons, prio); - } else { - return lay::ViewService::mouse_release_event (p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) - { - return mouse_release_event (p, buttons, prio); - } - - virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) - { - if (f_wheel_event.can_issue ()) { - return f_wheel_event.issue (&PluginBase::wheel_event_noref, delta, horizontal, p, buttons, prio); - } else { - return lay::ViewService::wheel_event (delta, horizontal, p, buttons, prio); - } - } - - // NOTE: this version doesn't take a point reference which allows up to store the point - bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) - { - return wheel_event (delta, horizontal, p, buttons, prio); - } - - virtual void activated () - { - if (f_activated.can_issue ()) { - f_activated.issue (&lay::EditorServiceBase::activated); - } else { - lay::EditorServiceBase::activated (); - } - } - - void deactivated_impl () - { - if (f_deactivated.can_issue ()) { - f_deactivated.issue (&PluginBase::deactivated_impl); - } - } - - virtual void deactivated () - { - lay::EditorServiceBase::deactivated (); - deactivated_impl (); - } - - virtual void drag_cancel () - { - if (f_drag_cancel.can_issue ()) { - f_drag_cancel.issue (&lay::EditorServiceBase::drag_cancel); - } else { - lay::EditorServiceBase::drag_cancel (); - } - } - - virtual void update () - { - if (f_update.can_issue ()) { - f_update.issue (&lay::EditorServiceBase::update); - } else { - lay::EditorServiceBase::update (); - } - } - - void add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) - { - lay::EditorServiceBase::add_mouse_cursor (p, emphasize); - } - - void add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) - { - const lay::CellView &cv = view ()->cellview (cv_index); - if (! cv.is_valid ()) { - return; - } - - int layer = cv->layout ().get_layer_maybe (lp); - if (layer < 0) { - return; - } - - edt::TransformationVariants tv (view ()); - const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); - if (! tv_list || tv_list->empty ()) { - return; - } - - lay::EditorServiceBase::add_mouse_cursor (p, cv_index, cv.context_trans (), *tv_list, emphasize); - } - - void add_edge_marker_dedge (const db::DEdge &p, bool emphasize) - { - lay::EditorServiceBase::add_edge_marker (p, emphasize); - } - - void add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) - { - const lay::CellView &cv = view ()->cellview (cv_index); - if (! cv.is_valid ()) { - return; - } - - int layer = cv->layout ().get_layer_maybe (lp); - if (layer < 0) { - return; - } - - edt::TransformationVariants tv (view ()); - const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); - if (! tv_list || tv_list->empty ()) { - return; - } - - lay::EditorServiceBase::add_edge_marker (p, cv_index, cv.context_trans (), *tv_list, emphasize); - } - - // for testing - bool has_tracking_position_test () const - { - return has_tracking_position (); - } - - bool has_tracking_position_base () const - { - return lay::EditorServiceBase::has_tracking_position (); - } - - virtual bool has_tracking_position () const - { - if (f_has_tracking_position.can_issue ()) { - return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); - } else { - return has_tracking_position_base (); - } - } - - // for testing - db::DPoint tracking_position_test () const - { - return tracking_position (); - } - - db::DPoint tracking_position_base () const - { - return lay::EditorServiceBase::tracking_position (); - } - - virtual db::DPoint tracking_position () const - { - if (f_tracking_position.can_issue ()) { - return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); - } else { - return tracking_position_base (); - } - } - - lay::LayoutViewBase *view () const - { - return const_cast (mp_view.get ()); - } - - lay::Dispatcher *dispatcher () const - { - return const_cast (mp_dispatcher.get ()); - } - -#if defined(HAVE_QTBINDINGS) - std::vector editor_options_pages () - { - lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); - if (!eo_pages) { - return std::vector (); - } else { - std::vector pages; - for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { - if ((*p)->plugin_declaration () == plugin_declaration ()) { - pages.push_back (*p); - } - } - return pages; - } - } -#endif - - gsi::Callback f_menu_activated; - gsi::Callback f_configure; - gsi::Callback f_config_finalize; - gsi::Callback f_key_event; - gsi::Callback f_mouse_press_event; - gsi::Callback f_mouse_click_event; - gsi::Callback f_mouse_double_click_event; - gsi::Callback f_leave_event; - gsi::Callback f_enter_event; - gsi::Callback f_mouse_move_event; - gsi::Callback f_mouse_release_event; - gsi::Callback f_wheel_event; - gsi::Callback f_activated; - gsi::Callback f_deactivated; - gsi::Callback f_drag_cancel; - gsi::Callback f_update; - gsi::Callback f_has_tracking_position; - gsi::Callback f_tracking_position; - -private: - tl::weak_ptr mp_view; - tl::weak_ptr mp_dispatcher; - - // Angle constraints and grids - lay::angle_constraint_type m_connect_ac, m_move_ac; - db::DVector m_edit_grid; - bool m_snap_to_objects; - bool m_snap_objects_to_grid; - db::DVector m_global_grid; - - lay::angle_constraint_type connect_ac (lay::angle_constraint_type ac) const - { - // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint - return ac != lay::AC_Global ? ac : m_connect_ac; - } - - lay::angle_constraint_type move_ac (lay::angle_constraint_type ac) const - { - // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint - return ac != lay::AC_Global ? ac : m_move_ac; - } -}; - -static std::map s_factories; - -class PluginFactoryBase - : public lay::PluginDeclaration +void +PluginBase::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) { -public: - PluginFactoryBase () - : PluginDeclaration (), - m_implements_mouse_mode (true), mp_registration (0) - { - // .. nothing yet .. + mp_view = view; + mp_dispatcher = dispatcher; + lay::EditorServiceBase::init (view); +} + +void +PluginBase::grab_mouse () +{ + if (ui ()) { + ui ()->grab_mouse (this, false); + } +} + +void +PluginBase::ungrab_mouse () +{ + if (ui ()) { + ui ()->ungrab_mouse (this); + } +} + +void +PluginBase::set_cursor (int c) +{ + if (ui ()) { + lay::ViewService::set_cursor ((enum lay::Cursor::cursor_shape) c); + } +} + +void +PluginBase::menu_activated (const std::string &symbol) +{ + if (f_menu_activated.can_issue ()) { + f_menu_activated.issue (&lay::Plugin::menu_activated, symbol); + } else { + lay::Plugin::menu_activated (symbol); + } +} + +db::DPoint +PluginBase::snap (db::DPoint p) const +{ + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + p = lay::snap_xy (p, m_global_grid); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + p = lay::snap_xy (p, m_edit_grid); } - ~PluginFactoryBase () - { - for (auto f = s_factories.begin (); f != s_factories.end (); ++f) { - if (f->second == this) { - s_factories.erase (f); - break; - } - } + return p; +} - delete mp_registration; - mp_registration = 0; +db::DVector +PluginBase::snap_vector (db::DVector v) const +{ + // snap according to the grid + if (m_edit_grid == db::DVector ()) { + v = lay::snap_xy (db::DPoint () + v, m_global_grid) - db::DPoint (); + } else if (m_edit_grid.x () < 1e-6) { + ; // nothing + } else { + v = lay::snap_xy (db::DPoint () + v, m_edit_grid) - db::DPoint (); } - void register_gsi (int position, const char *name, const char *title) - { - register_gsi2 (position, name, title, 0); + return v; +} + +db::DPoint +PluginBase::snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const +{ + db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac (ac) : move_ac (ac)); + return snap (ps); +} + +db::DVector +PluginBase::snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const +{ + return snap_vector (lay::snap_angle (v, connect ? connect_ac (ac) : move_ac (ac))); +} + +db::DPoint +PluginBase::snap2 (const db::DPoint &p, bool visualize) +{ + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; +} + +db::DPoint +PluginBase::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) +{ + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); + if (visualize) { + mouse_cursor_from_snap_details (details); + } + return details.snapped_point; +} + +/** + * @brief Captures some edt space configuration events for convencience + */ +void +PluginBase::configure_edt (const std::string &name, const std::string &value) +{ + edt::EditGridConverter egc; + edt::ACConverter acc; + + if (name == edt::cfg_edit_global_grid) { + egc.from_string (value, m_global_grid); + } else if (name == edt::cfg_edit_grid) { + egc.from_string (value, m_edit_grid); + } else if (name == edt::cfg_edit_snap_to_objects) { + tl::from_string (value, m_snap_to_objects); + } else if (name == edt::cfg_edit_snap_objects_to_grid) { + tl::from_string (value, m_snap_objects_to_grid); + } else if (name == edt::cfg_edit_move_angle_mode) { + acc.from_string (value, m_move_ac); + } else if (name == edt::cfg_edit_connect_angle_mode) { + acc.from_string (value, m_connect_ac); + } else { + lay::EditorServiceBase::configure (name, value); + } +} + +/** + * @brief The implementation does not allow to bypass the base class configuration call + */ +bool +PluginBase::configure_impl (const std::string &name, const std::string &value) +{ + return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); +} + +// for testing +void + PluginBase::configure_test (const std::string &name, const std::string &value) +{ + configure_edt (name, value); +} + +bool +PluginBase::configure (const std::string &name, const std::string &value) +{ + configure_edt (name, value); + return configure_impl (name, value); +} + +/** + * @brief The implementation does not allow to bypass the base class configuration call + */ +void +PluginBase::config_finalize_impl () +{ + f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginBase::config_finalize) : lay::Plugin::config_finalize (); +} + +void +PluginBase::config_finalize () +{ + lay::EditorServiceBase::config_finalize (); + config_finalize_impl (); +} + +bool +PluginBase::key_event (unsigned int key, unsigned int buttons) +{ + if (f_key_event.can_issue ()) { + return f_key_event.issue (&lay::ViewService::key_event, key, buttons); + } else { + return lay::EditorServiceBase::key_event (key, buttons); + } +} + +bool +PluginBase::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_press_event.can_issue ()) { + return f_mouse_press_event.issue (&PluginBase::mouse_press_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_press_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_press_event (p, buttons, prio); +} + +bool +PluginBase::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_click_event.can_issue ()) { + return f_mouse_click_event.issue (&PluginBase::mouse_click_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_click_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_click_event (p, buttons, prio); +} + +bool +PluginBase::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_double_click_event.can_issue ()) { + return f_mouse_double_click_event.issue (&PluginBase::mouse_double_click_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_double_click_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_double_click_event (p, buttons, prio); +} + +bool +PluginBase::leave_event (bool prio) +{ + if (f_leave_event.can_issue ()) { + return f_leave_event.issue (&lay::ViewService::leave_event, prio); + } else { + return lay::EditorServiceBase::leave_event (prio); + } +} + +bool +PluginBase::enter_event (bool prio) +{ + if (f_enter_event.can_issue ()) { + return f_enter_event.issue (&lay::ViewService::enter_event, prio); + } else { + return lay::EditorServiceBase::enter_event (prio); + } +} + +bool +PluginBase::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_move_event.can_issue ()) { + return f_mouse_move_event.issue (&PluginBase::mouse_move_event_noref, p, buttons, prio); + } else { + return lay::EditorServiceBase::mouse_move_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_move_event (p, buttons, prio); +} + +bool +PluginBase::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_mouse_release_event.can_issue ()) { + return f_mouse_release_event.issue (&PluginBase::mouse_release_event_noref, p, buttons, prio); + } else { + return lay::ViewService::mouse_release_event (p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) +{ + return mouse_release_event (p, buttons, prio); +} + +bool +PluginBase::wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) +{ + if (f_wheel_event.can_issue ()) { + return f_wheel_event.issue (&PluginBase::wheel_event_noref, delta, horizontal, p, buttons, prio); + } else { + return lay::ViewService::wheel_event (delta, horizontal, p, buttons, prio); + } +} + +// NOTE: this version doesn't take a point reference which allows up to store the point +bool +PluginBase::wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) +{ + return wheel_event (delta, horizontal, p, buttons, prio); +} + +void +PluginBase::activated () +{ + if (f_activated.can_issue ()) { + f_activated.issue (&lay::EditorServiceBase::activated); + } else { + lay::EditorServiceBase::activated (); + } +} + +void +PluginBase::deactivated_impl () +{ + if (f_deactivated.can_issue ()) { + f_deactivated.issue (&PluginBase::deactivated_impl); + } +} + +void +PluginBase::deactivated () +{ + lay::EditorServiceBase::deactivated (); + deactivated_impl (); +} + +void +PluginBase::drag_cancel () +{ + if (f_drag_cancel.can_issue ()) { + f_drag_cancel.issue (&lay::EditorServiceBase::drag_cancel); + } else { + lay::EditorServiceBase::drag_cancel (); + } +} + +void +PluginBase::update () +{ + if (f_update.can_issue ()) { + f_update.issue (&lay::EditorServiceBase::update); + } else { + lay::EditorServiceBase::update (); + } +} + +void +PluginBase::add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) +{ + lay::EditorServiceBase::add_mouse_cursor (p, emphasize); +} + +void +PluginBase::add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +{ + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; } - void register_gsi2 (int position, const char *name, const char *title, const char *icon) - { - // makes the object owned by the C++ side - keep (); - - // remove an existing factory with the same name - std::map ::iterator f = s_factories.find (name); - if (f != s_factories.end () && f->second != this) { - // NOTE: this also removes the plugin from the s_factories list - delete f->second; - } - s_factories[name] = this; - - // cancel any previous registration and register (again) - delete mp_registration; - mp_registration = new tl::RegisteredClass (this, position, name, false /*does not own object*/); - - m_mouse_mode_title = name; - if (title) { - m_mouse_mode_title += "\t"; - m_mouse_mode_title += title; - } - if (icon) { - m_mouse_mode_title += "\t<"; - m_mouse_mode_title += icon; - m_mouse_mode_title += ">"; - } - - // (dynamically) register the plugin class. This will also call initialize if the main window is - // present already. - register_plugin (); + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; } - virtual bool configure (const std::string &name, const std::string &value) - { - if (f_configure.can_issue ()) { - return f_configure.issue (&lay::PluginDeclaration::configure, name, value); - } else { - return lay::PluginDeclaration::configure (name, value); - } + edt::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; } - virtual void config_finalize () - { - if (f_config_finalize.can_issue ()) { - f_config_finalize.issue (&lay::PluginDeclaration::config_finalize); - } else { - lay::PluginDeclaration::config_finalize (); - } + lay::EditorServiceBase::add_mouse_cursor (p, cv_index, cv.context_trans (), *tv_list, emphasize); +} + +void +PluginBase::add_edge_marker_dedge (const db::DEdge &p, bool emphasize) +{ + lay::EditorServiceBase::add_edge_marker (p, emphasize); +} + +void +PluginBase::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +{ + const lay::CellView &cv = view ()->cellview (cv_index); + if (! cv.is_valid ()) { + return; } - virtual bool menu_activated (const std::string &symbol) const - { - if (f_menu_activated.can_issue ()) { - return f_menu_activated.issue (&lay::PluginDeclaration::menu_activated, symbol); - } else { - return lay::PluginDeclaration::menu_activated (symbol); - } + int layer = cv->layout ().get_layer_maybe (lp); + if (layer < 0) { + return; } - virtual void initialize (lay::Dispatcher *root) - { - if (f_initialize.can_issue ()) { - f_initialize.issue (&lay::PluginDeclaration::initialize, root); - } else { - lay::PluginDeclaration::initialize (root); - } + edt::TransformationVariants tv (view ()); + const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); + if (! tv_list || tv_list->empty ()) { + return; } - - virtual void uninitialize (lay::Dispatcher *root) - { - if (f_uninitialize.can_issue ()) { - f_uninitialize.issue (&lay::PluginDeclaration::uninitialize, root); - } else { - lay::PluginDeclaration::uninitialize (root); - } + + lay::EditorServiceBase::add_edge_marker (p, cv_index, cv.context_trans (), *tv_list, emphasize); +} + +// for testing +bool +PluginBase::has_tracking_position_test () const +{ + return has_tracking_position (); +} + +bool +PluginBase::has_tracking_position_base () const +{ + return lay::EditorServiceBase::has_tracking_position (); +} + +bool +PluginBase::has_tracking_position () const +{ + if (f_has_tracking_position.can_issue ()) { + return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); + } else { + return has_tracking_position_base (); } +} + +// for testing +db::DPoint +PluginBase::tracking_position_test () const +{ + return tracking_position (); +} + +db::DPoint +PluginBase::tracking_position_base () const +{ + return lay::EditorServiceBase::tracking_position (); +} + +db::DPoint +PluginBase::tracking_position () const +{ + if (f_tracking_position.can_issue ()) { + return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); + } else { + return tracking_position_base (); + } +} #if defined(HAVE_QTBINDINGS) - void add_editor_options_page (EditorOptionsPageImpl *page) const - { - page->keep (); - m_editor_options_pages.push_back (page); - } - - void get_editor_options_pages_impl () const - { - // .. nothing here .. - } - - virtual void get_editor_options_pages (std::vector &pages_out, lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) const - { - try { - - m_editor_options_pages.clear (); - - if (f_get_editor_options_pages.can_issue ()) { - f_get_editor_options_pages.issue (&PluginFactoryBase::get_editor_options_pages_impl); - } else { - get_editor_options_pages_impl (); +std::vector +PluginBase::editor_options_pages () +{ + lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); + if (!eo_pages) { + return std::vector (); + } else { + std::vector pages; + for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { + if ((*p)->plugin_declaration () == plugin_declaration ()) { + pages.push_back (*p); } - - for (auto i = m_editor_options_pages.begin (); i != m_editor_options_pages.end (); ++i) { - if (*i) { - (*i)->init (view, dispatcher); - (*i)->set_plugin_declaration (this); - pages_out.push_back (*i); - } - } - - m_editor_options_pages.clear (); - - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } catch (std::exception &ex) { - tl::error << ex.what (); - } catch (...) { } + return pages; } - - void add_config_page (ConfigPageImpl *page) const - { - page->keep (); - m_config_pages.push_back (page); - } - - void get_config_pages_impl () const - { - // .. nothing here .. - } - - virtual std::vector > config_pages (QWidget *parent) const - { - std::vector > pages_out; - - try { - - m_config_pages.clear (); - - if (f_config_pages.can_issue ()) { - f_config_pages.issue (&PluginFactoryBase::get_config_pages_impl); - } else { - get_config_pages_impl (); - } - - pages_out.clear (); - for (auto i = m_config_pages.begin (); i != m_config_pages.end (); ++i) { - if (*i) { - (*i)->setParent (parent); - pages_out.push_back (std::make_pair ((*i)->title (), *i)); - } - } - - m_config_pages.clear (); - - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } catch (std::exception &ex) { - tl::error << ex.what (); - } catch (...) { - } - - return pages_out; - } +} #endif - virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const - { - if (f_create_plugin.can_issue ()) { - return create_plugin_gsi (manager, root, view); - } else { - return lay::PluginDeclaration::create_plugin (manager, root, view); - } - } +lay::angle_constraint_type +PluginBase::connect_ac (lay::angle_constraint_type ac) const +{ + // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint + return ac != lay::AC_Global ? ac : m_connect_ac; +} - virtual gsi::PluginBase *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const - { - s_in_create_plugin = true; - - gsi::PluginBase *ret = 0; - try { - - ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); - if (ret) { - ret->init (view, root); - } - - } catch (tl::Exception &ex) { - tl::error << ex.msg (); - } catch (std::exception &ex) { - tl::error << ex.what (); - } catch (...) { - } - - s_in_create_plugin = false; - - return ret; - } - - virtual void get_menu_entries (std::vector &menu_entries) const - { - menu_entries = m_menu_entries; - } - - virtual void get_options (std::vector < std::pair > &options) const - { - options = m_options; - } - - void add_menu_entry1 (const std::string &menu_name, const std::string &insert_pos) - { - m_menu_entries.push_back (lay::separator (menu_name, insert_pos)); - } - - void add_menu_entry2 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title) - { - m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); - } - - void add_menu_entry_copy (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string ©_from) - { - m_menu_entries.push_back (lay::menu_item_copy (symbol, menu_name, insert_pos, copy_from)); - } - - void add_submenu (const std::string &menu_name, const std::string &insert_pos, const std::string &title) - { - m_menu_entries.push_back (lay::submenu (menu_name, insert_pos, title)); - } - - void add_config_menu_item (const std::string &menu_name, const std::string &insert_pos, const std::string &title, const std::string &cname, const std::string &cvalue) - { - m_menu_entries.push_back (lay::config_menu_item (menu_name, insert_pos, title, cname, cvalue)); - } - - void add_menu_entry3 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title, bool sub_menu) - { - if (sub_menu) { - m_menu_entries.push_back (lay::submenu (symbol, menu_name, insert_pos, title)); - } else { - m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); - } - } - - void add_option (const std::string &name, const std::string &default_value) - { - m_options.push_back (std::make_pair (name, default_value)); - } - - void has_tool_entry (bool f) - { - m_implements_mouse_mode = f; - } - - virtual bool implements_mouse_mode (std::string &title) const - { - title = m_mouse_mode_title; - return m_implements_mouse_mode; - } - - gsi::Callback f_create_plugin; - gsi::Callback f_initialize; - gsi::Callback f_uninitialize; - gsi::Callback f_configure; - gsi::Callback f_config_finalize; - gsi::Callback f_menu_activated; - gsi::Callback f_get_editor_options_pages; - gsi::Callback f_config_pages; - -private: - std::vector > m_options; - std::vector m_menu_entries; - bool m_implements_mouse_mode; - std::string m_mouse_mode_title; - tl::RegisteredClass *mp_registration; - mutable std::vector m_config_pages; - mutable std::vector m_editor_options_pages; -}; - -Class decl_PluginFactory ("lay", "PluginFactory", - method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), - "@brief Registers the plugin factory\n" - "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" - "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" - "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" - "\n" - "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " - "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" - ) + - method ("register", &PluginFactoryBase::register_gsi2, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), gsi::arg ("icon"), - "@brief Registers the plugin factory\n" - "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" - "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" - "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" - "@param icon The path to the icon that appears in the tool bar and menu related to this plugin.\n" - "\n" - "This version also allows registering an icon for the tool bar.\n" - "\n" - "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " - "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" - ) + - callback ("configure", &gsi::PluginFactoryBase::configure, &gsi::PluginFactoryBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), - "@brief Gets called for configuration events for the plugin singleton\n" - "This method can be reimplemented to receive configuration events " - "for the plugin singleton. Before a configuration can be received it must be " - "registered by calling \\add_option in the plugin factories' constructor.\n" - "\n" - "The implementation of this method may return true indicating that the configuration request " - "will not be handled by further modules. It's more cooperative to return false which will " - "make the system distribute the configuration request to other receivers as well.\n" - "\n" - "@param name The configuration key\n" - "@param value The value of the configuration variable\n" - "@return True to stop further processing\n" - ) + - callback ("config_finalize", &gsi::PluginFactoryBase::config_finalize, &gsi::PluginFactoryBase::f_config_finalize, - "@brief Gets called after a set of configuration events has been sent\n" - "This method can be reimplemented and is called after a set of configuration events " - "has been sent to the plugin factory singleton with \\configure. It can be used to " - "set up user interfaces properly for example.\n" - ) + - callback ("menu_activated", &gsi::PluginFactoryBase::menu_activated, &gsi::PluginFactoryBase::f_menu_activated, gsi::arg ("symbol"), - "@brief Gets called when a menu item is selected\n" - "\n" - "Usually, menu-triggered functionality is implemented in the per-view instance of the plugin. " - "However, using this method it is possible to implement functionality globally for all plugin " - "instances. The symbol is the string registered with the specific menu item in the \\add_menu_item " - "call.\n" - "\n" - "If this method was handling the menu event, it should return true. This indicates that the event " - "will not be propagated to other plugins hence avoiding duplicate calls.\n" - ) + - callback ("initialized", &gsi::PluginFactoryBase::initialize, &gsi::PluginFactoryBase::f_initialize, gsi::arg ("dispatcher"), - "@brief Gets called when the plugin singleton is initialized, i.e. when the application has been started.\n" - "@param dispatcher The reference to the \\MainWindow object\n" - ) + - callback ("uninitialized", &gsi::PluginFactoryBase::uninitialize, &gsi::PluginFactoryBase::f_uninitialize, gsi::arg ("dispatcher"), - "@brief Gets called when the application shuts down and the plugin is unregistered\n" - "This event can be used to free resources allocated with this factory singleton.\n" - "@param dispatcher The reference to the \\MainWindow object\n" - ) + - factory_callback ("create_plugin", &gsi::PluginFactoryBase::create_plugin_gsi, &gsi::PluginFactoryBase::f_create_plugin, gsi::arg ("manager"), gsi::arg ("dispatcher"), gsi::arg ("view"), - "@brief Creates the plugin\n" - "This is the basic functionality that the factory must provide. This method must create a plugin of the " - "specific type.\n" - "@param manager The database manager object responsible for handling database transactions\n" - "@param dispatcher The reference to the \\MainWindow object\n" - "@param view The \\LayoutView that is plugin is created for\n" - "@return The new \\Plugin implementation object\n" - ) + - method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry1, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), - "@brief Specifies a separator\n" - "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" - "This specific call inserts a separator at the given position (insert_pos). The position uses abstract menu item paths " - "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" - ) + - method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry2, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), - "@brief Specifies a menu item\n" - "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" - "This specific call inserts a menu item at the specified position (insert_pos). The position uses abstract menu item paths " - "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" - "When the menu item is selected \"symbol\" is the string that is sent to the \\menu_activated callback (either the global one for the factory ot the one of the per-view plugin instance).\n" - "\n" - "@param symbol The string to send to the plugin if the menu is triggered\n" - "@param menu_name The name of entry to create at the given position\n" - "@param insert_pos The position where to create the entry\n" - "@param title The title string for the item. The title can contain a keyboard shortcut in round braces after the title text, i.e. \"My Menu Item(F12)\"\n" - ) + - method ("#add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry3, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("sub_menu"), - "@brief Specifies a menu item or sub-menu\n" - "Similar to the previous form of \"add_menu_entry\", but this version allows also to create sub-menus by setting the " - "last parameter to \"true\".\n" - "\n" - "With version 0.27 it's more convenient to use \\add_submenu." - ) + - method ("add_menu_item_clone", &gsi::PluginFactoryBase::add_menu_entry_copy, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("copy_from"), - "@brief Specifies a menu item as a clone of another one\n" - "Using this method, a menu item can be made a clone of another entry (given as path by 'copy_from').\n" - "The new item will share the \\Action object with the original one, so manipulating the action will change both the original entry " - "and the new entry.\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_submenu", &gsi::PluginFactoryBase::add_submenu, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), - "@brief Specifies a menu item or sub-menu\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_config_menu_item", &gsi::PluginFactoryBase::add_config_menu_item, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("cname"), gsi::arg ("cvalue"), - "@brief Adds a configuration menu item\n" - "\n" - "Menu items created this way will send a configuration request with 'cname' as the configuration parameter name " - "and 'cvalue' as the configuration parameter value.\n" - "If 'cvalue' is a string with a single question mark (\"?\"), the item is a check box that reflects the boolean " - "value of the configuration item.\n" - "\n" - "This method has been introduced in version 0.27." - ) + - method ("add_option", &gsi::PluginFactoryBase::add_option, gsi::arg ("name"), gsi::arg ("default_value"), - "@brief Specifies configuration variables.\n" - "Call this method in the factory constructor to add configuration key/value pairs to the configuration repository. " - "Without specifying configuration variables, the status of a plugin cannot be persisted. " - "\n\n" - "Once the configuration variables are known, they can be retrieved on demand using \"get_config\" from " - "\\MainWindow or listening to \\configure callbacks (either in the factory or the plugin instance). Configuration variables can " - "be set using \"set_config\" from \\MainWindow. This scheme also works without registering the configuration options, but " - "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." - ) + -#if defined(HAVE_QTBINDINGS) - method ("add_editor_options_page", &PluginFactoryBase::add_editor_options_page, gsi::arg ("page"), - "@brief Adds the given editor options page\n" - "See \\create_editor_options_pages how to use this function. The method is effective only in " - "the reimplementation context of this function.\n" - "\n" - "This method has been introduced in version 0.30.4." - ) + - callback ("create_editor_options_pages", &PluginFactoryBase::get_editor_options_pages_impl, &PluginFactoryBase::f_get_editor_options_pages, - "@brief Creates the editor option pages\n" - "The editor option pages are widgets of type \\EditorOptionsPage. These Qt widgets " - "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " - "its mode is selected. Use this method to provide customized pages that will be displayed in the " - "editor options dock.\n" - "\n" - "In order to create config pages, instantiate a \\EditorOptionsPage object and " - "call \\add_editor_options_page to register it.\n" - "\n" - "This method has been introduced in version 0.30.4." - ) + - method ("add_config_page", &PluginFactoryBase::add_config_page, gsi::arg ("page"), - "@brief Adds the given configuration page\n" - "See \\create_config_pages how to use this function. The method is effective only in " - "the reimplementation context of this function.\n" - "\n" - "This method has been introduced in version 0.30.4." - ) + - callback ("create_config_pages", &PluginFactoryBase::get_config_pages_impl, &PluginFactoryBase::f_config_pages, - "@brief Creates the configuration widgets\n" - "The configuration pages are widgets that are displayed in the " - "configuration dialog ('File/Setup'). Every plugin can create multiple such " - "widgets and specify, where these widgets are displayed. The widgets are of type \\ConfigPage.\n" - "\n" - "The title string also specifies the location of the widget in the " - "configuration page hierarchy. See \\ConfigPage for more details.\n" - "\n" - "In order to create config pages, instantiate a \\ConfigPage object and " - "call \\add_config_page to register it.\n" - "\n" - "This method has been introduced in version 0.30.4." - ) + -#endif - method ("has_tool_entry=", &gsi::PluginFactoryBase::has_tool_entry, gsi::arg ("f"), - "@brief Enables or disables the tool bar entry\n" - "Initially this property is set to true. This means that the plugin will have a visible entry in the toolbar. " - "This property can be set to false to disable this feature. In that case, the title and icon given on registration will be ignored. " - ), - "@brief The plugin framework's plugin factory object\n" - "\n" - "Plugins are components that extend KLayout's functionality in various aspects. Scripting support exists " - "currently for providing mouse mode handlers and general on-demand functionality connected with a menu " - "entry.\n" - "\n" - "Plugins are objects that implement the \\Plugin interface. Each layout view is associated with one instance " - "of such an object. The PluginFactory is a singleton which is responsible for creating \\Plugin objects and " - "providing certain configuration information such as where to put the menu items connected to this plugin and " - "what configuration keys are used.\n" - "\n" - "An implementation of PluginFactory must at least provide an implementation of \\create_plugin. This method " - "must instantiate a new object of the specific plugin.\n" - "\n" - "After the factory has been created, it must be registered in the system using one of the \\register methods. " - "It is therefore recommended to put the call to \\register at the end of the \"initialize\" method. For the registration " - "to work properly, the menu items must be defined before \\register is called.\n" - "\n" - "The following features can also be implemented:\n" - "\n" - "@
    \n" - " @
  • Reserve keys in the configuration file using \\add_option in the constructor@
  • \n" - " @
  • Create menu items by using \\add_menu_entry in the constructor@
  • \n" - " @
  • Set the title for the mode entry that appears in the tool bar using the \\register argument@
  • \n" - " @
  • Provide global functionality (independent from the layout view) using \\configure or \\menu_activated@
  • \n" - "@
\n" - "\n" - "This is a simple example for a plugin in Ruby. It switches the mouse cursor to a 'cross' cursor when it is active:\n" - "\n" - "@code\n" - "class PluginTestFactory < RBA::PluginFactory\n" - "\n" - " # Constructor\n" - " def initialize\n" - " # registers the new plugin class at position 100000 (at the end), with name\n" - " # \"my_plugin_test\" and title \"My plugin test\"\n" - " register(100000, \"my_plugin_test\", \"My plugin test\")\n" - " end\n" - " \n" - " # Create a new plugin instance of the custom type\n" - " def create_plugin(manager, dispatcher, view)\n" - " return PluginTest.new\n" - " end\n" - "\n" - "end\n" - "\n" - "# The plugin class\n" - "class PluginTest < RBA::Plugin\n" - " def mouse_moved_event(p, buttons, prio)\n" - " if prio\n" - " # Set the cursor to cross if our plugin is active.\n" - " set_cursor(RBA::Cursor::Cross)\n" - " end\n" - " # Returning false indicates that we don't want to consume the event.\n" - " # This way for example the cursor position tracker still works.\n" - " false\n" - " end\n" - " def mouse_click_event(p, buttons, prio)\n" - " if prio\n" - " puts \"mouse button clicked.\"\n" - " # This indicates we want to consume the event and others don't receive the mouse click\n" - " # with prio = false.\n" - " return true\n" - " end\n" - " # don't consume the event if we are not active.\n" - " false\n" - " end\n" - "end\n" - "\n" - "# Instantiate the new plugin factory.\n" - "PluginTestFactory.new\n" - "@/code\n" - "\n" - "This class has been introduced in version 0.22.\n" -); +lay::angle_constraint_type +PluginBase::move_ac (lay::angle_constraint_type ac) const +{ + // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint + return ac != lay::AC_Global ? ac : m_move_ac; +} Class decl_Plugin ("lay", "Plugin", callback ("menu_activated", &gsi::PluginBase::menu_activated, &gsi::PluginBase::f_menu_activated, gsi::arg ("symbol"), @@ -1698,7 +919,7 @@ static int cursor_shape_open_hand () { return int (lay::Cursor::open_hand); } static int cursor_shape_closed_hand () { return int (lay::Cursor::closed_hand); } Class decl_Cursor ("lay", "Cursor", - method ("None", &cursor_shape_none, "@brief 'No cursor (default)' constant for \\set_cursor (resets cursor to default)") + + method ("None", &cursor_shape_none, "@brief 'No cursor (default)' constant for \\Plugin#set_cursor (resets cursor to default)") + method ("Arrow", &cursor_shape_arrow, "@brief 'Arrow cursor' constant") + method ("UpArrow", &cursor_shape_up_arrow, "@brief 'Upward arrow cursor' constant") + method ("Cross", &cursor_shape_cross, "@brief 'Cross cursor' constant") + @@ -1719,7 +940,7 @@ Class decl_Cursor ("lay", "Cursor", method ("OpenHand", &cursor_shape_open_hand, "@brief 'Open hand cursor' constant") + method ("ClosedHand", &cursor_shape_closed_hand, "@brief 'Closed hand cursor' constant"), "@brief The namespace for the cursor constants\n" - "This class defines the constants for the cursor setting (for example for class \\Plugin, method set_cursor)." + "This class defines the constants for the cursor setting (for example for method \\Plugin#set_cursor)." "\n" "This class has been introduced in version 0.22.\n" ); @@ -1791,112 +1012,4 @@ Class decl_KeyCode ("lay", "KeyCode", "This class has been introduced in version 0.28.\n" ); -static std::vector -get_config_names (lay::Dispatcher *dispatcher) -{ - std::vector names; - dispatcher->get_config_names (names); - return names; -} - -static lay::Dispatcher *dispatcher_instance () -{ - return lay::Dispatcher::instance (); -} - -static tl::Variant get_config (lay::Dispatcher *dispatcher, const std::string &name) -{ - std::string value; - if (dispatcher->config_get (name, value)) { - return tl::Variant (value); - } else { - return tl::Variant (); - } -} - -/** - * @brief Exposes the Dispatcher interface - * - * This interface is intentionally not derived from Plugin. It is used currently to - * identify the dispatcher node for configuration. The Plugin nature of this interface - * is somewhat artificial and may be removed later. - * - * TODO: this is a duplicate of the respective methods in LayoutView and Application. - * This is intentional since we don't want to spend the only derivation path on this. - * Once there is a mixin concept, provide a path through that concept. - */ -Class decl_Dispatcher ("lay", "Dispatcher", - method ("clear_config", &lay::Dispatcher::clear_config, - "@brief Clears the configuration parameters\n" - ) + - method ("instance", &dispatcher_instance, - "@brief Gets the singleton instance of the Dispatcher object\n" - "\n" - "@return The instance\n" - ) + - method ("write_config", &lay::Dispatcher::write_config, gsi::arg ("file_name"), - "@brief Writes configuration to a file\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "If the configuration file cannot be written, false \n" - "is returned but no exception is thrown.\n" - ) + - method ("read_config", &lay::Dispatcher::read_config, gsi::arg ("file_name"), - "@brief Reads the configuration from a file\n" - "@return A value indicating whether the operation was successful\n" - "\n" - "This method silently does nothing, if the config file does not\n" - "exist. If it does and an error occurred, the error message is printed\n" - "on stderr. In both cases, false is returned.\n" - ) + - method_ext ("get_config", &get_config, gsi::arg ("name"), - "@brief Gets the value of a local configuration parameter\n" - "\n" - "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" - "\n" - "@return The value of the parameter or nil if there is no such parameter\n" - ) + - method ("set_config", (void (lay::Dispatcher::*) (const std::string &, const std::string &)) &lay::Dispatcher::config_set, gsi::arg ("name"), gsi::arg ("value"), - "@brief Set a local configuration parameter with the given name to the given value\n" - "\n" - "@param name The name of the configuration parameter to set\n" - "@param value The value to which to set the configuration parameter\n" - "\n" - "This method sets a configuration parameter with the given name to the given value. " - "Values can only be strings. Numerical values have to be converted into strings first. " - "Local configuration parameters override global configurations for this specific view. " - "This allows for example to override global settings of background colors. " - "Any local settings are not written to the configuration file. " - ) + - method_ext ("get_config_names", &get_config_names, - "@brief Gets the configuration parameter names\n" - "\n" - "@return A list of configuration parameter names\n" - "\n" - "This method returns the names of all known configuration parameters. These names can be used to " - "get and set configuration parameter values.\n" - ) + - method ("commit_config", &lay::Dispatcher::config_end, - "@brief Commits the configuration settings\n" - "\n" - "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " - "After a sequence of \\set_config calls, this method should be called to activate the " - "settings made by these calls.\n" - ), - "@brief Root of the configuration space in the plugin context and menu dispatcher\n" - "\n" - "This class provides access to the root configuration space in the context " - "of plugin programming. You can use this class to obtain configuration parameters " - "from the configuration tree during plugin initialization. However, the " - "preferred way of plugin configuration is through \\Plugin#configure.\n" - "\n" - "Currently, the application object provides an identical entry point for configuration modification. " - "For example, \"Application::instance.set_config\" is identical to \"Dispatcher::instance.set_config\". " - "Hence there is little motivation for the Dispatcher class currently and " - "this interface may be modified or removed in the future." - "\n" - "This class has been introduced in version 0.25 as 'PluginRoot'.\n" - "It is renamed and enhanced as 'Dispatcher' in 0.27." -); - } diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h new file mode 100644 index 000000000..7bf5ce48d --- /dev/null +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -0,0 +1,161 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef _HDR_gsiDeclLayPlugin +#define _HDR_gsiDeclLayPlugin + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" + +#include "layEditorServiceBase.h" +#include "layLayoutViewBase.h" + +namespace gsi +{ + +class PluginBase + : public lay::EditorServiceBase +{ +public: + PluginBase (); + + void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); + void grab_mouse (); + void ungrab_mouse (); + void set_cursor (int c); + + virtual void menu_activated (const std::string &symbol); + db::DPoint snap (db::DPoint p) const; + db::DVector snap_vector (db::DVector v) const; + db::DPoint snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const; + db::DVector snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const; + db::DPoint snap2 (const db::DPoint &p, bool visualize); + db::DPoint snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize); + + // Captures some edt space configuration events for convencience + void configure_edt (const std::string &name, const std::string &value); + // NOTE: The implementation does not allow to bypass the base class configuration call + bool configure_impl (const std::string &name, const std::string &value); + // for testing + void configure_test (const std::string &name, const std::string &value); + virtual bool configure (const std::string &name, const std::string &value); + // NOTE: The implementation does not allow to bypass the base class configuration call + virtual void config_finalize_impl (); + virtual void config_finalize (); + virtual bool key_event (unsigned int key, unsigned int buttons); + virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) ; + // NOTE: this version doesn't take a point reference which allows up to store the point + bool mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); + // NOTE: this version doesn't take a point reference which allows up to store the point + bool mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); + // NOTE: this version doesn't take a point reference which allows up to store the point + bool mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool leave_event (bool prio); + virtual bool enter_event (bool prio); + virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); + // NOTE: this version doesn't take a point reference which allows up to store the point + bool mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio); + // NOTE: this version doesn't take a point reference which allows up to store the point + bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio); + virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); + // NOTE: this version doesn't take a point reference which allows up to store the point + bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio); + virtual void activated (); + void deactivated_impl (); + virtual void deactivated (); + virtual void drag_cancel (); + virtual void update (); + void add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize); + void add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize); + void add_edge_marker_dedge (const db::DEdge &p, bool emphasize); + void add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize); + + // for testing + bool has_tracking_position_test () const; + bool has_tracking_position_base () const; + virtual bool has_tracking_position () const; + + // for testing + db::DPoint tracking_position_test () const; + db::DPoint tracking_position_base () const; + virtual db::DPoint tracking_position () const; + + virtual lay::ViewService *view_service_interface () + { + return this; + } + + lay::LayoutViewBase *view () const + { + return const_cast (mp_view.get ()); + } + + lay::Dispatcher *dispatcher () const + { + return const_cast (mp_dispatcher.get ()); + } + +#if defined(HAVE_QTBINDINGS) + std::vector editor_options_pages (); +#endif + + gsi::Callback f_menu_activated; + gsi::Callback f_configure; + gsi::Callback f_config_finalize; + gsi::Callback f_key_event; + gsi::Callback f_mouse_press_event; + gsi::Callback f_mouse_click_event; + gsi::Callback f_mouse_double_click_event; + gsi::Callback f_leave_event; + gsi::Callback f_enter_event; + gsi::Callback f_mouse_move_event; + gsi::Callback f_mouse_release_event; + gsi::Callback f_wheel_event; + gsi::Callback f_activated; + gsi::Callback f_deactivated; + gsi::Callback f_drag_cancel; + gsi::Callback f_update; + gsi::Callback f_has_tracking_position; + gsi::Callback f_tracking_position; + +private: + tl::weak_ptr mp_view; + tl::weak_ptr mp_dispatcher; + + // Angle constraints and grids + lay::angle_constraint_type m_connect_ac, m_move_ac; + db::DVector m_edit_grid; + bool m_snap_to_objects; + bool m_snap_objects_to_grid; + db::DVector m_global_grid; + + lay::angle_constraint_type connect_ac (lay::angle_constraint_type ac) const; + lay::angle_constraint_type move_ac (lay::angle_constraint_type ac) const; +}; + +} + +#endif + diff --git a/src/lay/lay/gsiDeclLayPluginFactory.cc b/src/lay/lay/gsiDeclLayPluginFactory.cc new file mode 100644 index 000000000..1bbadd3dd --- /dev/null +++ b/src/lay/lay/gsiDeclLayPluginFactory.cc @@ -0,0 +1,600 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2025 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "gsiDecl.h" +#include "gsiDeclBasic.h" +#include "gsiEnums.h" + +#include "gsiDeclLayEditorOptionsPage.h" +#include "gsiDeclLayConfigPage.h" +#include "gsiDeclLayPlugin.h" + +#include "layEditorOptionsPages.h" + +namespace gsi +{ + +class PluginFactoryBase; + +static std::map s_factories; +extern bool s_in_create_plugin; + +class PluginFactoryBase + : public lay::PluginDeclaration +{ +public: + PluginFactoryBase () + : PluginDeclaration (), + m_implements_mouse_mode (true), mp_registration (0) + { + // .. nothing yet .. + } + + ~PluginFactoryBase () + { + for (auto f = s_factories.begin (); f != s_factories.end (); ++f) { + if (f->second == this) { + s_factories.erase (f); + break; + } + } + + delete mp_registration; + mp_registration = 0; + } + + void register_gsi (int position, const char *name, const char *title) + { + register_gsi2 (position, name, title, 0); + } + + void register_gsi2 (int position, const char *name, const char *title, const char *icon) + { + // makes the object owned by the C++ side + keep (); + + // remove an existing factory with the same name + std::map ::iterator f = s_factories.find (name); + if (f != s_factories.end () && f->second != this) { + // NOTE: this also removes the plugin from the s_factories list + delete f->second; + } + s_factories[name] = this; + + // cancel any previous registration and register (again) + delete mp_registration; + mp_registration = new tl::RegisteredClass (this, position, name, false /*does not own object*/); + + m_mouse_mode_title = name; + if (title) { + m_mouse_mode_title += "\t"; + m_mouse_mode_title += title; + } + if (icon) { + m_mouse_mode_title += "\t<"; + m_mouse_mode_title += icon; + m_mouse_mode_title += ">"; + } + + // (dynamically) register the plugin class. This will also call initialize if the main window is + // present already. + register_plugin (); + } + + virtual bool configure (const std::string &name, const std::string &value) + { + if (f_configure.can_issue ()) { + return f_configure.issue (&lay::PluginDeclaration::configure, name, value); + } else { + return lay::PluginDeclaration::configure (name, value); + } + } + + virtual void config_finalize () + { + if (f_config_finalize.can_issue ()) { + f_config_finalize.issue (&lay::PluginDeclaration::config_finalize); + } else { + lay::PluginDeclaration::config_finalize (); + } + } + + virtual bool menu_activated (const std::string &symbol) const + { + if (f_menu_activated.can_issue ()) { + return f_menu_activated.issue (&lay::PluginDeclaration::menu_activated, symbol); + } else { + return lay::PluginDeclaration::menu_activated (symbol); + } + } + + virtual void initialize (lay::Dispatcher *root) + { + if (f_initialize.can_issue ()) { + f_initialize.issue (&lay::PluginDeclaration::initialize, root); + } else { + lay::PluginDeclaration::initialize (root); + } + } + + virtual void uninitialize (lay::Dispatcher *root) + { + if (f_uninitialize.can_issue ()) { + f_uninitialize.issue (&lay::PluginDeclaration::uninitialize, root); + } else { + lay::PluginDeclaration::uninitialize (root); + } + } + +#if defined(HAVE_QTBINDINGS) + void add_editor_options_page (EditorOptionsPageImpl *page) const + { + page->keep (); + m_editor_options_pages.push_back (page); + } + + void get_editor_options_pages_impl () const + { + // .. nothing here .. + } + + virtual void get_editor_options_pages (std::vector &pages_out, lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) const + { + try { + + m_editor_options_pages.clear (); + + if (f_get_editor_options_pages.can_issue ()) { + f_get_editor_options_pages.issue (&PluginFactoryBase::get_editor_options_pages_impl); + } else { + get_editor_options_pages_impl (); + } + + for (auto i = m_editor_options_pages.begin (); i != m_editor_options_pages.end (); ++i) { + if (*i) { + (*i)->init (view, dispatcher); + (*i)->set_plugin_declaration (this); + pages_out.push_back (*i); + } + } + + m_editor_options_pages.clear (); + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + } + + void add_config_page (ConfigPageImpl *page) const + { + page->keep (); + m_config_pages.push_back (page); + } + + void get_config_pages_impl () const + { + // .. nothing here .. + } + + virtual std::vector > config_pages (QWidget *parent) const + { + std::vector > pages_out; + + try { + + m_config_pages.clear (); + + if (f_config_pages.can_issue ()) { + f_config_pages.issue (&PluginFactoryBase::get_config_pages_impl); + } else { + get_config_pages_impl (); + } + + pages_out.clear (); + for (auto i = m_config_pages.begin (); i != m_config_pages.end (); ++i) { + if (*i) { + (*i)->setParent (parent); + pages_out.push_back (std::make_pair ((*i)->title (), *i)); + } + } + + m_config_pages.clear (); + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + + return pages_out; + } +#endif + + virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const + { + if (f_create_plugin.can_issue ()) { + return create_plugin_gsi (manager, root, view); + } else { + return lay::PluginDeclaration::create_plugin (manager, root, view); + } + } + + virtual gsi::PluginBase *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const + { + s_in_create_plugin = true; + + gsi::PluginBase *ret = 0; + try { + + ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); + if (ret) { + ret->init (view, root); + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + } catch (std::exception &ex) { + tl::error << ex.what (); + } catch (...) { + } + + s_in_create_plugin = false; + + return ret; + } + + virtual void get_menu_entries (std::vector &menu_entries) const + { + menu_entries = m_menu_entries; + } + + virtual void get_options (std::vector < std::pair > &options) const + { + options = m_options; + } + + void add_menu_entry1 (const std::string &menu_name, const std::string &insert_pos) + { + m_menu_entries.push_back (lay::separator (menu_name, insert_pos)); + } + + void add_menu_entry2 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title) + { + m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); + } + + void add_menu_entry_copy (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string ©_from) + { + m_menu_entries.push_back (lay::menu_item_copy (symbol, menu_name, insert_pos, copy_from)); + } + + void add_submenu (const std::string &menu_name, const std::string &insert_pos, const std::string &title) + { + m_menu_entries.push_back (lay::submenu (menu_name, insert_pos, title)); + } + + void add_config_menu_item (const std::string &menu_name, const std::string &insert_pos, const std::string &title, const std::string &cname, const std::string &cvalue) + { + m_menu_entries.push_back (lay::config_menu_item (menu_name, insert_pos, title, cname, cvalue)); + } + + void add_menu_entry3 (const std::string &symbol, const std::string &menu_name, const std::string &insert_pos, const std::string &title, bool sub_menu) + { + if (sub_menu) { + m_menu_entries.push_back (lay::submenu (symbol, menu_name, insert_pos, title)); + } else { + m_menu_entries.push_back (lay::menu_item (symbol, menu_name, insert_pos, title)); + } + } + + void add_option (const std::string &name, const std::string &default_value) + { + m_options.push_back (std::make_pair (name, default_value)); + } + + void has_tool_entry (bool f) + { + m_implements_mouse_mode = f; + } + + virtual bool implements_mouse_mode (std::string &title) const + { + title = m_mouse_mode_title; + return m_implements_mouse_mode; + } + + gsi::Callback f_create_plugin; + gsi::Callback f_initialize; + gsi::Callback f_uninitialize; + gsi::Callback f_configure; + gsi::Callback f_config_finalize; + gsi::Callback f_menu_activated; + gsi::Callback f_get_editor_options_pages; + gsi::Callback f_config_pages; + +private: + std::vector > m_options; + std::vector m_menu_entries; + bool m_implements_mouse_mode; + std::string m_mouse_mode_title; + tl::RegisteredClass *mp_registration; + mutable std::vector m_config_pages; + mutable std::vector m_editor_options_pages; +}; + +Class decl_PluginFactory ("lay", "PluginFactory", + method ("register", &PluginFactoryBase::register_gsi, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), + "@brief Registers the plugin factory\n" + "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" + "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" + "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" + "\n" + "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " + "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" + ) + + method ("register", &PluginFactoryBase::register_gsi2, gsi::arg ("position"), gsi::arg ("name"), gsi::arg ("title"), gsi::arg ("icon"), + "@brief Registers the plugin factory\n" + "@param position An integer that determines the order in which the plugins are created. The internal plugins use the values from 1000 to 50000.\n" + "@param name The plugin name. This is an arbitrary string which should be unique. Hence it is recommended to use a unique prefix, i.e. \"myplugin::ThePluginClass\".\n" + "@param title The title string which is supposed to appear in the tool bar and menu related to this plugin.\n" + "@param icon The path to the icon that appears in the tool bar and menu related to this plugin.\n" + "\n" + "This version also allows registering an icon for the tool bar.\n" + "\n" + "Registration of the plugin factory makes the object known to the system. Registration requires that the menu items have been set " + "already. Hence it is recommended to put the registration at the end of the initialization method of the factory class.\n" + ) + + callback ("configure", &gsi::PluginFactoryBase::configure, &gsi::PluginFactoryBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), + "@brief Gets called for configuration events for the plugin singleton\n" + "This method can be reimplemented to receive configuration events " + "for the plugin singleton. Before a configuration can be received it must be " + "registered by calling \\add_option in the plugin factories' constructor.\n" + "\n" + "The implementation of this method may return true indicating that the configuration request " + "will not be handled by further modules. It's more cooperative to return false which will " + "make the system distribute the configuration request to other receivers as well.\n" + "\n" + "@param name The configuration key\n" + "@param value The value of the configuration variable\n" + "@return True to stop further processing\n" + ) + + callback ("config_finalize", &gsi::PluginFactoryBase::config_finalize, &gsi::PluginFactoryBase::f_config_finalize, + "@brief Gets called after a set of configuration events has been sent\n" + "This method can be reimplemented and is called after a set of configuration events " + "has been sent to the plugin factory singleton with \\configure. It can be used to " + "set up user interfaces properly for example.\n" + ) + + callback ("menu_activated", &gsi::PluginFactoryBase::menu_activated, &gsi::PluginFactoryBase::f_menu_activated, gsi::arg ("symbol"), + "@brief Gets called when a menu item is selected\n" + "\n" + "Usually, menu-triggered functionality is implemented in the per-view instance of the plugin. " + "However, using this method it is possible to implement functionality globally for all plugin " + "instances. The symbol is the string registered with the specific menu item in the \\add_menu_item " + "call.\n" + "\n" + "If this method was handling the menu event, it should return true. This indicates that the event " + "will not be propagated to other plugins hence avoiding duplicate calls.\n" + ) + + callback ("initialized", &gsi::PluginFactoryBase::initialize, &gsi::PluginFactoryBase::f_initialize, gsi::arg ("dispatcher"), + "@brief Gets called when the plugin singleton is initialized, i.e. when the application has been started.\n" + "@param dispatcher The reference to the \\MainWindow object\n" + ) + + callback ("uninitialized", &gsi::PluginFactoryBase::uninitialize, &gsi::PluginFactoryBase::f_uninitialize, gsi::arg ("dispatcher"), + "@brief Gets called when the application shuts down and the plugin is unregistered\n" + "This event can be used to free resources allocated with this factory singleton.\n" + "@param dispatcher The reference to the \\MainWindow object\n" + ) + + factory_callback ("create_plugin", &gsi::PluginFactoryBase::create_plugin_gsi, &gsi::PluginFactoryBase::f_create_plugin, gsi::arg ("manager"), gsi::arg ("dispatcher"), gsi::arg ("view"), + "@brief Creates the plugin\n" + "This is the basic functionality that the factory must provide. This method must create a plugin of the " + "specific type.\n" + "@param manager The database manager object responsible for handling database transactions\n" + "@param dispatcher The reference to the \\MainWindow object\n" + "@param view The \\LayoutView that is plugin is created for\n" + "@return The new \\Plugin implementation object\n" + ) + + method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry1, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), + "@brief Specifies a separator\n" + "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" + "This specific call inserts a separator at the given position (insert_pos). The position uses abstract menu item paths " + "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" + ) + + method ("add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry2, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), + "@brief Specifies a menu item\n" + "Call this method in the factory constructor to build the menu items that this plugin shall create.\n" + "This specific call inserts a menu item at the specified position (insert_pos). The position uses abstract menu item paths " + "and \"menu_name\" names the component that will be created. See \\AbstractMenu for a description of the path.\n" + "When the menu item is selected \"symbol\" is the string that is sent to the \\menu_activated callback (either the global one for the factory ot the one of the per-view plugin instance).\n" + "\n" + "@param symbol The string to send to the plugin if the menu is triggered\n" + "@param menu_name The name of entry to create at the given position\n" + "@param insert_pos The position where to create the entry\n" + "@param title The title string for the item. The title can contain a keyboard shortcut in round braces after the title text, i.e. \"My Menu Item(F12)\"\n" + ) + + method ("#add_menu_entry", &gsi::PluginFactoryBase::add_menu_entry3, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("sub_menu"), + "@brief Specifies a menu item or sub-menu\n" + "Similar to the previous form of \"add_menu_entry\", but this version allows also to create sub-menus by setting the " + "last parameter to \"true\".\n" + "\n" + "With version 0.27 it's more convenient to use \\add_submenu." + ) + + method ("add_menu_item_clone", &gsi::PluginFactoryBase::add_menu_entry_copy, gsi::arg ("symbol"), gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("copy_from"), + "@brief Specifies a menu item as a clone of another one\n" + "Using this method, a menu item can be made a clone of another entry (given as path by 'copy_from').\n" + "The new item will share the \\Action object with the original one, so manipulating the action will change both the original entry " + "and the new entry.\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_submenu", &gsi::PluginFactoryBase::add_submenu, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), + "@brief Specifies a menu item or sub-menu\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_config_menu_item", &gsi::PluginFactoryBase::add_config_menu_item, gsi::arg ("menu_name"), gsi::arg ("insert_pos"), gsi::arg ("title"), gsi::arg ("cname"), gsi::arg ("cvalue"), + "@brief Adds a configuration menu item\n" + "\n" + "Menu items created this way will send a configuration request with 'cname' as the configuration parameter name " + "and 'cvalue' as the configuration parameter value.\n" + "If 'cvalue' is a string with a single question mark (\"?\"), the item is a check box that reflects the boolean " + "value of the configuration item.\n" + "\n" + "This method has been introduced in version 0.27." + ) + + method ("add_option", &gsi::PluginFactoryBase::add_option, gsi::arg ("name"), gsi::arg ("default_value"), + "@brief Specifies configuration variables.\n" + "Call this method in the factory constructor to add configuration key/value pairs to the configuration repository. " + "Without specifying configuration variables, the status of a plugin cannot be persisted. " + "\n\n" + "Once the configuration variables are known, they can be retrieved on demand using \"get_config\" from " + "\\MainWindow or listening to \\configure callbacks (either in the factory or the plugin instance). Configuration variables can " + "be set using \"set_config\" from \\MainWindow. This scheme also works without registering the configuration options, but " + "doing so has the advantage that it is guaranteed that a variable with this keys exists and has the given default value initially." + ) + +#if defined(HAVE_QTBINDINGS) + method ("add_editor_options_page", &PluginFactoryBase::add_editor_options_page, gsi::arg ("page"), + "@brief Adds the given editor options page\n" + "See \\create_editor_options_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_editor_options_pages", &PluginFactoryBase::get_editor_options_pages_impl, &PluginFactoryBase::f_get_editor_options_pages, + "@brief Creates the editor option pages\n" + "The editor option pages are widgets of type \\EditorOptionsPage. These Qt widgets " + "are displayed in a seperate dock (the 'editor options') and become visible when the plugin is active - i.e. " + "its mode is selected. Use this method to provide customized pages that will be displayed in the " + "editor options dock.\n" + "\n" + "In order to create config pages, instantiate a \\EditorOptionsPage object and " + "call \\add_editor_options_page to register it.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + method ("add_config_page", &PluginFactoryBase::add_config_page, gsi::arg ("page"), + "@brief Adds the given configuration page\n" + "See \\create_config_pages how to use this function. The method is effective only in " + "the reimplementation context of this function.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + + callback ("create_config_pages", &PluginFactoryBase::get_config_pages_impl, &PluginFactoryBase::f_config_pages, + "@brief Creates the configuration widgets\n" + "The configuration pages are widgets that are displayed in the " + "configuration dialog ('File/Setup'). Every plugin can create multiple such " + "widgets and specify, where these widgets are displayed. The widgets are of type \\ConfigPage.\n" + "\n" + "The title string also specifies the location of the widget in the " + "configuration page hierarchy. See \\ConfigPage for more details.\n" + "\n" + "In order to create config pages, instantiate a \\ConfigPage object and " + "call \\add_config_page to register it.\n" + "\n" + "This method has been introduced in version 0.30.4." + ) + +#endif + method ("has_tool_entry=", &gsi::PluginFactoryBase::has_tool_entry, gsi::arg ("f"), + "@brief Enables or disables the tool bar entry\n" + "Initially this property is set to true. This means that the plugin will have a visible entry in the toolbar. " + "This property can be set to false to disable this feature. In that case, the title and icon given on registration will be ignored. " + ), + "@brief The plugin framework's plugin factory object\n" + "\n" + "Plugins are components that extend KLayout's functionality in various aspects. Scripting support exists " + "currently for providing mouse mode handlers and general on-demand functionality connected with a menu " + "entry.\n" + "\n" + "Plugins are objects that implement the \\Plugin interface. Each layout view is associated with one instance " + "of such an object. The PluginFactory is a singleton which is responsible for creating \\Plugin objects and " + "providing certain configuration information such as where to put the menu items connected to this plugin and " + "what configuration keys are used.\n" + "\n" + "An implementation of PluginFactory must at least provide an implementation of \\create_plugin. This method " + "must instantiate a new object of the specific plugin.\n" + "\n" + "After the factory has been created, it must be registered in the system using one of the \\register methods. " + "It is therefore recommended to put the call to \\register at the end of the \"initialize\" method. For the registration " + "to work properly, the menu items must be defined before \\register is called.\n" + "\n" + "The following features can also be implemented:\n" + "\n" + "@
    \n" + " @
  • Reserve keys in the configuration file using \\add_option in the constructor@
  • \n" + " @
  • Create menu items by using \\add_menu_entry in the constructor@
  • \n" + " @
  • Set the title for the mode entry that appears in the tool bar using the \\register argument@
  • \n" + " @
  • Provide global functionality (independent from the layout view) using \\configure or \\menu_activated@
  • \n" + "@
\n" + "\n" + "This is a simple example for a plugin in Ruby. It switches the mouse cursor to a 'cross' cursor when it is active:\n" + "\n" + "@code\n" + "class PluginTestFactory < RBA::PluginFactory\n" + "\n" + " # Constructor\n" + " def initialize\n" + " # registers the new plugin class at position 100000 (at the end), with name\n" + " # \"my_plugin_test\" and title \"My plugin test\"\n" + " register(100000, \"my_plugin_test\", \"My plugin test\")\n" + " end\n" + " \n" + " # Create a new plugin instance of the custom type\n" + " def create_plugin(manager, dispatcher, view)\n" + " return PluginTest.new\n" + " end\n" + "\n" + "end\n" + "\n" + "# The plugin class\n" + "class PluginTest < RBA::Plugin\n" + " def mouse_moved_event(p, buttons, prio)\n" + " if prio\n" + " # Set the cursor to cross if our plugin is active.\n" + " set_cursor(RBA::Cursor::Cross)\n" + " end\n" + " # Returning false indicates that we don't want to consume the event.\n" + " # This way for example the cursor position tracker still works.\n" + " false\n" + " end\n" + " def mouse_click_event(p, buttons, prio)\n" + " if prio\n" + " puts \"mouse button clicked.\"\n" + " # This indicates we want to consume the event and others don't receive the mouse click\n" + " # with prio = false.\n" + " return true\n" + " end\n" + " # don't consume the event if we are not active.\n" + " false\n" + " end\n" + "end\n" + "\n" + "# Instantiate the new plugin factory.\n" + "PluginTestFactory.new\n" + "@/code\n" + "\n" + "This class has been introduced in version 0.22.\n" +); + +} diff --git a/src/lay/lay/lay.pro b/src/lay/lay/lay.pro index d3cfcdaf6..4a920fc86 100644 --- a/src/lay/lay/lay.pro +++ b/src/lay/lay/lay.pro @@ -7,6 +7,9 @@ include($$PWD/../../lib.pri) DEFINES += MAKE_LAY_LIBRARY HEADERS = \ + gsiDeclLayConfigPage.h \ + gsiDeclLayEditorOptionsPage.h \ + gsiDeclLayPlugin.h \ layApplication.h \ layClipDialog.h \ layControlWidgetStack.h \ @@ -117,9 +120,13 @@ FORMS = \ SOURCES = \ gsiDeclLayApplication.cc \ + gsiDeclLayConfigPage.cc \ + gsiDeclLayDispatcher.cc \ + gsiDeclLayEditorOptionsPage.cc \ gsiDeclLayHelpDialog.cc \ gsiDeclLayMainWindow.cc \ gsiDeclLayPlugin.cc \ + gsiDeclLayPluginFactory.cc \ layApplication.cc \ layClipDialog.cc \ layControlWidgetStack.cc \ From 4d9f924466234c5005963e657a81a799a0ceeb6e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 31 Aug 2025 17:49:13 +0200 Subject: [PATCH 20/52] Refactoring: providing GSI binding to plugin base class, so the super implementation can be called. --- src/lay/lay/gsiDeclLayPlugin.cc | 389 +++++++++++++------ src/lay/lay/gsiDeclLayPlugin.h | 12 +- src/lay/lay/gsiDeclLayPluginFactory.cc | 6 +- src/laybasic/laybasic/layEditorServiceBase.h | 112 ++++++ 4 files changed, 382 insertions(+), 137 deletions(-) diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 5b288c6fb..9b40b42c3 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -33,10 +33,164 @@ namespace gsi { +static bool has_tracking_position_impl (const lay::EditorServiceBase *p) +{ + return p->lay::EditorServiceBase::has_tracking_position (); +} + +static db::DPoint tracking_position_impl (const lay::EditorServiceBase *p) +{ + return p->lay::EditorServiceBase::tracking_position (); +} + +static void menu_activated_impl (lay::EditorServiceBase *p, const std::string &symbol) +{ + return p->lay::EditorServiceBase::menu_activated (symbol); +} + +static bool configure_impl (lay::EditorServiceBase *p, const std::string &name, const std::string &value) +{ + return p->lay::EditorServiceBase::configure (name, value); +} + +static void config_finalize_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::config_finalize (); +} + +static void deactivated_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::deactivated (); +} + +static void activated_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::activated (); +} + +static bool key_event_impl (lay::EditorServiceBase *p, unsigned int key, unsigned int buttons) +{ + return p->lay::EditorServiceBase::key_event (key, buttons); +} + +static bool mouse_press_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_press_event (pt, buttons, prio); +} + +static bool mouse_click_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_click_event (pt, buttons, prio); +} + +static bool mouse_double_click_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_double_click_event (pt, buttons, prio); +} + +static bool leave_event_impl (lay::EditorServiceBase *p, bool prio) +{ + return p->lay::EditorServiceBase::leave_event (prio); +} + +static bool enter_event_impl (lay::EditorServiceBase *p, bool prio) +{ + return p->lay::EditorServiceBase::enter_event (prio); +} + +static bool mouse_move_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_move_event (pt, buttons, prio); +} + +static bool mouse_release_event_impl (lay::EditorServiceBase *p, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::mouse_release_event (pt, buttons, prio); +} + +static bool wheel_event_impl (lay::EditorServiceBase *p, int delta, bool horizontal, const db::DPoint &pt, unsigned int buttons, bool prio) +{ + return p->lay::EditorServiceBase::wheel_event (delta, horizontal, pt, buttons, prio); +} + +static void update_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::update (); +} + +static void drag_cancel_impl (lay::EditorServiceBase *p) +{ + p->lay::EditorServiceBase::drag_cancel (); +} + +Class decl_PluginBase ("lay", "PluginBase", + gsi::method_ext ("tracking_position", &tracking_position_impl, + "@brief Gets the tracking position (base class implementation)" + ) + + gsi::method_ext ("has_tracking_position", &has_tracking_position_impl, + "@brief Gets a value indicating whether the plugin provides a tracking position (base class implementation)" + ) + + gsi::method_ext ("menu_activated", &menu_activated_impl, gsi::arg ("symbol"), + "@brief Gets called when a custom menu item is selected (base class implementation)" + ) + + gsi::method_ext ("configure", &configure_impl, gsi::arg ("name"), gsi::arg ("value"), + "@brief Sends configuration requests to the plugin (base class implementation)" + ) + + gsi::method_ext ("config_finalize", &config_finalize_impl, + "@brief Sends the post-configuration request to the plugin (base class implementation)" + ) + + gsi::method_ext ("key_event", &key_event_impl, gsi::arg ("key"), gsi::arg ("buttons"), + "@brief Handles the key pressed event (base class implementation)" + ) + + gsi::method_ext ("mouse_button_pressed_event", &mouse_press_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button pressed event (base class implementation)" + ) + + gsi::method_ext ("mouse_click_event", &mouse_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button click event after the button has been released (base class implementation)" + ) + + gsi::method_ext ("mouse_double_click_event", &mouse_double_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button double-click event (base class implementation)" + ) + + gsi::method_ext ("leave_event", &leave_event_impl, gsi::arg ("prio"), + "@brief Handles the leave event (base class implementation)" + ) + + gsi::method_ext ("enter_event", &enter_event_impl, gsi::arg ("prio"), + "@brief Handles the enter event (base class implementation)" + ) + + gsi::method_ext ("mouse_moved_event", &mouse_move_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse move event (base class implementation)" + ) + + gsi::method_ext ("mouse_button_released_event", &mouse_release_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse button release event (base class implementation)" + ) + + gsi::method_ext ("wheel_event", &wheel_event_impl, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse wheel event (base class implementation)" + ) + + gsi::method_ext ("activated", &activated_impl, + "@brief Gets called when the plugin is activated (base class implementation)" + ) + + gsi::method_ext ("deactivated", &deactivated_impl, + "@brief Gets called when the plugin is deactivated and another plugin is activated (base class implementation)" + ) + + gsi::method_ext ("drag_cancel", &drag_cancel_impl, + "@brief This method is called when some mouse dragging operation should be cancelled (base class implementation)" + ) + + gsi::method_ext ("update", &update_impl, + "@brief Gets called when the view has changed (base class implementation)" + ), + "@brief The plugin base class\n" + "\n" + "This class is provided as an interface to the base class implementation for various functions.\n" + "You can use these methods in order to pass down events to the original implementation.\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + + // HACK: used to track if we're inside a create_plugin method and can be sure that "init" is called bool s_in_create_plugin = false; -PluginBase::PluginBase () +PluginImpl::PluginImpl () : lay::EditorServiceBase (), mp_view (0), mp_dispatcher (0), m_connect_ac (lay::AC_Any), m_move_ac (lay::AC_Any), @@ -49,7 +203,7 @@ PluginBase::PluginBase () } void -PluginBase::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) +PluginImpl::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) { mp_view = view; mp_dispatcher = dispatcher; @@ -57,7 +211,7 @@ PluginBase::init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) } void -PluginBase::grab_mouse () +PluginImpl::grab_mouse () { if (ui ()) { ui ()->grab_mouse (this, false); @@ -65,7 +219,7 @@ PluginBase::grab_mouse () } void -PluginBase::ungrab_mouse () +PluginImpl::ungrab_mouse () { if (ui ()) { ui ()->ungrab_mouse (this); @@ -73,7 +227,7 @@ PluginBase::ungrab_mouse () } void -PluginBase::set_cursor (int c) +PluginImpl::set_cursor (int c) { if (ui ()) { lay::ViewService::set_cursor ((enum lay::Cursor::cursor_shape) c); @@ -81,17 +235,17 @@ PluginBase::set_cursor (int c) } void -PluginBase::menu_activated (const std::string &symbol) +PluginImpl::menu_activated (const std::string &symbol) { if (f_menu_activated.can_issue ()) { - f_menu_activated.issue (&lay::Plugin::menu_activated, symbol); + f_menu_activated.issue (&lay::EditorServiceBase::menu_activated, symbol); } else { - lay::Plugin::menu_activated (symbol); + lay::EditorServiceBase::menu_activated (symbol); } } db::DPoint -PluginBase::snap (db::DPoint p) const +PluginImpl::snap (db::DPoint p) const { // snap according to the grid if (m_edit_grid == db::DVector ()) { @@ -106,7 +260,7 @@ PluginBase::snap (db::DPoint p) const } db::DVector -PluginBase::snap_vector (db::DVector v) const +PluginImpl::snap_vector (db::DVector v) const { // snap according to the grid if (m_edit_grid == db::DVector ()) { @@ -121,20 +275,20 @@ PluginBase::snap_vector (db::DVector v) const } db::DPoint -PluginBase::snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const +PluginImpl::snap_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac) const { db::DPoint ps = plast + lay::snap_angle (db::DVector (p - plast), connect ? connect_ac (ac) : move_ac (ac)); return snap (ps); } db::DVector -PluginBase::snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const +PluginImpl::snap_delta (const db::DVector &v, bool connect, lay::angle_constraint_type ac) const { return snap_vector (lay::snap_angle (v, connect ? connect_ac (ac) : move_ac (ac))); } db::DPoint -PluginBase::snap2 (const db::DPoint &p, bool visualize) +PluginImpl::snap2 (const db::DPoint &p, bool visualize) { double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); @@ -145,7 +299,7 @@ PluginBase::snap2 (const db::DPoint &p, bool visualize) } db::DPoint -PluginBase::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) +PluginImpl::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) { double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); @@ -159,7 +313,7 @@ PluginBase::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool co * @brief Captures some edt space configuration events for convencience */ void -PluginBase::configure_edt (const std::string &name, const std::string &value) +PluginImpl::configure_edt (const std::string &name, const std::string &value) { edt::EditGridConverter egc; edt::ACConverter acc; @@ -185,20 +339,20 @@ PluginBase::configure_edt (const std::string &name, const std::string &value) * @brief The implementation does not allow to bypass the base class configuration call */ bool -PluginBase::configure_impl (const std::string &name, const std::string &value) +PluginImpl::configure_impl (const std::string &name, const std::string &value) { - return f_configure.can_issue () ? f_configure.issue (&PluginBase::configure, name, value) : lay::Plugin::configure (name, value); + return f_configure.can_issue () ? f_configure.issue (&PluginImpl::configure, name, value) : lay::EditorServiceBase::configure (name, value); } // for testing void - PluginBase::configure_test (const std::string &name, const std::string &value) +PluginImpl::configure_test (const std::string &name, const std::string &value) { configure_edt (name, value); } bool -PluginBase::configure (const std::string &name, const std::string &value) +PluginImpl::configure (const std::string &name, const std::string &value) { configure_edt (name, value); return configure_impl (name, value); @@ -208,20 +362,20 @@ PluginBase::configure (const std::string &name, const std::string &value) * @brief The implementation does not allow to bypass the base class configuration call */ void -PluginBase::config_finalize_impl () +PluginImpl::config_finalize_impl () { - f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginBase::config_finalize) : lay::Plugin::config_finalize (); + f_config_finalize.can_issue () ? f_config_finalize.issue (&PluginImpl::config_finalize) : lay::EditorServiceBase::config_finalize (); } void -PluginBase::config_finalize () +PluginImpl::config_finalize () { lay::EditorServiceBase::config_finalize (); config_finalize_impl (); } bool -PluginBase::key_event (unsigned int key, unsigned int buttons) +PluginImpl::key_event (unsigned int key, unsigned int buttons) { if (f_key_event.can_issue ()) { return f_key_event.issue (&lay::ViewService::key_event, key, buttons); @@ -231,58 +385,58 @@ PluginBase::key_event (unsigned int key, unsigned int buttons) } bool -PluginBase::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (f_mouse_press_event.can_issue ()) { - return f_mouse_press_event.issue (&PluginBase::mouse_press_event_noref, p, buttons, prio); + return f_mouse_press_event.issue (&PluginImpl::mouse_press_event_noref, p, buttons, prio); } else { return lay::EditorServiceBase::mouse_press_event (p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio) { return mouse_press_event (p, buttons, prio); } bool -PluginBase::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (f_mouse_click_event.can_issue ()) { - return f_mouse_click_event.issue (&PluginBase::mouse_click_event_noref, p, buttons, prio); + return f_mouse_click_event.issue (&PluginImpl::mouse_click_event_noref, p, buttons, prio); } else { return lay::EditorServiceBase::mouse_click_event (p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) { return mouse_click_event (p, buttons, prio); } bool -PluginBase::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (f_mouse_double_click_event.can_issue ()) { - return f_mouse_double_click_event.issue (&PluginBase::mouse_double_click_event_noref, p, buttons, prio); + return f_mouse_double_click_event.issue (&PluginImpl::mouse_double_click_event_noref, p, buttons, prio); } else { return lay::EditorServiceBase::mouse_double_click_event (p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio) { return mouse_double_click_event (p, buttons, prio); } bool -PluginBase::leave_event (bool prio) +PluginImpl::leave_event (bool prio) { if (f_leave_event.can_issue ()) { return f_leave_event.issue (&lay::ViewService::leave_event, prio); @@ -292,7 +446,7 @@ PluginBase::leave_event (bool prio) } bool -PluginBase::enter_event (bool prio) +PluginImpl::enter_event (bool prio) { if (f_enter_event.can_issue ()) { return f_enter_event.issue (&lay::ViewService::enter_event, prio); @@ -302,58 +456,58 @@ PluginBase::enter_event (bool prio) } bool -PluginBase::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (f_mouse_move_event.can_issue ()) { - return f_mouse_move_event.issue (&PluginBase::mouse_move_event_noref, p, buttons, prio); + return f_mouse_move_event.issue (&PluginImpl::mouse_move_event_noref, p, buttons, prio); } else { return lay::EditorServiceBase::mouse_move_event (p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio) { return mouse_move_event (p, buttons, prio); } bool -PluginBase::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio) { if (f_mouse_release_event.can_issue ()) { - return f_mouse_release_event.issue (&PluginBase::mouse_release_event_noref, p, buttons, prio); + return f_mouse_release_event.issue (&PluginImpl::mouse_release_event_noref, p, buttons, prio); } else { return lay::ViewService::mouse_release_event (p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio) { return mouse_release_event (p, buttons, prio); } bool -PluginBase::wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) +PluginImpl::wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio) { if (f_wheel_event.can_issue ()) { - return f_wheel_event.issue (&PluginBase::wheel_event_noref, delta, horizontal, p, buttons, prio); + return f_wheel_event.issue (&PluginImpl::wheel_event_noref, delta, horizontal, p, buttons, prio); } else { return lay::ViewService::wheel_event (delta, horizontal, p, buttons, prio); } } -// NOTE: this version doesn't take a point reference which allows up to store the point +// NOTE: this version doesn't take a point reference which allows us to store the point in script code without generating a reference bool -PluginBase::wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) +PluginImpl::wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio) { return wheel_event (delta, horizontal, p, buttons, prio); } void -PluginBase::activated () +PluginImpl::activated () { if (f_activated.can_issue ()) { f_activated.issue (&lay::EditorServiceBase::activated); @@ -363,22 +517,22 @@ PluginBase::activated () } void -PluginBase::deactivated_impl () +PluginImpl::deactivated_impl () { if (f_deactivated.can_issue ()) { - f_deactivated.issue (&PluginBase::deactivated_impl); + f_deactivated.issue (&PluginImpl::deactivated_impl); } } void -PluginBase::deactivated () +PluginImpl::deactivated () { lay::EditorServiceBase::deactivated (); deactivated_impl (); } void -PluginBase::drag_cancel () +PluginImpl::drag_cancel () { if (f_drag_cancel.can_issue ()) { f_drag_cancel.issue (&lay::EditorServiceBase::drag_cancel); @@ -388,7 +542,7 @@ PluginBase::drag_cancel () } void -PluginBase::update () +PluginImpl::update () { if (f_update.can_issue ()) { f_update.issue (&lay::EditorServiceBase::update); @@ -398,13 +552,13 @@ PluginBase::update () } void -PluginBase::add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) +PluginImpl::add_mouse_cursor_dpoint (const db::DPoint &p, bool emphasize) { lay::EditorServiceBase::add_mouse_cursor (p, emphasize); } void -PluginBase::add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +PluginImpl::add_mouse_cursor_point (const db::Point &p, int cv_index, const db::LayerProperties &lp, bool emphasize) { const lay::CellView &cv = view ()->cellview (cv_index); if (! cv.is_valid ()) { @@ -426,13 +580,13 @@ PluginBase::add_mouse_cursor_point (const db::Point &p, int cv_index, const db:: } void -PluginBase::add_edge_marker_dedge (const db::DEdge &p, bool emphasize) +PluginImpl::add_edge_marker_dedge (const db::DEdge &p, bool emphasize) { lay::EditorServiceBase::add_edge_marker (p, emphasize); } void -PluginBase::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) +PluginImpl::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::LayerProperties &lp, bool emphasize) { const lay::CellView &cv = view ()->cellview (cv_index); if (! cv.is_valid ()) { @@ -455,53 +609,41 @@ PluginBase::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::Lay // for testing bool -PluginBase::has_tracking_position_test () const +PluginImpl::has_tracking_position_test () const { return has_tracking_position (); } bool -PluginBase::has_tracking_position_base () const -{ - return lay::EditorServiceBase::has_tracking_position (); -} - -bool -PluginBase::has_tracking_position () const +PluginImpl::has_tracking_position () const { if (f_has_tracking_position.can_issue ()) { return f_has_tracking_position.issue (&lay::EditorServiceBase::has_tracking_position); } else { - return has_tracking_position_base (); + return lay::EditorServiceBase::has_tracking_position (); } } // for testing db::DPoint -PluginBase::tracking_position_test () const +PluginImpl::tracking_position_test () const { return tracking_position (); } db::DPoint -PluginBase::tracking_position_base () const -{ - return lay::EditorServiceBase::tracking_position (); -} - -db::DPoint -PluginBase::tracking_position () const +PluginImpl::tracking_position () const { if (f_tracking_position.can_issue ()) { return f_tracking_position.issue (&lay::EditorServiceBase::tracking_position); } else { - return tracking_position_base (); + return lay::EditorServiceBase::tracking_position (); } } #if defined(HAVE_QTBINDINGS) std::vector -PluginBase::editor_options_pages () +PluginImpl::editor_options_pages () { lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); if (!eo_pages) { @@ -519,38 +661,38 @@ PluginBase::editor_options_pages () #endif lay::angle_constraint_type -PluginBase::connect_ac (lay::angle_constraint_type ac) const +PluginImpl::connect_ac (lay::angle_constraint_type ac) const { // m_alt_ac (which is set from mouse buttons) can override the specified connect angle constraint return ac != lay::AC_Global ? ac : m_connect_ac; } lay::angle_constraint_type -PluginBase::move_ac (lay::angle_constraint_type ac) const +PluginImpl::move_ac (lay::angle_constraint_type ac) const { // m_alt_ac (which is set from mouse buttons) can override the specified move angle constraint return ac != lay::AC_Global ? ac : m_move_ac; } -Class decl_Plugin ("lay", "Plugin", - callback ("menu_activated", &gsi::PluginBase::menu_activated, &gsi::PluginBase::f_menu_activated, gsi::arg ("symbol"), +Class decl_Plugin (decl_PluginBase, "lay", "Plugin", + callback ("menu_activated", &gsi::PluginImpl::menu_activated, &gsi::PluginImpl::f_menu_activated, gsi::arg ("symbol"), "@brief Gets called when a custom menu item is selected\n" "When a menu item is clicked which was registered with the plugin factory, the plugin's 'menu_activated' method is " "called for the current view. The symbol registered for the menu item is passed in the 'symbol' argument." ) + - method ("configure_test", &gsi::PluginBase::configure_test, gsi::arg ("name"), gsi::arg ("value"), "@hide") + - callback ("configure", &gsi::PluginBase::configure_impl, &gsi::PluginBase::f_configure, gsi::arg ("name"), gsi::arg ("value"), + method ("configure_test", &gsi::PluginImpl::configure_test, gsi::arg ("name"), gsi::arg ("value"), "@hide") + + callback ("configure", &gsi::PluginImpl::configure_impl, &gsi::PluginImpl::f_configure, gsi::arg ("name"), gsi::arg ("value"), "@brief Sends configuration requests to the plugin\n" "@param name The name of the configuration variable as registered in the plugin factory\n" "@param value The value of the configuration variable\n" "When a configuration variable is changed, the new value is reported to the plugin by calling the 'configure' method." ) + - callback ("config_finalize", &gsi::PluginBase::config_finalize_impl, &gsi::PluginBase::f_config_finalize, + callback ("config_finalize", &gsi::PluginImpl::config_finalize_impl, &gsi::PluginImpl::f_config_finalize, "@brief Sends the post-configuration request to the plugin\n" "After all configuration parameters have been sent, 'config_finalize' is called to given the plugin a chance to " "update its internal state according to the new configuration.\n" ) + - callback ("key_event", &gsi::PluginBase::key_event, &gsi::PluginBase::f_key_event, gsi::arg ("key"), gsi::arg ("buttons"), + callback ("key_event", &gsi::PluginImpl::key_event, &gsi::PluginImpl::f_key_event, gsi::arg ("key"), gsi::arg ("buttons"), "@brief Handles the key pressed event\n" "This method will called by the view on the active plugin when a button is pressed on the mouse.\n" "\n" @@ -560,7 +702,7 @@ Class decl_Plugin ("lay", "Plugin", "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. ShiftButton etc).\n" "@return True to terminate dispatcher\n" ) + - callback ("mouse_button_pressed_event", &gsi::PluginBase::mouse_press_event_noref, &gsi::PluginBase::f_mouse_press_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("mouse_button_pressed_event", &gsi::PluginImpl::mouse_press_event_noref, &gsi::PluginImpl::f_mouse_press_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button pressed event\n" "This method will called by the view when a button is pressed on the mouse.\n" "\n" @@ -582,72 +724,72 @@ Class decl_Plugin ("lay", "Plugin", "@param buttons A combination of the constants in the \\ButtonState class which codes both the mouse buttons and the key modifiers (.e. LeftButton, ShiftButton etc).\n" "@return True to terminate dispatcher\n" ) + - callback ("mouse_click_event", &gsi::PluginBase::mouse_click_event_noref, &gsi::PluginBase::f_mouse_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("mouse_click_event", &gsi::PluginImpl::mouse_click_event_noref, &gsi::PluginImpl::f_mouse_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button click event (after the button has been released)\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been released without moving it.\n" ) + - callback ("mouse_double_click_event", &gsi::PluginBase::mouse_double_click_event_noref, &gsi::PluginBase::f_mouse_double_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("mouse_double_click_event", &gsi::PluginImpl::mouse_double_click_event_noref, &gsi::PluginImpl::f_mouse_double_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button double-click event\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been double-clicked.\n" ) + - callback ("leave_event", &gsi::PluginBase::leave_event, &gsi::PluginBase::f_leave_event, gsi::arg ("prio"), + callback ("leave_event", &gsi::PluginImpl::leave_event, &gsi::PluginImpl::f_leave_event, gsi::arg ("prio"), "@brief Handles the leave event (mouse leaves canvas area of view)\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse leaves the canvas area.\n" "This method does not have a position nor button flags.\n" ) + - callback ("enter_event", &gsi::PluginBase::enter_event, &gsi::PluginBase::f_enter_event, gsi::arg ("prio"), + callback ("enter_event", &gsi::PluginImpl::enter_event, &gsi::PluginImpl::f_enter_event, gsi::arg ("prio"), "@brief Handles the enter event (mouse enters canvas area of view)\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse enters the canvas area.\n" "This method does not have a position nor button flags.\n" ) + - callback ("mouse_moved_event", &gsi::PluginBase::mouse_move_event_noref, &gsi::PluginBase::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("mouse_moved_event", &gsi::PluginImpl::mouse_move_event_noref, &gsi::PluginImpl::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse move event\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse is moved in the canvas area.\n" "\n" "The mouse move event is important for a number of background jobs, such as coordinate display in the status bar.\n" "Hence, you should not consume the event - i.e. you should return 'false' from this method.\n" ) + - callback ("mouse_button_released_event", &gsi::PluginBase::mouse_release_event_noref, &gsi::PluginBase::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("mouse_button_released_event", &gsi::PluginImpl::mouse_release_event_noref, &gsi::PluginImpl::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button release event\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button is released.\n" ) + - callback ("wheel_event", &gsi::PluginBase::wheel_event_noref, &gsi::PluginBase::f_wheel_event, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + callback ("wheel_event", &gsi::PluginImpl::wheel_event_noref, &gsi::PluginImpl::f_wheel_event, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), + "@brief Handles the mouse wheel event\n" "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse wheel is rotated.\n" "Additional parameters for this event are 'delta' (the rotation angle in units of 1/8th degree) and 'horizontal' which is true when the horizontal wheel was rotated and " "false if the vertical wheel was rotated.\n" ) + - callback ("activated", &gsi::PluginBase::activated, &gsi::PluginBase::f_activated, + callback ("activated", &gsi::PluginImpl::activated, &gsi::PluginImpl::f_activated, "@brief Gets called when the plugin is activated (selected in the tool bar)\n" ) + - callback ("deactivated", &gsi::PluginBase::deactivated, &gsi::PluginBase::f_deactivated, + callback ("deactivated", &gsi::PluginImpl::deactivated, &gsi::PluginImpl::f_deactivated, "@brief Gets called when the plugin is deactivated and another plugin is activated\n" ) + - callback ("drag_cancel", &gsi::PluginBase::drag_cancel, &gsi::PluginBase::f_drag_cancel, - "@brief Gets called on various occasions when a drag operation should be canceled\n" + callback ("drag_cancel", &gsi::PluginImpl::drag_cancel, &gsi::PluginImpl::f_drag_cancel, + "@brief Gets called on various occasions when some mouse drag operation should be canceled\n" "If the plugin implements some press-and-drag or a click-and-drag operation, this callback should " - "cancel this operation and return in some state waiting for a new mouse event." + "cancel this operation and return to some state waiting for a new mouse event." ) + - callback ("update", &gsi::PluginBase::update, &gsi::PluginBase::f_update, + callback ("update", &gsi::PluginImpl::update, &gsi::PluginImpl::f_update, "@brief Gets called when the view has changed\n" "This method is called in particular if the view has changed the visible rectangle, i.e. after zooming in or out or panning. " "This callback can be used to update any internal states that depend on the view's state." ) + - method ("grab_mouse", &gsi::PluginBase::grab_mouse, + method ("grab_mouse", &gsi::PluginImpl::grab_mouse, "@brief Redirects mouse events to this plugin, even if the plugin is not active.\n" ) + - method ("ungrab_mouse", &gsi::PluginBase::ungrab_mouse, + method ("ungrab_mouse", &gsi::PluginImpl::ungrab_mouse, "@brief Removes a mouse grab registered with \\grab_mouse.\n" ) + - method ("set_cursor", &gsi::PluginBase::set_cursor, gsi::arg ("cursor_type"), + method ("set_cursor", &gsi::PluginImpl::set_cursor, gsi::arg ("cursor_type"), "@brief Sets the cursor in the view area to the given type\n" "Setting the cursor has an effect only inside event handlers, i.e. \\mouse_button_pressed_event. The cursor is not set permanently. Is is reset " "in the mouse move handler unless a button is pressed or the cursor is explicitly set again in \\mouse_moved_event.\n" "\n" "The cursor type is one of the cursor constants in the \\Cursor class, i.e. 'CursorArrow' for the normal cursor." ) + - method ("has_tracking_position_test", &gsi::PluginBase::has_tracking_position_test, "@hide") + - method ("has_tracking_position", &gsi::PluginBase::has_tracking_position_base, "@hide") + - callback ("has_tracking_position", &gsi::PluginBase::has_tracking_position, &gsi::PluginBase::f_has_tracking_position, + method ("has_tracking_position_test", &gsi::PluginImpl::has_tracking_position_test, "@hide") + + callback ("has_tracking_position", &gsi::PluginImpl::has_tracking_position, &gsi::PluginImpl::f_has_tracking_position, "@brief Gets a value indicating whether the plugin provides a tracking position\n" "The tracking position is shown in the lower-left corner of the layout window to indicate the current position.\n" "If this method returns true for the active service, the application will fetch the position by calling \\tracking_position " @@ -659,9 +801,8 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + - method ("tracking_position_test", &gsi::PluginBase::tracking_position_test, "@hide") + - method ("tracking_position", &gsi::PluginBase::tracking_position_base, "@hide") + - callback ("tracking_position", &gsi::PluginBase::tracking_position, &gsi::PluginBase::f_tracking_position, + method ("tracking_position_test", &gsi::PluginImpl::tracking_position_test, "@hide") + + callback ("tracking_position", &gsi::PluginImpl::tracking_position, &gsi::PluginImpl::f_tracking_position, "@brief Gets the tracking position\n" "See \\has_tracking_position for details.\n" "\n" @@ -672,14 +813,14 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.27.6." ) + - method ("clear_mouse_cursors", &gsi::PluginBase::clear_mouse_cursors, + method ("clear_mouse_cursors", &gsi::PluginImpl::clear_mouse_cursors, "@brief Clears all existing mouse cursors\n" "Use this function to remove exisiting mouse cursors (see \\add_mouse_cursor and \\add_edge_marker).\n" "This method is automatically called when the plugin becomes deactivated.\n" "\n" "This method has been added in version 0.30.4." ) + - method ("add_mouse_cursor", &gsi::PluginBase::add_mouse_cursor_dpoint, gsi::arg ("p"), gsi::arg ("emphasize", false), + method ("add_mouse_cursor", &gsi::PluginImpl::add_mouse_cursor_dpoint, gsi::arg ("p"), gsi::arg ("emphasize", false), "@brief Creates a cursor to indicate the mouse position\n" "This function will create a marker that indicates the (for example snapped) mouse position.\n" "In addition to this, it will establish the position for the tracking cursor, if mouse\n" @@ -697,7 +838,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("add_mouse_cursor", &gsi::PluginBase::add_mouse_cursor_point, gsi::arg ("p"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + method ("add_mouse_cursor", &gsi::PluginImpl::add_mouse_cursor_point, gsi::arg ("p"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), "@brief Creates a cursor to indicate the mouse position\n" "This version of this method creates a mouse cursor based on the integer-unit point and\n" "a source cellview index plus a layer info.\n" @@ -706,7 +847,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("add_edge_marker", &gsi::PluginBase::add_edge_marker_dedge, gsi::arg ("e"), gsi::arg ("emphasize", false), + method ("add_edge_marker", &gsi::PluginImpl::add_edge_marker_dedge, gsi::arg ("e"), gsi::arg ("emphasize", false), "@brief Creates a cursor to indicate an edge\n" "This function will create a marker that indicates an edge - for example the edge that a point is snapping to. " "\n" @@ -716,7 +857,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("add_edge_marker", &gsi::PluginBase::add_edge_marker_edge, gsi::arg ("e"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), + method ("add_edge_marker", &gsi::PluginImpl::add_edge_marker_edge, gsi::arg ("e"), gsi::arg ("cv_index"), gsi::arg ("layer"), gsi::arg ("emphasize", false), "@brief Creates a cursor to indicate an edge\n" "This version of this method creates an edge marker based on the integer-unit edge and\n" "a source cellview index plus a layer info.\n" @@ -733,7 +874,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap", &gsi::PluginBase::snap, gsi::arg ("p"), + method ("snap", &gsi::PluginImpl::snap, gsi::arg ("p"), "@brief Snaps a point to the edit grid\n" "\n" "@param p The point to snap\n" @@ -744,7 +885,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap", &gsi::PluginBase::snap_vector, gsi::arg ("v"), + method ("snap", &gsi::PluginImpl::snap_vector, gsi::arg ("v"), "@brief Snaps a vector to the edit grid\n" "\n" "@param v The vector to snap\n" @@ -755,7 +896,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap", &gsi::PluginBase::snap_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + method ("snap", &gsi::PluginImpl::snap_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), "@brief Snaps a point to the edit grid with an angle constraint\n" "\n" "@param p The point to snap\n" @@ -778,7 +919,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap", &gsi::PluginBase::snap_delta, gsi::arg ("v"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), + method ("snap", &gsi::PluginImpl::snap_delta, gsi::arg ("v"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), "@brief Snaps a move vector to the edit grid with and implies an angle constraint\n" "\n" "@param v The vector to snap\n" @@ -795,7 +936,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap2", &gsi::PluginBase::snap2, gsi::arg ("p"), gsi::arg ("visualize", false), + method ("snap2", &gsi::PluginImpl::snap2, gsi::arg ("p"), gsi::arg ("visualize", false), "@brief Snaps a point to the edit grid with advanced snapping (including object snapping)\n" "\n" "@param p The point to snap\n" @@ -810,7 +951,7 @@ Class decl_Plugin ("lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("snap2", &gsi::PluginBase::snap2_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), gsi::arg ("visualize", false), + method ("snap2", &gsi::PluginImpl::snap2_from_to, gsi::arg ("p"), gsi::arg ("plast"), gsi::arg ("connect", false), gsi::arg ("ac", lay::AC_Global, "AC_Global"), gsi::arg ("visualize", false), "@brief Snaps a point to the edit grid with an angle constraint with advanced snapping (including object snapping)\n" "\n" "@param p The point to snap\n" @@ -836,7 +977,7 @@ Class decl_Plugin ("lay", "Plugin", "This method has been added in version 0.30.4." ) + #if defined(HAVE_QTBINDINGS) - gsi::method ("editor_options_pages", &gsi::PluginBase::editor_options_pages, + gsi::method ("editor_options_pages", &gsi::PluginImpl::editor_options_pages, "@brief Gets the editor options pages which are associated with the view\n" "The editor options pages are created by the plugin factory class and are associated with this plugin.\n" "This method allows locating them and using them for plugin-specific purposes.\n" @@ -844,13 +985,13 @@ Class decl_Plugin ("lay", "Plugin", "This method has been added in version 0.30.4." ) + #endif - gsi::method ("view", &gsi::PluginBase::view, + gsi::method ("view", &gsi::PluginImpl::view, "@brief Gets the view object the plugin is associated with\n" "This method returns the view object that the plugin is associated with.\n" "\n" "This convenience method has been added in version 0.30.4." ) + - gsi::method ("dispatcher", &gsi::PluginBase::dispatcher, + gsi::method ("dispatcher", &gsi::PluginImpl::dispatcher, "@brief Gets the dispatcher object the plugin is associated with\n" "This method returns the dispatcher object that the plugin is associated with.\n" "The dispatcher object manages the configuration parameters. 'set_config', 'get_config' and 'commit_config' " @@ -893,7 +1034,7 @@ gsi::Enum decl_AngleConstraintType ("lay", "AngleCon "This enum has been introduced in version 0.30.4." ); -gsi::ClassExt inject_AngleConstraintType_in_parent (decl_AngleConstraintType.defs ()); +gsi::ClassExt inject_AngleConstraintType_in_parent (decl_AngleConstraintType.defs ()); class CursorNamespace { }; diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h index 7bf5ce48d..fa639e518 100644 --- a/src/lay/lay/gsiDeclLayPlugin.h +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -32,11 +32,11 @@ namespace gsi { -class PluginBase +class PluginImpl : public lay::EditorServiceBase { public: - PluginBase (); + PluginImpl (); void init (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); void grab_mouse (); @@ -63,24 +63,18 @@ public: virtual void config_finalize (); virtual bool key_event (unsigned int key, unsigned int buttons); virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio) ; - // NOTE: this version doesn't take a point reference which allows up to store the point bool mouse_press_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); - // NOTE: this version doesn't take a point reference which allows up to store the point bool mouse_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool mouse_double_click_event (const db::DPoint &p, unsigned int buttons, bool prio); - // NOTE: this version doesn't take a point reference which allows up to store the point bool mouse_double_click_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool leave_event (bool prio); virtual bool enter_event (bool prio); virtual bool mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio); - // NOTE: this version doesn't take a point reference which allows up to store the point bool mouse_move_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool mouse_release_event (const db::DPoint &p, unsigned int buttons, bool prio); - // NOTE: this version doesn't take a point reference which allows up to store the point bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); - // NOTE: this version doesn't take a point reference which allows up to store the point bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio); virtual void activated (); void deactivated_impl (); @@ -94,12 +88,10 @@ public: // for testing bool has_tracking_position_test () const; - bool has_tracking_position_base () const; virtual bool has_tracking_position () const; // for testing db::DPoint tracking_position_test () const; - db::DPoint tracking_position_base () const; virtual db::DPoint tracking_position () const; virtual lay::ViewService *view_service_interface () diff --git a/src/lay/lay/gsiDeclLayPluginFactory.cc b/src/lay/lay/gsiDeclLayPluginFactory.cc index 1bbadd3dd..c840b7755 100644 --- a/src/lay/lay/gsiDeclLayPluginFactory.cc +++ b/src/lay/lay/gsiDeclLayPluginFactory.cc @@ -242,14 +242,14 @@ public: } } - virtual gsi::PluginBase *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const + virtual gsi::PluginImpl *create_plugin_gsi (db::Manager *manager, lay::Dispatcher *root, lay::LayoutViewBase *view) const { s_in_create_plugin = true; - gsi::PluginBase *ret = 0; + gsi::PluginImpl *ret = 0; try { - ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); + ret = f_create_plugin.issue (&PluginFactoryBase::create_plugin_gsi, manager, root, view); if (ret) { ret->init (view, root); } diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index f41afbf85..7a73c8bb8 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -141,16 +141,128 @@ public: */ void show_error (tl::Exception &ex); + /** + * @brief Menu command handler + */ + virtual void menu_activated (const std::string & /*symbol*/) + { + // .. this implementation does nothing .. + } + /** * @brief Sets a configuration option */ virtual bool configure (const std::string &name, const std::string &value); + /** + * @brief Configuration finalization + */ + virtual void config_finalize () + { + lay::Plugin::config_finalize (); + } + /** * @brief Called when the plugin is deactivated */ virtual void deactivated (); + /** + * @brief Called when the plugin is activated + */ + virtual void activated () + { + // .. this implementation does nothing .. + } + + /** + * @brief Key event handler + */ + virtual bool key_event (unsigned int /*key*/, unsigned int /*buttons*/) + { + return false; + } + + /** + * @brief Mouse press event handler + */ + virtual bool mouse_press_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse single-click event handler + */ + virtual bool mouse_click_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse double-click event handler + */ + virtual bool mouse_double_click_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse leave event handler + */ + virtual bool leave_event (bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse enter event handler + */ + virtual bool enter_event (bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse move event handler + */ + virtual bool mouse_move_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Mouse release event handler + */ + virtual bool mouse_release_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Wheel event handler + */ + virtual bool wheel_event (int /*delta*/, bool /*horizontal*/, const db::DPoint & /*p*/, unsigned int /*buttons*/, bool /*prio*/) + { + return false; + } + + /** + * @brief Updates the internal data after a coordinate system change for example + */ + virtual void update () + { + // The default implementation does nothing + } + + /** + * @brief This method is called when some mouse dragging operation should be cancelled + */ + virtual void drag_cancel () + { + // The default implementation does nothing + } + private: // The marker representing the mouse cursor lay::LayoutViewBase *mp_view; From e21eb6aafc96e045e9dc72220caf758b4a846726 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 31 Aug 2025 17:52:04 +0200 Subject: [PATCH 21/52] Typo fixed --- src/lay/lay/macro_templates/drag_box_sample.lym | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lay/lay/macro_templates/drag_box_sample.lym b/src/lay/lay/macro_templates/drag_box_sample.lym index 7d29c2be1..f747fded3 100644 --- a/src/lay/lay/macro_templates/drag_box_sample.lym +++ b/src/lay/lay/macro_templates/drag_box_sample.lym @@ -72,7 +72,7 @@ class DragBoxConfigPage < RBA::ConfigPage # and "Configure" page super("Drag Box|Configure") - # Qt user interfact setup + # Qt user interface setup layout = RBA::QHBoxLayout::new(self) label = RBA::QLabel::new("Color (hex, rrggbb)", self) layout.addWidget(label) From 1e570351fbcc7ef0fdfdd0eec640557e0de09b00 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 31 Aug 2025 20:37:48 +0200 Subject: [PATCH 22/52] Refactoring (GSI base class for EditorOptionsPage), preparations for 'focus_page' feature --- src/lay/lay/gsiDeclLayEditorOptionsPage.cc | 53 +++++++++++++++++-- src/lay/lay/gsiDeclLayPlugin.cc | 32 ++++------- src/lay/lay/gsiDeclLayPlugin.h | 5 +- src/laybasic/laybasic/layEditorOptionsPage.cc | 12 ++++- src/laybasic/laybasic/layEditorOptionsPage.h | 5 ++ .../laybasic/layEditorOptionsPages.cc | 13 ++++- src/laybasic/laybasic/layEditorOptionsPages.h | 1 + src/laybasic/laybasic/layEditorServiceBase.cc | 50 ++++++++++++++++- src/laybasic/laybasic/layEditorServiceBase.h | 26 ++++++--- 9 files changed, 154 insertions(+), 43 deletions(-) diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc index 318aa1385..b5860e3ef 100644 --- a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -30,6 +30,51 @@ namespace gsi { +Class decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPageBase", + method ("view", &lay::EditorOptionsPage::view, + "@brief Gets the view object this page is associated with\n" + ) + + method ("title", &lay::EditorOptionsPage::title, + "@brief Gets the title string of the page\n" + ) + + method ("order", &lay::EditorOptionsPage::order, + "@brief Gets the order index of the page\n" + ) + + method ("is_focus_page?", &lay::EditorOptionsPage::is_focus_page, + "@brief Gets a flag indicating whether the page is a focus page\n" + "See \\focus_page= for a description is this attribute.\n" + ) + + method ("focus_page=", &lay::EditorOptionsPage::set_focus_page, gsi::arg ("flag"), + "@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 ("do_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"), + "@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" + "This class is provided as an interface to the base class implementation for various functions.\n" + "You can use these methods in order to pass down events to the original implementation or access\n" + "objects not created in script space.\n" + "\n" + "It features some useful methods such as 'view' and provides a slot to call for triggering a data " + "transfer ('edited').\n" + "\n" + "This class has been introduced in version 0.30.4.\n" +); + EditorOptionsPageImpl::EditorOptionsPageImpl (const std::string &title, int index) : lay::EditorOptionsPage (), m_title (title), m_index (index) { @@ -79,15 +124,12 @@ EditorOptionsPageImpl *new_editor_options_page (const std::string &title, int in return new EditorOptionsPageImpl (title, index); } -Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) "lay", "EditorOptionsPage", +Class decl_EditorOptionsPage (decl_EditorOptionsPageBase, "lay", "EditorOptionsPage", constructor ("new", &new_editor_options_page, gsi::arg ("title"), gsi::arg ("index"), "@brief Creates a new EditorOptionsPage object\n" "@param title The title of the page\n" "@param index The position of the page in the tab bar\n" ) + - method ("view", &EditorOptionsPageImpl::view, - "@brief Gets the view object this page is associated with\n" - ) + method ("edited", &EditorOptionsPageImpl::call_edited, "@brief Call this method when some entry widget has changed\n" "When some entry widget (for example 'editingFinished' slot of a QLineEdit), " @@ -117,6 +159,9 @@ Class decl_EditorOptionsPage (QT_EXTERNAL_BASE (QWidget) "configuration parameters into editor widget states. Both methods are called by the system when " "some transfer is needed.\n" "\n" + "When you want to respond to widget signals and transfer information, call \\edited " + "in the signal slot. This will trigger a transfer (aka 'apply').\n" + "\n" "This class has been introduced in version 0.30.4.\n" ); diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 9b40b42c3..1d055a537 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -507,15 +507,20 @@ PluginImpl::wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigne } void -PluginImpl::activated () +PluginImpl::activated_impl () { if (f_activated.can_issue ()) { - f_activated.issue (&lay::EditorServiceBase::activated); - } else { - lay::EditorServiceBase::activated (); + f_activated.issue (&PluginImpl::activated_impl); } } +void +PluginImpl::activated () +{ + lay::EditorServiceBase::activated (); + activated_impl (); +} + void PluginImpl::deactivated_impl () { @@ -641,25 +646,6 @@ PluginImpl::tracking_position () const } } -#if defined(HAVE_QTBINDINGS) -std::vector -PluginImpl::editor_options_pages () -{ - lay::EditorOptionsPages *eo_pages = view ()->editor_options_pages (); - if (!eo_pages) { - return std::vector (); - } else { - std::vector pages; - for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { - if ((*p)->plugin_declaration () == plugin_declaration ()) { - pages.push_back (*p); - } - } - return pages; - } -} -#endif - lay::angle_constraint_type PluginImpl::connect_ac (lay::angle_constraint_type ac) const { diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h index fa639e518..52bd3b448 100644 --- a/src/lay/lay/gsiDeclLayPlugin.h +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -76,6 +76,7 @@ public: bool mouse_release_event_noref (db::DPoint p, unsigned int buttons, bool prio); virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); bool wheel_event_noref (int delta, bool horizontal, db::DPoint p, unsigned int buttons, bool prio); + void activated_impl (); virtual void activated (); void deactivated_impl (); virtual void deactivated (); @@ -109,10 +110,6 @@ public: return const_cast (mp_dispatcher.get ()); } -#if defined(HAVE_QTBINDINGS) - std::vector editor_options_pages (); -#endif - gsi::Callback f_menu_activated; gsi::Callback f_configure; gsi::Callback f_config_finalize; diff --git a/src/laybasic/laybasic/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc index 3f6a3046c..7ef7ded95 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -34,13 +34,13 @@ namespace lay // EditorOptionsPage implementation EditorOptionsPage::EditorOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) - : QWidget (0), mp_owner (0), m_active (true), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view) + : QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), mp_plugin_declaration (0), mp_dispatcher (dispatcher), mp_view (view) { attach_events (); } EditorOptionsPage::EditorOptionsPage () - : QWidget (0), mp_owner (0), m_active (true), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0) + : QWidget (0), mp_owner (0), m_active (true), m_focus_page (false), mp_plugin_declaration (0), mp_dispatcher (0), mp_view (0) { // .. nothing here -> call init to set view and dispatcher } @@ -58,6 +58,14 @@ EditorOptionsPage::~EditorOptionsPage () set_owner (0); } +void +EditorOptionsPage::make_current () +{ + if (mp_owner && m_active) { + mp_owner->make_page_current (this); + } +} + void EditorOptionsPage::attach_events () { diff --git a/src/laybasic/laybasic/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h index 9bbfae510..298cbc183 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -60,10 +60,14 @@ public: virtual void setup (lay::Dispatcher * /*root*/) { } virtual void commit_recent (lay::Dispatcher * /*root*/) { } + bool is_focus_page () const { return m_focus_page; } + void set_focus_page (bool f) { m_focus_page = f; } bool active () const { return m_active; } void activate (bool active); void set_owner (EditorOptionsPages *owner); + void make_current (); + const lay::PluginDeclaration *plugin_declaration () const { return mp_plugin_declaration; } void set_plugin_declaration (const lay::PluginDeclaration *pd) { mp_plugin_declaration = pd; } @@ -92,6 +96,7 @@ protected: private: EditorOptionsPages *mp_owner; bool m_active; + bool m_focus_page; const lay::PluginDeclaration *mp_plugin_declaration; lay::Dispatcher *mp_dispatcher; lay::LayoutViewBase *mp_view; diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index eefe25ee9..738528d27 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -120,7 +120,18 @@ EditorOptionsPages::unregister_page (lay::EditorOptionsPage *page) update (0); } -void +void +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); + break; + } + } +} + +void EditorOptionsPages::activate_page (lay::EditorOptionsPage *page) { try { diff --git a/src/laybasic/laybasic/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h index dd4e1b15c..10024e982 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -60,6 +60,7 @@ public: void activate_page (lay::EditorOptionsPage *page); void activate (const lay::Plugin *plugin); void focusInEvent (QFocusEvent *event); + void make_page_current (lay::EditorOptionsPage *page); const std::vector &pages () const { diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index 1a3e4c86c..e20f200a5 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -21,6 +21,8 @@ */ #include "layEditorServiceBase.h" +#include "layEditorOptionsPage.h" +#include "layEditorOptionsPages.h" #include "layViewport.h" #include "layLayoutViewBase.h" #include "laybasicConfig.h" @@ -212,7 +214,8 @@ EditorServiceBase::EditorServiceBase (LayoutViewBase *view) lay::Plugin (view), mp_view (view), m_cursor_enabled (true), - m_has_tracking_position (false) + m_has_tracking_position (false), + m_active (false) { // .. nothing yet .. } @@ -328,6 +331,51 @@ void EditorServiceBase::deactivated () { clear_mouse_cursors (); + m_active = false; +} + +void +EditorServiceBase::activated () +{ + m_active = true; +} + +#if defined(HAVE_QT) +std::vector +EditorServiceBase::editor_options_pages () +{ + lay::EditorOptionsPages *eo_pages = mp_view->editor_options_pages (); + if (!eo_pages) { + return std::vector (); + } else { + std::vector pages; + for (auto p = eo_pages->pages ().begin (); p != eo_pages->pages ().end (); ++p) { + if ((*p)->plugin_declaration () == plugin_declaration ()) { + pages.push_back (*p); + } + } + return pages; + } +} +#endif + +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)->make_current (); + (*p)->setFocus (Qt::TabFocusReason); + return true; + } + } + } +#endif + + return false; } void diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 7a73c8bb8..1c50bebbc 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -74,6 +74,14 @@ public: return this; } + /** + * @brief Gets a value indicating whether the plugin is active + */ + bool is_active () const + { + return m_active; + } + /** * @brief Adds a mouse cursor to the given point */ @@ -170,18 +178,12 @@ public: /** * @brief Called when the plugin is activated */ - virtual void activated () - { - // .. this implementation does nothing .. - } + virtual void activated (); /** * @brief Key event handler */ - virtual bool key_event (unsigned int /*key*/, unsigned int /*buttons*/) - { - return false; - } + virtual bool key_event (unsigned int /*key*/, unsigned int /*buttons*/); /** * @brief Mouse press event handler @@ -263,6 +265,13 @@ public: // The default implementation does nothing } +#if defined(HAVE_QT) + /** + * @brief Gets the editor options pages associated with this plugin + */ + std::vector editor_options_pages (); +#endif + private: // The marker representing the mouse cursor lay::LayoutViewBase *mp_view; @@ -271,6 +280,7 @@ private: bool m_cursor_enabled; bool m_has_tracking_position; db::DPoint m_tracking_position; + bool m_active; }; } From 2b04ecb1f7f492d656546ff3bd11e6406c68a7fd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 2 Sep 2025 00:45:42 +0200 Subject: [PATCH 23/52] WIP: experimental - modal editor options pages --- src/lay/lay/gsiDeclLayEditorOptionsPage.cc | 21 ++- src/laybasic/laybasic/layEditorOptionsPage.cc | 75 ++++++++- src/laybasic/laybasic/layEditorOptionsPage.h | 16 +- .../laybasic/layEditorOptionsPages.cc | 156 ++++++++++++++++-- src/laybasic/laybasic/layEditorOptionsPages.h | 40 ++++- src/laybasic/laybasic/layEditorServiceBase.cc | 3 +- 6 files changed, 272 insertions(+), 39 deletions(-) diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc index b5860e3ef..a59044b35 100644 --- a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -48,10 +48,20 @@ Class 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 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" ); diff --git a/src/laybasic/laybasic/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc index 7ef7ded95..a33d53c4b 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -27,6 +27,9 @@ #include "layEditorOptionsPages.h" #include "layLayoutViewBase.h" +#include +#include + 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 (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); + } } } diff --git a/src/laybasic/laybasic/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h index 85b92a57d..50e90eb54 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -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; diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 738528d27..e1efbc650 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -34,6 +34,8 @@ #include #include #include +#include +#include namespace lay { @@ -52,6 +54,8 @@ struct EOPCompareOp EditorOptionsPages::EditorOptionsPages (QWidget *parent, const std::vector &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 ::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 ::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 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 ::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 (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 ::iterator p = m_pages.begin (); p != m_pages.end (); ++p) { - if ((*p)->active ()) { - (*p)->setup (mp_dispatcher); - } + for (std::vector ::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 diff --git a/src/laybasic/laybasic/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h index 10024e982..b549e1dd6 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -28,14 +28,16 @@ #include "laybasicCommon.h" #include "layEditorOptionsPage.h" -#include - #include +#include + #include #include 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 &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 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 diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index e20f200a5..e28e1ba5e 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -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; } } From a24e2431a20bfa559963e3d1c1998f9c7cfcc4d3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 2 Sep 2025 20:28:55 +0200 Subject: [PATCH 24/52] WIP: automatically calling ungrab_mouse on deactivation of plugin --- src/laybasic/laybasic/layEditorServiceBase.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index e28e1ba5e..06f736516 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -331,6 +331,9 @@ void EditorServiceBase::deactivated () { clear_mouse_cursors (); + if (ui ()) { + ui ()->ungrab_mouse (this); + } m_active = false; } From 0447080d17ade0117836d1ec28a175345038e084 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 2 Sep 2025 21:21:00 +0200 Subject: [PATCH 25/52] WIP: polishing geometry of modal editor options widget --- .../laybasic/layEditorOptionsPages.cc | 146 +++++++++++++++--- src/laybasic/laybasic/layEditorOptionsPages.h | 14 +- 2 files changed, 131 insertions(+), 29 deletions(-) diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index e1efbc650..650c26314 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -116,14 +116,12 @@ EditorOptionsPages::has_modal_content () const bool EditorOptionsPages::exec_modal (EditorOptionsPage *page) { - QTabWidget *modal_pages = mp_modal_pages->pages_widget (); + for (int i = 0; i < mp_modal_pages->count (); ++i) { - for (int i = 0; i < modal_pages->count (); ++i) { - - if (modal_pages->widget (i) == page) { + if (mp_modal_pages->widget (i) == page) { // found the page - make it current and show the dialog - modal_pages->setCurrentIndex (i); + mp_modal_pages->set_current_index (i); page->set_focus (); return mp_modal_pages->exec () != 0; @@ -193,8 +191,6 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page) std::vector 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 (); } @@ -203,8 +199,8 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page) mp_pages->removeTab (0); } - while (modal_pages->count () > 0) { - modal_pages->removeTab (0); + while (mp_modal_pages->count () > 0) { + mp_modal_pages->remove_page (0); } int index = -1; @@ -219,22 +215,15 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page) mp_pages->addTab (*p, tl::to_qstring ((*p)->title ())); } else { if ((*p) == page) { - modal_index = modal_pages->count (); + modal_index = mp_modal_pages->count (); } - modal_pages->addTab (*p, tl::to_qstring ((*p)->title ())); + mp_modal_pages->add_page (*p); } } else { (*p)->setParent (0); } } - lay::EditorOptionsPage *single_modal_page = modal_pages->count () == 1 ? dynamic_cast (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 (); } @@ -244,12 +233,12 @@ EditorOptionsPages::update (lay::EditorOptionsPage *page) mp_pages->setCurrentIndex (index); if (modal_index < 0) { - modal_index = modal_pages->currentIndex (); + modal_index = mp_modal_pages->current_index (); } - if (modal_index >= int (modal_pages->count ())) { - modal_index = modal_pages->count () - 1; + if (modal_index >= int (mp_modal_pages->count ())) { + modal_index = mp_modal_pages->count () - 1; } - modal_pages->setCurrentIndex (modal_index); + mp_modal_pages->set_current_index (modal_index); setVisible (mp_pages->count () > 0); } @@ -295,22 +284,38 @@ END_PROTECTED_W (this) // EditorOptionsModalPages implementation EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent) - : QDialog (parent), mp_parent (parent) + : QDialog (parent), mp_parent (parent), mp_single_page (0) { QVBoxLayout *ly = new QVBoxLayout (this); + ly->setContentsMargins (0, 0, 0, 0); + QVBoxLayout *ly4 = new QVBoxLayout (this); + ly4->setContentsMargins (6, 6, 6, 0); + ly->addLayout (ly4); mp_pages = new QTabWidget (this); - ly->addWidget (mp_pages, 1); + ly4->addWidget (mp_pages, 1); mp_pages->setTabBarAutoHide (true); + mp_pages->hide (); + mp_single_page_frame = new QFrame (this); + QVBoxLayout *ly2 = new QVBoxLayout (mp_single_page_frame); + ly2->setContentsMargins (0, 0, 0, 0); + ly->addWidget (mp_single_page_frame, 1); + mp_single_page_frame->hide (); + + QVBoxLayout *ly3 = new QVBoxLayout (this); + ly->addLayout (ly3); + ly3->setContentsMargins (6, 6, 6, 6); mp_button_box = new QDialogButtonBox (this); - ly->addWidget (mp_button_box); + ly3->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())); + + update_title (); } EditorOptionsModalPages::~EditorOptionsModalPages () @@ -318,6 +323,97 @@ EditorOptionsModalPages::~EditorOptionsModalPages () // .. nothing yet .. } +int +EditorOptionsModalPages::count () +{ + return mp_single_page ? 1 : mp_pages->count (); +} + +int +EditorOptionsModalPages::current_index () +{ + return mp_single_page ? 0 : mp_pages->currentIndex (); +} + +void +EditorOptionsModalPages::set_current_index (int index) +{ + if (! mp_single_page) { + mp_pages->setCurrentIndex (index); + } +} + +void +EditorOptionsModalPages::add_page (EditorOptionsPage *page) +{ + if (! mp_single_page) { + if (mp_pages->count () == 0) { + mp_single_page = page; + mp_single_page->setParent (mp_single_page_frame); + mp_single_page_frame->layout ()->addWidget (mp_single_page); + mp_single_page_frame->show (); + mp_pages->hide (); + } else { + mp_pages->addTab (page, tl::to_qstring (page->title ())); + } + } else { + mp_pages->clear (); + mp_single_page_frame->layout ()->removeWidget (mp_single_page); + mp_single_page_frame->hide (); + mp_pages->addTab (mp_single_page, tl::to_qstring (mp_single_page->title ())); + mp_single_page = 0; + mp_pages->addTab (page, tl::to_qstring (page->title ())); + mp_pages->show (); + } + + update_title (); +} + +void +EditorOptionsModalPages::remove_page (int index) +{ + if (mp_single_page) { + if (index == 0) { + mp_single_page->setParent (0); + mp_single_page = 0; + mp_single_page_frame->hide (); + mp_single_page_frame->layout ()->removeWidget (mp_single_page); + } + } else { + mp_pages->removeTab (index); + if (mp_pages->count () == 1) { + mp_pages->hide (); + mp_single_page = dynamic_cast (mp_pages->widget (0)); + mp_pages->removeTab (0); + mp_single_page->setParent (mp_single_page_frame); + mp_single_page_frame->layout ()->addWidget (mp_single_page); + mp_single_page_frame->show (); + } + } + + update_title (); +} + +void +EditorOptionsModalPages::update_title () +{ + if (mp_single_page) { + setWindowTitle (tl::to_qstring (mp_single_page->title ())); + } else { + setWindowTitle (tr ("Editor Options")); + } +} + +EditorOptionsPage * +EditorOptionsModalPages::widget (int index) +{ + if (mp_single_page) { + return index == 0 ? mp_single_page : 0; + } else { + return dynamic_cast (mp_pages->widget (index)); + } +} + void EditorOptionsModalPages::accept () { diff --git a/src/laybasic/laybasic/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h index b549e1dd6..97d5c07ba 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -100,10 +100,12 @@ public: EditorOptionsModalPages (EditorOptionsPages *parent); ~EditorOptionsModalPages (); - QTabWidget *pages_widget () - { - return mp_pages; - } + int count (); + int current_index (); + void set_current_index (int index); + void add_page (EditorOptionsPage *page); + void remove_page (int index); + EditorOptionsPage *widget (int index); private slots: void accept (); @@ -113,7 +115,11 @@ private slots: private: EditorOptionsPages *mp_parent; QTabWidget *mp_pages; + QFrame *mp_single_page_frame; + EditorOptionsPage *mp_single_page; QDialogButtonBox *mp_button_box; + + void update_title (); }; } From b9115fc0a2634901ac56f482343625aae3a7f10b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 3 Sep 2025 00:02:18 +0200 Subject: [PATCH 26/52] 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) --- src/lay/lay/gsiDeclLayEditorOptionsPage.cc | 23 ++++++-- src/lay/lay/gsiDeclLayEditorOptionsPage.h | 5 +- src/lay/lay/gsiDeclLayPlugin.cc | 25 ++++++++ src/lay/lay/gsiDeclLayPlugin.h | 3 + src/lay/lay/lay.pro | 1 - .../laybasic}/gsiDeclLayDispatcher.cc | 0 .../laybasic/gsiDeclLayLayoutViewBase.cc | 4 +- src/laybasic/laybasic/layEditorOptionsPage.cc | 13 ++-- src/laybasic/laybasic/layEditorOptionsPage.h | 6 +- .../laybasic/layEditorOptionsPages.cc | 19 ++++-- src/laybasic/laybasic/layEditorOptionsPages.h | 2 +- src/laybasic/laybasic/layEditorServiceBase.cc | 59 +++++++++++++++---- src/laybasic/laybasic/layEditorServiceBase.h | 12 ++++ src/laybasic/laybasic/layLayoutViewBase.h | 4 +- src/laybasic/laybasic/laybasic.pro | 1 + 15 files changed, 139 insertions(+), 38 deletions(-) rename src/{lay/lay => laybasic/laybasic}/gsiDeclLayDispatcher.cc (100%) diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc index a59044b35..c6bc94017 100644 --- a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -59,19 +59,16 @@ Class 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 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" diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.h b/src/lay/lay/gsiDeclLayEditorOptionsPage.h index f778150bd..0e1598898 100644 --- a/src/lay/lay/gsiDeclLayEditorOptionsPage.h +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.h @@ -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 mp_dispatcher; std::string m_title; int m_index; + + void apply_impl (lay::Dispatcher *root); + void setup_impl (lay::Dispatcher *root); }; } diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 1d055a537..88050e125 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -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::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 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" diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h index 52bd3b448..9fcd8a1bc 100644 --- a/src/lay/lay/gsiDeclLayPlugin.h +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -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 mp_view; diff --git a/src/lay/lay/lay.pro b/src/lay/lay/lay.pro index 4a920fc86..9ccbc0867 100644 --- a/src/lay/lay/lay.pro +++ b/src/lay/lay/lay.pro @@ -121,7 +121,6 @@ FORMS = \ SOURCES = \ gsiDeclLayApplication.cc \ gsiDeclLayConfigPage.cc \ - gsiDeclLayDispatcher.cc \ gsiDeclLayEditorOptionsPage.cc \ gsiDeclLayHelpDialog.cc \ gsiDeclLayMainWindow.cc \ diff --git a/src/lay/lay/gsiDeclLayDispatcher.cc b/src/laybasic/laybasic/gsiDeclLayDispatcher.cc similarity index 100% rename from src/lay/lay/gsiDeclLayDispatcher.cc rename to src/laybasic/laybasic/gsiDeclLayDispatcher.cc diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index 15925208b..3306852bc 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -497,7 +497,9 @@ static bool view_is_dirty (lay::LayoutViewBase *view) return view->is_dirty (); } -LAYBASIC_PUBLIC Class decl_LayoutViewBase ("lay", "LayoutViewBase", +extern Class decl_Dispatcher; + +LAYBASIC_PUBLIC Class 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" diff --git a/src/laybasic/laybasic/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc index a33d53c4b..0a31eb3eb 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -26,6 +26,7 @@ #include "layEditorOptionsPage.h" #include "layEditorOptionsPages.h" #include "layLayoutViewBase.h" +#include "tlExceptions.h" #include #include @@ -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; } } } diff --git a/src/laybasic/laybasic/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h index 50e90eb54..33b9fc1d3 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -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; } diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 650c26314..855515d2d 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -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 ::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 } } diff --git a/src/laybasic/laybasic/layEditorOptionsPages.h b/src/laybasic/laybasic/layEditorOptionsPages.h index 97d5c07ba..7ce48559c 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.h +++ b/src/laybasic/laybasic/layEditorOptionsPages.h @@ -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 (); }; /** diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index 06f736516..784889dac 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -344,6 +344,7 @@ EditorServiceBase::activated () } #if defined(HAVE_QT) + std::vector 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 + } diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 1c50bebbc..3fffcfcce 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -270,6 +270,18 @@ public: * @brief Gets the editor options pages associated with this plugin */ std::vector 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: diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index 4b839ed61..a19effbf0 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -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; diff --git a/src/laybasic/laybasic/laybasic.pro b/src/laybasic/laybasic/laybasic.pro index 8c5ba030d..52ce019fc 100644 --- a/src/laybasic/laybasic/laybasic.pro +++ b/src/laybasic/laybasic/laybasic.pro @@ -28,6 +28,7 @@ DEFINES += MAKE_LAYBASIC_LIBRARY SOURCES += \ gsiDeclLayLayers.cc \ + gsiDeclLayDispatcher.cc \ gsiDeclLayLayoutViewBase.cc \ gsiDeclLayMarker.cc \ gsiDeclLayMenu.cc \ From b3123d385a86073eba4d130977b8f54b1c16f4cb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 19:24:49 +0200 Subject: [PATCH 27/52] Updating samples to include focus pages --- .../lay/macro_templates/drag_box_sample.lym | 274 +++++++++++++---- .../drag_box_sample_python.lym | 285 ++++++++++++++++-- 2 files changed, 474 insertions(+), 85 deletions(-) diff --git a/src/lay/lay/macro_templates/drag_box_sample.lym b/src/lay/lay/macro_templates/drag_box_sample.lym index f747fded3..fbcea2c12 100644 --- a/src/lay/lay/macro_templates/drag_box_sample.lym +++ b/src/lay/lay/macro_templates/drag_box_sample.lym @@ -8,29 +8,39 @@ ruby # Sample plugin -# + # This plugin implements a box that can be drawn by # clicking at the first and then at the second point. # There is one box which is replacing the previous one. # Line color and line width of the box can be configured # by editor options (line width) or configuration pages -# (color). +# (color). These settings are managed through configuration +# options and their current state is persisted. +# +# The dimension of the box can be entered numerically +# while dragging the box. This feature is implemented +# through a modal "focus page", which opens when you +# press the Tab key during editing and when the keyboard +# focus is on the canvas. +# +# Register this macro as "autorun" to enable the plugin +# on startup. module DragBox -# Register this macro as "autorun" to enable the plugin - CFG_COLOR = "drag-box-color" CFG_WIDTH = "drag-box-width" -# The widget placed into the editor options dock +# An option page providing a single entry box for configuring the line width +# This page communicates via configuration options. One advantage of this +# approach is that the values are persisted class DragBoxEditorOptionsPage < RBA::EditorOptionsPage + + # Creates a new page with title "Options" and at position 1 (second from left) def initialize - - # Creates a new page with title "Options" and at - # position 1 (second from left) + super("Options", 1) layout2 = RBA::QVBoxLayout::new(self) @@ -44,10 +54,15 @@ class DragBoxEditorOptionsPage < RBA::EditorOptionsPage layout.addWidget(@spin_box) layout.addStretch(1) layout2.addStretch(1) + + # connect the spin box value change with the "edited" slot + # which will result in a call of "apply". @spin_box.valueChanged = lambda { |x| self.edited } end + # "setup" is called when the page needs to be populated with information - + # i.e. on first show. def setup(dispatcher) begin @spin_box.setValue(dispatcher.get_config(CFG_WIDTH).to_i) @@ -56,23 +71,123 @@ class DragBoxEditorOptionsPage < RBA::EditorOptionsPage end end + # "apply" is called when the page is requested to submit the entered + # values to the plugin. Usually this should be done via configuration + # events. def apply(dispatcher) dispatcher.set_config(CFG_WIDTH, @spin_box.value.to_s) end end -# The widget placed into the configuration page - -class DragBoxConfigPage < RBA::ConfigPage +# A (modal) option page, also called a "focus page". This page is +# registered like an editor options page. It is brought to front +# when the user hits the "Tab" key during editing. +# In this case, this page uses "setup" and "apply" callbacks to +# set and fetch information. It also employs a handler named +# "update_box" to communicate changes between the client (the +# plugin) and the page. +# +# Attributes that the client needs to take care of are +# "self.box" (the current box), "self.pfix" (the start point) +# and "self.update_box". + +class DragBoxFocusPage < RBA::EditorOptionsPage + + # Creates a new page with title "Options" and at + # position 1 (second from left) + + attr_accessor :box + attr_accessor :pfix + attr_accessor :update_box def initialize + + super("Geometry", 2) + + self.focus_page = true + self.modal_page = true + + @box = RBA::DBox::new + @pfix = RBA::DPoint::new + + layout = RBA::QGridLayout::new(self) + layout.setColumnStretch(1, 1) + + label = RBA::QLabel::new("Width", self) + layout.addWidget(label, 0, 0, 1, 1) + @le_width = RBA::QLineEdit::new(self) + layout.addWidget(@le_width, 0, 1, 1, 1) + + label = RBA::QLabel::new("Height", self) + layout.addWidget(label, 1, 0, 1, 1) + @le_height = RBA::QLineEdit::new(self) + layout.addWidget(@le_height, 1, 1, 1, 1) + + layout.setRowStretch(2, 1) + + end + + # Is called when the page needs to be set up. + # We assume that the client has properly set up self.box + def setup(dispatcher) + @le_width.text = "%.12g" % @box.width + @le_height.text = "%.12g" % @box.height + end + + # Apply is called when the dialog is accepted or the "Apply" button is pressed + # Usually this method is intended to submit configuration parameter changes, + # but we can use it for any other purpose as well. - # places the widget on a new section ("Drag Box") - # and "Configure" page + def apply(dispatcher) + + # fetches the coordinates from the entry boxes + # throws an exception in case of an error + x = @le_width.text.to_f + y = @le_height.text.to_f + + # prepares a new box with the given dimensions + # using the initial point ("pfix") and considering + # the drag direction + t = b = @pfix.y + l = r = @pfix.x + + if @box.bottom < t - 1e-10 + b = t - y + else + t = b + y + end + + if @box.left < l - 1e-10 + l = r - x + else + r = l + x + end + + # issue the event (call the handler) to inform the plugin of this change + if @update_box + @update_box.call(RBA::DBox::new(l, b, r, t)) + end + + end + +end + +# The widget placed into the configuration page + +# A configuration page with a single entry box to change +# the box color in RGB hex style. +# Configuration pages appear in the Setup dialog and can +# communicate only through configuration parameter updates. + +class DragBoxConfigPage < RBA::ConfigPage + + # Initializes the page. Places it on a new section ("Drag Box") and "Configure" page + # and creates a single entry field. + def initialize + super("Drag Box|Configure") - # Qt user interface setup layout = RBA::QHBoxLayout::new(self) label = RBA::QLabel::new("Color (hex, rrggbb)", self) layout.addWidget(label) @@ -82,22 +197,26 @@ class DragBoxConfigPage < RBA::ConfigPage end + # This method is called to request an update of the entry fields def setup(dispatcher) @line_edit.setText(dispatcher.get_config(CFG_COLOR)) end + # This method is called to request a transfer of the edited values + # to the configuration space. def apply(dispatcher) dispatcher.set_config(CFG_COLOR, @line_edit.text) end - end +# The custom plugin implementation. + class DragBoxPlugin < RBA::Plugin def initialize(view) super() @marker = nil - @last_box = nil + @last_marker = nil @box = nil @start_point = nil @view = view @@ -105,14 +224,14 @@ class DragBoxPlugin < RBA::Plugin @width = 1 end + # This method receives configuration callbacks def configure(name, value) - # receives configuration callbacks if name == CFG_COLOR # configure marker color begin @color = value != "" ? value.to_i(16) : nil rescue - self.color = nil + @color = nil end self._configure_marker elsif name == CFG_WIDTH @@ -126,57 +245,97 @@ class DragBoxPlugin < RBA::Plugin end end + # clears all markers def _clear_marker - # clears all markers - [ @marker, @last_box ].each { |m| m && m._destroy } + [ @marker, @last_marker ].each { |marker| marker && marker._destroy } @marker = nil - @last_box = nil + @last_marker = nil end + # stops dragging the marker and copy to a static one + def _finish + if @last_marker + @last_marker._destroy + end + @last_marker = @marker + @marker = nil + # reset to idle + self.ungrab_mouse + RBA::MainWindow.instance.message("Box finished: " + @box.to_s, 10000) + end + + # updates the marker with the current box def _update_marker - # updates the marker with the current box - if !@marker - @marker = RBA::Marker::new(self.view) + if ! @marker + @marker = RBA::Marker::new(@view) self._configure_marker end @marker.set(@box) end - def freeze_marker - # stop dragging the marker and copy to a static one - if @last_box - @last_box._destroy() - end - @last_box = @marker - @marker = nil - end - + # changes the marker's appearance def _configure_marker - # change the marker's appearance if @marker @marker.line_style = 2 # short-dashed @marker.vertex_size = 0 # no vertexes @marker.line_width = @width - @marker.color = @color ? (@color | 0xff000000) : 0 + @marker.color = @color ? (@color | 0xff000000) : 0 # auto end end + + # Updates the box with the given value and updates the marker. + # This method is bound to the focus page handler when needed. + def _update_box(box) + @box = box + self._update_marker + end + # overloaded callback: the focus page is requested + def focus_page_open(fp) + + # stop unless dragging + if !@marker + return + end + + # configure the focus page and show it: + # the page will call the handler of "update_box" to commit + # changes to the box + fp.box = @box + fp.pfix = @start_point + fp.update_box = lambda { |box| self._update_box(box) } + ret = fp.show + fp.update_box = nil + if ret == 1 + # accepted: stop dragging now, we are done. + self._finish + end + return ret + end + + # overloaded callback: + # plugin is activated - i.e. the mode is selected def activated - # plugin is activated - i.e. the mode is selected RBA::MainWindow.instance.message("Click on point to start dragging a box", 10000) end + # overloaded callback: + # plugin is deactivated - i.e. the mode is unselected def deactivated - # plugin is deactivated - i.e. the mode is unselected self._clear_marker RBA::MainWindow.instance.message("", 0) end + # overloaded callback: + # a mouse button was clicked + def mouse_click_event(p, buttons, prio) + if prio + # first-level event: start a new box or # stop dragging it and freeze the box - if !@marker + if ! @marker p = self.snap2(p) @box = RBA::DBox::new(p, p) @start_point = p @@ -186,49 +345,55 @@ class DragBoxPlugin < RBA::Plugin RBA::MainWindow.instance.message("Drag the box and click again", 10000) else p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons)) - self.freeze_marker - self.ungrab_mouse - RBA::MainWindow.instance.message("Box finished: " + @box.to_s, 10000) + self._update_box(RBA::DBox::new(@start_point, p)) + self._finish end - # consume event + return true - else - return false + end + + return false + end + # overloaded callback: + # the mouse was moved def mouse_moved_event(p, buttons, prio) + if prio # first-level event: if not dragging, provide a # mouse cursor for tracking. If dragging, update # the box and provide a mouse cursor. if !@marker self.clear_mouse_cursors - p = self.snap2(p, true) + p = self.snap2(p, :visualize => true) self.add_mouse_cursor(p) else self.clear_mouse_cursors - p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons), true) + p = self.snap2(p, @start_point, true, self.ac_from_buttons(buttons), :visualize => true) self.add_mouse_cursor(p) @box = RBA::DBox::new(@start_point, p) self._update_marker end end + # NOTE: we must not digest this event (i.e. return true) # to allow the mouse tracker to receive the events as well return false + end end # Implements a "plugin factory". -# Purpose of this object is to create a plugin object +# The purpose of this object is to create a plugin object # and corresponding UI objects. class DragBoxPluginFactory < RBA::PluginFactory def initialize - super() + super self.has_tool_entry = true # NOTE: it's a good practice to register configuration options self.add_option(CFG_WIDTH, "1") @@ -236,23 +401,28 @@ class DragBoxPluginFactory < RBA::PluginFactory self.register(-1000, "drag_box", "Drag Box") end + # Called to create the configuration pages def create_config_pages self.add_config_page(DragBoxConfigPage::new) end + # Called to create the editor options pages def create_editor_options_pages self.add_editor_options_page(DragBoxEditorOptionsPage::new) + self.add_editor_options_page(DragBoxFocusPage::new) end + # Creates the plugin def create_plugin(manager, root, view) return DragBoxPlugin::new(view) end - -end -# Create the singleton instance - as we register it, +end + +# Creates the singleton instance - as we register it, # it is not garbage collected DragBoxPluginFactory::new -end +end + diff --git a/src/lay/lay/macro_templates/drag_box_sample_python.lym b/src/lay/lay/macro_templates/drag_box_sample_python.lym index 17d2b4a7b..3eb35164b 100644 --- a/src/lay/lay/macro_templates/drag_box_sample_python.lym +++ b/src/lay/lay/macro_templates/drag_box_sample_python.lym @@ -8,15 +8,23 @@ python # Sample plugin -# + # This plugin implements a box that can be drawn by # clicking at the first and then at the second point. # There is one box which is replacing the previous one. # Line color and line width of the box can be configured # by editor options (line width) or configuration pages -# (color). - +# (color). These settings are managed through configuration +# options and their current state is persisted. +# +# The dimension of the box can be entered numerically +# while dragging the box. This feature is implemented +# through a modal "focus page", which opens when you +# press the Tab key during editing and when the keyboard +# focus is on the canvas. +# # Register this macro as "autorun" to enable the plugin +# on startup. cfg_color = "drag-box-color" cfg_width = "drag-box-width" @@ -24,11 +32,19 @@ cfg_width = "drag-box-width" # The widget placed into the editor options dock class DragBoxEditorOptionsPage(pya.EditorOptionsPage): + + """ + An option page providing a single entry box for configuring the line width + This page communicates via configuration options. One advantage of this + approach is that the values are persisted + """ def __init__(self): - # Creates a new page with title "Options" and at - # position 1 (second from left) + """ + Creates a new page with title "Options" and at position 1 (second from left) + """ + super(DragBoxEditorOptionsPage, self).__init__("Options", 1) layout2 = pya.QVBoxLayout(self) @@ -43,25 +59,142 @@ class DragBoxEditorOptionsPage(pya.EditorOptionsPage): layout.addStretch(1) layout2.addStretch(1) + # connect the spin box value change with the "edited" slot + # which will result in a call of "apply". self.spin_box.valueChanged = lambda x: self.edited() def setup(self, dispatcher): + + """ + "setup" is called when the page needs to be populated with information - + i.e. on first show. + """ + try: self.spin_box.setValue(int(dispatcher.get_config(cfg_width))) except: self.spin_box.setValue(1) def apply(self, dispatcher): + + """ + "apply" is called when the page is requested to submit the entered + values to the plugin. Usually this should be done via configuration + events. + """ + dispatcher.set_config(cfg_width, str(self.spin_box.value)) +# The modal dialog page that appears when "Tab" is pressed + +class DragBoxFocusPage(pya.EditorOptionsPage): + + """ + A (modal) option page, also called a "focus page". This page is + registered like an editor options page. It is brought to front + when the user hits the "Tab" key during editing. + In this case, this page uses "setup" and "apply" callbacks to + set and fetch information. It also employs a handler named + "update_box" to communicate changes between the client (the + plugin) and the page. + + Attributes that the client needs to take care of are + "self.box" (the current box), "self.pfix" (the start point) + and "self.update_box". + """ + + def __init__(self): + + """ + Creates a new page with title "Options" and at + position 1 (second from left) + """ + + super(DragBoxFocusPage, self).__init__("Geometry", 2) + + self.focus_page = True + self.modal_page = True + + self.box = pya.DBox() + self.pfix = pya.DPoint() + self.update_box = None + + layout = pya.QGridLayout(self) + layout.setColumnStretch(1, 1) + + label = pya.QLabel("Width", self) + layout.addWidget(label, 0, 0, 1, 1) + self.le_width = pya.QLineEdit(self) + layout.addWidget(self.le_width, 0, 1, 1, 1) + + label = pya.QLabel("Height", self) + layout.addWidget(label, 1, 0, 1, 1) + self.le_height = pya.QLineEdit(self) + layout.addWidget(self.le_height, 1, 1, 1, 1) + + layout.setRowStretch(2, 1) + + def setup(self, dispatcher): + + """ + Is called when the page needs to be set up. + We assume that the client has properly set up self.box + """ + + self.le_width.text = "%.12g" % self.box.width() + self.le_height.text = "%.12g" % self.box.height() + + def apply(self, dispatcher): + + """ + Apply is called when the dialog is accepted or the "Apply" button is pressed + Usually this method is intended to submit configuration parameter changes, + but we can use it for any other purpose as well. + """ + + # fetches the coordinates from the entry boxes + # throws an exception in case of an error + x = float(self.le_width.text) + y = float(self.le_height.text) + + # prepares a new box with the given dimensions + # using the initial point ("pfix") and considering + # the drag direction + t = b = self.pfix.y + l = r = self.pfix.x + + if self.box.bottom < t - 1e-10: + b = t - y + else: + t = b + y + + if self.box.left < l - 1e-10: + l = r - x + else: + r = l + x + + # issue the event (call the handler) to inform the plugin of this change + if self.update_box is not None: + self.update_box(pya.DBox(l, b, r, t)) + # The widget placed into the configuration page class DragBoxConfigPage(pya.ConfigPage): + + """ + A configuration page with a single entry box to change + the box color in RGB hex style. + Configuration pages appear in the Setup dialog and can + communicate only through configuration parameter updates. + """ def __init__(self): - # places the widget on a new section ("Drag Box") - # and "Configure" page + """ + Initializes the page. Places it on a new section ("Drag Box") and "Configure" page + and creates a single entry field. + """ + super(DragBoxConfigPage, self).__init__("Drag Box|Configure") layout = pya.QHBoxLayout(self) @@ -72,17 +205,28 @@ class DragBoxConfigPage(pya.ConfigPage): layout.addStretch(1) def setup(self, dispatcher): + """ + This method is called to request an update of the entry fields + """ self.line_edit.setText(dispatcher.get_config(cfg_color)) def apply(self, dispatcher): + """ + This method is called to request a transfer of the edited values + to the configuration space. + """ dispatcher.set_config(cfg_color, self.line_edit.text) class DragBoxPlugin(pya.Plugin): + """ + The custom plugin implementation. + """ + def __init__(self, view): super(DragBoxPlugin, self).__init__() self.marker = None - self.last_box = None + self.last_marker = None self.box = None self.start_point = None self.view = view @@ -90,8 +234,9 @@ class DragBoxPlugin(pya.Plugin): self.width = 1 def configure(self, name, value): - # receives configuration callbacks - needs_update = False + """ + This method receives configuration callbacks + """ if name == cfg_color: # configure marker color try: @@ -111,29 +256,40 @@ class DragBoxPlugin(pya.Plugin): self._configure_marker() def _clear_marker(self): - # clears all markers - for marker in [ self.marker, self.last_box ]: + """ + clears all markers + """ + for marker in [ self.marker, self.last_marker ]: if marker is not None: marker._destroy() self.marker = None - self.last_box = None + self.last_marker = None + def _finish(self): + """ + stops dragging the marker and copy to a static one + """ + if self.last_marker is not None: + self.last_marker._destroy() + self.last_marker = self.marker + self.marker = None + # reset to idle + self.ungrab_mouse() + pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000) + def _update_marker(self): - # updates the marker with the current box + """ + updates the marker with the current box + """ if self.marker is None: self.marker = pya.Marker(self.view) self._configure_marker() self.marker.set(self.box) - def freeze_marker(self): - # stop dragging the marker and copy to a static one - if self.last_box is not None: - self.last_box._destroy() - self.last_box = self.marker - self.marker = None - def _configure_marker(self): - # change the marker's appearance + """ + changes the marker's appearance + """ if self.marker is not None: self.marker.line_style = 2 # short-dashed self.marker.vertex_size = 0 # no vertexes @@ -142,17 +298,64 @@ class DragBoxPlugin(pya.Plugin): self.marker.color = self.color | 0xff000000 else: self.marker.color = 0 # auto + + def _update_box(self, box): + """ + Updates the box with the given value and updates the marker. + This method is bound to the focus page handler when needed. + """ + self.box = box + self._update_marker() + + def focus_page_open(self, fp): + + """ + overloaded callback: the focus page is requested + """ + + # stop unless dragging + if self.marker is None: + return + + # configure the focus page and show it: + # the page will call the handler of "update_box" to commit + # changes to the box + fp.box = self.box + fp.pfix = self.start_point + fp.update_box = self._update_box + ret = fp.show() + fp.update_box = None + if ret == 1: + # accepted: stop dragging now, we are done. + self._finish() + return ret def activated(self): - # plugin is activated - i.e. the mode is selected + + """ + overloaded callback: + plugin is activated - i.e. the mode is selected + """ + pya.MainWindow.instance().message("Click on point to start dragging a box", 10000) def deactivated(self): - # plugin is deactivated - i.e. the mode is unselected + + """ + overloaded callback: + plugin is deactivated - i.e. the mode is unselected + """ + self._clear_marker() pya.MainWindow.instance().message("", 0) def mouse_click_event(self, p, buttons, prio): + + """ + overloaded callback: + a mouse button was clicked + """ + if prio: # first-level event: start a new box or # stop dragging it and freeze the box @@ -166,13 +369,18 @@ class DragBoxPlugin(pya.Plugin): pya.MainWindow.instance().message("Drag the box and click again", 10000) else: p = self.snap2(p, self.start_point, True, self.ac_from_buttons(buttons)) - self.freeze_marker() - self.ungrab_mouse() - pya.MainWindow.instance().message("Box finished: " + str(self.box), 10000) + self._update_box(pya.DBox(self.start_point, p)) + self._finish() return True return False def mouse_moved_event(self, p, buttons, prio): + + """ + overloaded callback: + the mouse was moved + """ + if prio: # first-level event: if not dragging, provide a # mouse cursor for tracking. If dragging, update @@ -191,13 +399,14 @@ class DragBoxPlugin(pya.Plugin): # to allow the mouse tracker to receive the events as well return False - -# Implements a "plugin factory". -# Purpose of this object is to create a plugin object -# and corresponding UI objects. - class DragBoxPluginFactory(pya.PluginFactory): + """ + Implements a "plugin factory". + The purpose of this object is to create a plugin object + and corresponding UI objects. + """ + def __init__(self): super(DragBoxPluginFactory, self).__init__() self.has_tool_entry = True @@ -207,15 +416,25 @@ class DragBoxPluginFactory(pya.PluginFactory): self.register(-1000, "drag_box", "Drag Box") def create_config_pages(self): + """ + Called to create the configuration pages + """ self.add_config_page(DragBoxConfigPage()) def create_editor_options_pages(self): + """ + Called to create the editor options pages + """ self.add_editor_options_page(DragBoxEditorOptionsPage()) + self.add_editor_options_page(DragBoxFocusPage()) def create_plugin(self, manager, root, view): + """ + Creates the plugin + """ return DragBoxPlugin(view) -# Create the singleton instance - as we register it, +# Creates the singleton instance - as we register it, # it is not garbage collected DragBoxPluginFactory() From 7f29cf91dfcba697afbc1af85f0e3c9d38b091b7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 19:50:51 +0200 Subject: [PATCH 28/52] Removing GSI methods which are no longer required as they are included in the base class now, added + fixed GSI unit tests for LayoutView --- .../laybasic/gsiDeclLayLayoutViewBase.cc | 62 ------------------- .../laybasic/layEditorOptionsPages.cc | 6 +- testdata/ruby/layLayoutView.rb | 31 ++++++++-- 3 files changed, 29 insertions(+), 70 deletions(-) diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index 3306852bc..1f5b9b50f 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -1942,68 +1942,6 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase (decl_Dispatcher, "\n" "This method has been added in version 0.26." ) + - // HINT: the cast is important to direct GSI to the LayoutView method rather than the - // Plugin method (in which case we get a segmentation violation ..) - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("clear_config", (void (lay::LayoutViewBase::*)()) &lay::LayoutViewBase::clear_config, - "@brief Clears the local configuration parameters\n" - "\n" - "See \\set_config for a description of the local configuration parameters." - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method_ext ("get_config_names", &get_config_names, - "@brief Gets the configuration parameter names\n" - "\n" - "@return A list of configuration parameter names\n" - "\n" - "This method returns the names of all known configuration parameters. These names can be used to " - "get and set configuration parameter values.\n" - "\n" - "This method was introduced in version 0.25.\n" - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("get_config", (std::string (lay::LayoutViewBase::*)(const std::string &name) const) &lay::LayoutViewBase::config_get, gsi::arg ("name"), - "@brief Gets the value of a local configuration parameter\n" - "\n" - "@param name The name of the configuration parameter whose value shall be obtained (a string)\n" - "\n" - "@return The value of the parameter\n" - "\n" - "See \\set_config for a description of the local configuration parameters." - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("set_config", (void (lay::LayoutViewBase::*)(const std::string &name, const std::string &value)) &lay::LayoutViewBase::config_set, gsi::arg ("name"), gsi::arg ("value"), - "@brief Sets a local configuration parameter with the given name to the given value\n" - "\n" - "@param name The name of the configuration parameter to set\n" - "@param value The value to which to set the configuration parameter\n" - "\n" - "This method sets a local configuration parameter with the given name to the given value. " - "Values can only be strings. Numerical values have to be converted into strings first. " - "Local configuration parameters override global configurations for this specific view. " - "This allows for example to override global settings of background colors. " - "Any local settings are not written to the configuration file. " - ) + - // TODO: this method belongs to the Plugin interface and should be located there. - // Change this once there is a mixin concept available and the Plugin interface can - // be mixed into LayoutView. - gsi::method ("commit_config", (void (lay::LayoutViewBase::*)()) &lay::LayoutViewBase::config_end, - "@brief Commits the configuration settings\n" - "\n" - "Some configuration options are queued for performance reasons and become active only after 'commit_config' has been called. " - "After a sequence of \\set_config calls, this method should be called to activate the " - "settings made by these calls.\n" - "\n" - "This method has been introduced in version 0.25.\n" - ) + gsi::method_ext ("transaction", &gsi::transaction, gsi::arg ("description"), "@brief Begins a transaction\n" "\n" diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 855515d2d..6d9b97f92 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -292,7 +292,7 @@ EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent) QVBoxLayout *ly = new QVBoxLayout (this); ly->setContentsMargins (0, 0, 0, 0); - QVBoxLayout *ly4 = new QVBoxLayout (this); + QVBoxLayout *ly4 = new QVBoxLayout (0); ly4->setContentsMargins (6, 6, 6, 0); ly->addLayout (ly4); mp_pages = new QTabWidget (this); @@ -306,9 +306,9 @@ EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent) ly->addWidget (mp_single_page_frame, 1); mp_single_page_frame->hide (); - QVBoxLayout *ly3 = new QVBoxLayout (this); - ly->addLayout (ly3); + QVBoxLayout *ly3 = new QVBoxLayout (0); ly3->setContentsMargins (6, 6, 6, 6); + ly->addLayout (ly3); mp_button_box = new QDialogButtonBox (this); ly3->addWidget (mp_button_box); mp_button_box->setOrientation (Qt::Horizontal); diff --git a/testdata/ruby/layLayoutView.rb b/testdata/ruby/layLayoutView.rb index 2d55f84e1..e56c64a05 100644 --- a/testdata/ruby/layLayoutView.rb +++ b/testdata/ruby/layLayoutView.rb @@ -506,9 +506,8 @@ class LAYLayoutView_TestClass < TestBase end class DummyPlugin < RBA::Plugin - def initialize(manager, view) - self.manager = manager - self.view = view + def initialize + super end end @@ -516,8 +515,8 @@ class LAYLayoutView_TestClass < TestBase def initialize() register(1000, "dummy_plugin", "Dummy Plugin") end - def create_plugin(manager, unused, view) - DummyPlugin::new(manager, view) + def create_plugin(manager, dispatcher, view) + DummyPlugin::new end end @@ -601,6 +600,28 @@ class LAYLayoutView_TestClass < TestBase end + # private config + def test_10 + + lv = RBA::LayoutView::new(true) + + assert_equal(lv.get_config_names.member?("edit-grid"), true) + lv.set_config("edit-grid", "0.01") + # smoke test + lv.commit_config + assert_equal(lv.get_config("edit-grid"), "0.01") + lv.clear_config + assert_equal(lv.get_config("edit-grid"), "") + + # unknown config names can be used, but are not initialized + lv.set_config("does-not-exist", "aaa") + assert_equal(lv.get_config_names.member?("does-not-exist"), true) + assert_equal(lv.get_config("does-not-exist"), "aaa") + lv.clear_config + assert_equal(lv.get_config_names.member?("does-not-exist"), false) + + end + end load("test_epilogue.rb") From 128f92c42719bcbeff61eb11197bd02f6d80c7d2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 19:53:36 +0200 Subject: [PATCH 29/52] Doc update --- src/lay/lay/gsiDeclLayEditorOptionsPage.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc index c6bc94017..7a63b0df5 100644 --- a/src/lay/lay/gsiDeclLayEditorOptionsPage.cc +++ b/src/lay/lay/gsiDeclLayEditorOptionsPage.cc @@ -62,7 +62,11 @@ Class decl_EditorOptionsPageBase (QT_EXTERNAL_BASE (QWid "@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." + "dock and bring it to the front if it is non-modal.\n" + "\n" + "Before the page is shown, \\setup is called. When the page is dismissed (accepted), \\apply is called. " + "You can overload these methods to transfer data to and from the configuration space or to perform other " + "actions, not related to configuration parameters." ) + method ("apply", &lay::EditorOptionsPage::apply, gsi::arg ("dispatcher"), "@brief Transfers data from the page to the configuration\n" From e6692a2fd09af288feeab530da6f4fdba976dfa0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 19:59:57 +0200 Subject: [PATCH 30/52] Added LayoutView#layer_list_name as read access for LayoutView#rename_layer_list --- .../laybasic/gsiDeclLayLayoutViewBase.cc | 17 +++++++++-------- testdata/ruby/layLayers.rb | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc index 1f5b9b50f..86892f3c1 100644 --- a/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc +++ b/src/laybasic/laybasic/gsiDeclLayLayoutViewBase.cc @@ -144,6 +144,11 @@ static std::string get_line_style (lay::LayoutViewBase *view, unsigned int index return view->line_styles ().style (index).to_string (); } +static std::string layer_list_name (lay::LayoutViewBase *view, unsigned int index) +{ + return view->get_properties (index).name (); +} + static void transaction (lay::LayoutViewBase *view, const std::string &desc) { view->manager ()->transaction (desc); @@ -358,14 +363,6 @@ static QWidget *widget (lay::LayoutViewBase *view) #endif -static std::vector -get_config_names (lay::LayoutViewBase *view) -{ - std::vector names; - view->get_config_names (names); - return names; -} - static void send_key_press_event (lay::LayoutViewBase *view, unsigned int key, unsigned int buttons) { @@ -1574,6 +1571,10 @@ LAYBASIC_PUBLIC Class decl_LayoutViewBase (decl_Dispatcher, "@brief Sets the title of the given layer properties tab\n" "This method has been introduced in version 0.21.\n" ) + + gsi::method_ext ("layer_list_name", &layer_list_name, gsi::arg ("index"), + "@brief Gets the title of the given layer properties tab\n" + "This method has been introduced in version 0.30.4.\n" + ) + gsi::method_ext ("remove_stipple", &remove_stipple, gsi::arg ("index"), "@brief Removes the stipple pattern with the given index\n" "The pattern with an index less than the first custom pattern cannot be removed. " diff --git a/testdata/ruby/layLayers.rb b/testdata/ruby/layLayers.rb index 447f3fd16..168ab141c 100644 --- a/testdata/ruby/layLayers.rb +++ b/testdata/ruby/layLayers.rb @@ -234,6 +234,8 @@ class LAYLayers_TestClass < TestBase cv.insert_layer_list(1) cv.rename_layer_list(1, "x") + assert_equal(cv.layer_list_name(1), "x") + assert_equal(cv.layer_list_name(10000), "") # must not crash assert_equal(cv.current_layer_list, 1) cv.set_current_layer_list(0) assert_equal(cv.current_layer_list, 0) From 3d8eaf8dbf01f96d02f0aafc9c4a89d47e8a01b4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 20:27:43 +0200 Subject: [PATCH 31/52] Changed via key binding to 'O' --- src/edt/edt/edtPlugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/edt/edt/edtPlugin.cc b/src/edt/edt/edtPlugin.cc index cc78f30a3..33fbb341f 100644 --- a/src/edt/edt/edtPlugin.cc +++ b/src/edt/edt/edtPlugin.cc @@ -387,7 +387,7 @@ public: menu_entries.push_back (lay::menu_item ("edt::combine_mode", "combine_mode:edit_mode", "@toolbar.end_modes", tl::to_string (tr ("Combine{Select background combination mode}")))); // Key binding only - menu_entries.push_back (lay::menu_item ("edt::via", "via:edit_mode", "@secrets.end", tl::to_string (tr ("Via")) + "(V)")); + menu_entries.push_back (lay::menu_item ("edt::via", "via:edit_mode", "@secrets.end", tl::to_string (tr ("Via")) + "(O)")); menu_entries.push_back (lay::menu_item ("edt::via_up", "via_up:edit_mode", "@secrets.end", tl::to_string (tr ("Via up")))); menu_entries.push_back (lay::menu_item ("edt::via_down", "via_down:edit_mode", "@secrets.end", tl::to_string (tr ("Via down")))); } From 50cad403b27753d38df6be69d8654174be4b1cd8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 4 Sep 2025 20:46:30 +0200 Subject: [PATCH 32/52] Doc updates (vias, paths) --- src/db/db/gsiDeclDbVia.cc | 2 +- src/doc/doc/manual/create_path.xml | 24 ++++++++++++++++++++---- src/doc/doc/manual/editor_operations.xml | 1 + 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/db/db/gsiDeclDbVia.cc b/src/db/db/gsiDeclDbVia.cc index 3b446bbe6..8e167435c 100644 --- a/src/db/db/gsiDeclDbVia.cc +++ b/src/db/db/gsiDeclDbVia.cc @@ -156,7 +156,7 @@ Class decl_dbViaType ("db", "ViaType", "@brief If non-zero, the top layer's dimensions will be rounded to this grid.\n" ), "@brief Describes a via type\n" - "These objects are used by PCellDeclaration#via_types to specify the via types a " + "These objects are used by \\PCellDeclaration#via_types to specify the via types a " "via PCell is able to provide.\n" "\n" "The basic parameters of a via type are bottom and top layers (the layers that are " diff --git a/src/doc/doc/manual/create_path.xml b/src/doc/doc/manual/create_path.xml index 1cee916cb..e6763c061 100644 --- a/src/doc/doc/manual/create_path.xml +++ b/src/doc/doc/manual/create_path.xml @@ -10,16 +10,16 @@

Select "Path" mode from the toolbar. The editor options dialog will open that additionally prompts for basic path parameters, such as width and extension scheme. When a path is being drawn, it will receive - the settings entered into this dialog. The path properties can even be changed, while the path is - being drawn. Don't forget to click "Apply" to take over the current entries. If the dialog has been - closed unintentionally, it can be reopened with the F3 shortcut. + the settings entered into this dialog. The path properties can be changed, while the path is + being drawn. If the option page has been closed unintentionally, it can be reopened with the F3 shortcut.

To actually draw a path, - choose a layer from the layer panel in which to create a new box. + choose a layer from the layer panel in which to create a new path. Left click at the first vertex, move the mouse to the second vertex, click to place this one and continue to the last vertex. Double-click at the last vertex to finish the path. Press the ESC key to cancel the operation. + Use the backspace key to remove the current segment and go back to the previous segment.

@@ -30,5 +30,21 @@ also during editing.

+

+ + + Paths are often used for wires. KLayout can be configured to provide + vias that allow switching from one layer to another and place a via + between the layers where the paths overlap. Before you can use the via feature, + KLayout has to be equipped with via definitions. Vias are basically PCells which + need to support a specific set of parameters. Via PCells need to expose a number + of descriptors to define the options supported by a particular PCell (e.g. the + layers which are connected or the type of via if multiple flavors are available). + This scheme allows for a lot of flexibility, but needs some coding skills. + In the macro IDE, a code sample is provided which implements two simple vias + for a three-layer metal stack. For more details see , + method "via_types" and . +

+ diff --git a/src/doc/doc/manual/editor_operations.xml b/src/doc/doc/manual/editor_operations.xml index a6d47ad91..6ae56347b 100644 --- a/src/doc/doc/manual/editor_operations.xml +++ b/src/doc/doc/manual/editor_operations.xml @@ -15,6 +15,7 @@ + From 6ada4cb6f948e96c77c2102f663c480b2f6ba5c5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Sep 2025 13:41:14 +0200 Subject: [PATCH 33/52] [consider merging] fixed build without Qt bindings --- src/lay/lay/gsiDeclLayPluginFactory.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lay/lay/gsiDeclLayPluginFactory.cc b/src/lay/lay/gsiDeclLayPluginFactory.cc index c840b7755..85f0541bf 100644 --- a/src/lay/lay/gsiDeclLayPluginFactory.cc +++ b/src/lay/lay/gsiDeclLayPluginFactory.cc @@ -341,8 +341,10 @@ private: bool m_implements_mouse_mode; std::string m_mouse_mode_title; tl::RegisteredClass *mp_registration; +#if defined(HAVE_QTBINDINGS) mutable std::vector m_config_pages; mutable std::vector m_editor_options_pages; +#endif }; Class decl_PluginFactory ("lay", "PluginFactory", From ff6ab4267b444ffc509202cb36e6e5a355b4ae3b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Sep 2025 17:58:44 +0200 Subject: [PATCH 34/52] WIP: exclude area for fill functions --- src/db/db/dbFillTool.cc | 561 ++++++++++++--------- src/db/db/dbFillTool.h | 30 +- src/db/db/gsiDeclDbCell.cc | 26 +- src/db/db/gsiDeclDbRegion.cc | 21 +- src/drc/drc/built-in-macros/_drc_engine.rb | 4 + src/drc/drc/built-in-macros/_drc_layer.rb | 17 +- src/drc/drc/built-in-macros/_drc_tags.rb | 12 + 7 files changed, 404 insertions(+), 267 deletions(-) diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index 04358f6e1..0cd298fe2 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -42,10 +42,18 @@ public: // .. nothing yet .. } - GenericRasterizer (const db::Polygon &fp, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) + GenericRasterizer (const std::vector &fr, const db::Box &rasterized_area, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) : m_row_step (row_step), m_column_step (column_step), m_row_steps (0), m_column_steps (0), m_origin (origin), m_dim (dim) { - rasterize (fp); + rasterize (rasterized_area, fr); + } + + GenericRasterizer (const db::Polygon &fp, const db::Box &rasterized_area, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, const db::Vector &dim) + : m_row_step (row_step), m_column_step (column_step), m_row_steps (0), m_column_steps (0), m_origin (origin), m_dim (dim) + { + std::vector fr; + fr.push_back (fp); + rasterize (rasterized_area, fr); } void move (const db::Vector &d) @@ -59,101 +67,6 @@ public: m_area_maps.clear (); } - void rasterize (const db::Polygon &fp) - { - db::Coord dx = m_row_step.x (); - db::Coord dy = m_column_step.y (); - - if (m_row_step.y () == 0) { - m_row_steps = 1; - } else { - m_row_steps = tl::lcm (dy, std::abs (m_row_step.y ())) / std::abs (m_row_step.y ()); - } - - if (m_column_step.x () == 0) { - m_column_steps = 1; - } else { - m_column_steps = tl::lcm (dx, std::abs (m_column_step.x ())) / std::abs (m_column_step.x ()); - } - - // because the rasterizer can't handle overlapping cells we need to multiply the row and columns steps - // with an integer until the effective rasterizer pitch gets big enough. - m_row_steps *= (m_dim.x () - 1) / (m_row_steps * m_row_step.x ()) + 1; - m_column_steps *= (m_dim.y () - 1) / (m_column_steps * m_column_step.y ()) + 1; - - db::Box fp_bbox = fp.box (); - - // compensate for distortion by sheared kernel - db::Coord ex = std::max (std::abs (db::Coord (m_column_step.x () * m_column_steps)), std::abs (db::Coord (m_row_step.x () * m_row_steps))); - db::Coord ey = std::max (std::abs (db::Coord (m_column_step.y () * m_column_steps)), std::abs (db::Coord (m_row_step.y () * m_row_steps))); - fp_bbox.enlarge (db::Vector (ex, ey)); - - int columns_per_rows = (int (m_row_steps) * m_row_step.y ()) / dy; - int rows_per_columns = (int (m_column_steps) * m_column_step.x ()) / dx; - - db::Coord ddx = dx * db::Coord (m_row_steps) - m_column_step.x () * columns_per_rows; - db::Coord ddy = dy * db::Coord (m_column_steps) - m_row_step.y () * rows_per_columns; - - // round polygon bbox - db::Coord fp_left = db::Coord (tl::round_down (fp_bbox.left () - m_origin.x (), ddx)) + m_origin.x (); - db::Coord fp_bottom = db::Coord (tl::round_down (fp_bbox.bottom () - m_origin.y (), ddy)) + m_origin.y (); - db::Coord fp_right = db::Coord (tl::round_up (fp_bbox.right () - m_origin.x (), ddx)) + m_origin.x (); - db::Coord fp_top = db::Coord (tl::round_up (fp_bbox.top () - m_origin.y (), ddy)) + m_origin.y (); - fp_bbox = db::Box (fp_left, fp_bottom, fp_right, fp_top); - - size_t nx = fp_bbox.width () / ddx; - size_t ny = fp_bbox.height () / ddy; - - tl_assert (fp.box ().inside (fp_bbox)); - - if (nx == 0 || ny == 0) { - // nothing to rasterize: - return; - } - - m_area_maps.reserve (m_row_steps * m_column_steps + std::abs (columns_per_rows) * std::abs (rows_per_columns)); - - db::AreaMap am; - - for (unsigned int ic = 0; ic < m_column_steps; ++ic) { - - for (unsigned int ir = 0; ir < m_row_steps; ++ir) { - - db::Vector dr = m_row_step * long (ir); - db::Vector dc = m_column_step * long (ic); - - am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); - - if (db::rasterize (fp, am)) { - m_area_maps.push_back (db::AreaMap ()); - m_area_maps.back ().swap (am); - } - - } - - } - - // adds the "dead corner" piece - - for (unsigned int ic = 0; ic < (unsigned int) std::abs (columns_per_rows); ++ic) { - - for (unsigned int ir = 0; ir < (unsigned int) std::abs (rows_per_columns); ++ir) { - - db::Vector dr = m_row_step * long ((rows_per_columns > 0 ? -int (ir + 1) : ir) + m_row_steps); - db::Vector dc = m_column_step * long ((columns_per_rows > 0 ? -int (ic + 1) : ic) + m_column_steps); - - am.reinitialize (db::Point (fp_left, fp_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); - - if (db::rasterize (fp, am)) { - m_area_maps.push_back (db::AreaMap ()); - m_area_maps.back ().swap (am); - } - - } - - } - } - const db::Point &p0 () const { return m_origin; } unsigned int row_steps () const { return m_row_steps; } @@ -180,12 +93,212 @@ private: unsigned int m_row_steps, m_column_steps; db::Point m_origin; db::Vector m_dim; + + void rasterize (const db::Box &rasterized_area, const std::vector &fr) + { + db::Coord dx = m_row_step.x (); + db::Coord dy = m_column_step.y (); + + if (m_row_step.y () == 0) { + m_row_steps = 1; + } else { + m_row_steps = tl::lcm (dy, std::abs (m_row_step.y ())) / std::abs (m_row_step.y ()); + } + + if (m_column_step.x () == 0) { + m_column_steps = 1; + } else { + m_column_steps = tl::lcm (dx, std::abs (m_column_step.x ())) / std::abs (m_column_step.x ()); + } + + // because the rasterizer can't handle overlapping cells we need to multiply the row and columns steps + // with an integer until the effective rasterizer pitch gets big enough. + m_row_steps *= (m_dim.x () - 1) / (m_row_steps * m_row_step.x ()) + 1; + m_column_steps *= (m_dim.y () - 1) / (m_column_steps * m_column_step.y ()) + 1; + + db::Box ra_org = rasterized_area; + + // compensate for distortion by sheared kernel + db::Coord ex = std::max (std::abs (db::Coord (m_column_step.x () * m_column_steps)), std::abs (db::Coord (m_row_step.x () * m_row_steps))); + db::Coord ey = std::max (std::abs (db::Coord (m_column_step.y () * m_column_steps)), std::abs (db::Coord (m_row_step.y () * m_row_steps))); + ra_org.enlarge (db::Vector (ex, ey)); + + int columns_per_rows = (int (m_row_steps) * m_row_step.y ()) / dy; + int rows_per_columns = (int (m_column_steps) * m_column_step.x ()) / dx; + + db::Coord ddx = dx * db::Coord (m_row_steps) - m_column_step.x () * columns_per_rows; + db::Coord ddy = dy * db::Coord (m_column_steps) - m_row_step.y () * rows_per_columns; + + // round polygon bbox + db::Coord ra_left = db::Coord (tl::round_down (ra_org.left () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord ra_bottom = db::Coord (tl::round_down (ra_org.bottom () - m_origin.y (), ddy)) + m_origin.y (); + db::Coord ra_right = db::Coord (tl::round_up (ra_org.right () - m_origin.x (), ddx)) + m_origin.x (); + db::Coord ra_top = db::Coord (tl::round_up (ra_org.top () - m_origin.y (), ddy)) + m_origin.y (); + db::Box ra = db::Box (ra_left, ra_bottom, ra_right, ra_top); + + size_t nx = ra.width () / ddx; + size_t ny = ra.height () / ddy; + + tl_assert (ra_org.inside (ra)); + + if (nx == 0 || ny == 0) { + // nothing to rasterize: + return; + } + + m_area_maps.reserve (m_row_steps * m_column_steps + std::abs (columns_per_rows) * std::abs (rows_per_columns)); + + db::AreaMap am; + + for (unsigned int ic = 0; ic < m_column_steps; ++ic) { + + for (unsigned int ir = 0; ir < m_row_steps; ++ir) { + + db::Vector dr = m_row_step * long (ir); + db::Vector dc = m_column_step * long (ic); + + am.reinitialize (db::Point (ra_left, ra_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); + + bool any = false; + for (auto i = fr.begin (); i != fr.end (); ++i) { + if (db::rasterize (*i, am)) { + any = true; + } + } + if (any) { + m_area_maps.push_back (db::AreaMap ()); + m_area_maps.back ().swap (am); + } + + } + + } + + // adds the "dead corner" piece + + for (unsigned int ic = 0; ic < (unsigned int) std::abs (columns_per_rows); ++ic) { + + for (unsigned int ir = 0; ir < (unsigned int) std::abs (rows_per_columns); ++ir) { + + db::Vector dr = m_row_step * long ((rows_per_columns > 0 ? -int (ir + 1) : ir) + m_row_steps); + db::Vector dc = m_column_step * long ((columns_per_rows > 0 ? -int (ic + 1) : ic) + m_column_steps); + + am.reinitialize (db::Point (ra_left, ra_bottom) + dr + dc, db::Vector (ddx, ddy), m_dim, nx, ny); + + bool any = false; + for (auto i = fr.begin (); i != fr.end (); ++i) { + if (db::rasterize (*i, am)) { + any = true; + } + } + if (any) { + m_area_maps.push_back (db::AreaMap ()); + m_area_maps.back ().swap (am); + } + + } + + } + } }; +static size_t +create_instances (GenericRasterizer &am, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Vector &kernel_origin, const db::Vector &fill_margin, const GenericRasterizer *exclude_rasterized, std::vector *filled_regions) +{ + size_t ninsts = 0; + + for (unsigned int i = 0; i < am.area_maps (); ++i) { + + db::AreaMap &am1 = am.area_map (i); + const db::AreaMap *am1_excl = 0; + if (exclude_rasterized) { + tl_assert (i < exclude_rasterized->area_maps ()); + am1_excl = &exclude_rasterized->area_map (i); + } + + size_t nx = am1.nx (); + size_t ny = am1.ny (); + + // Create the fill cell instances + for (size_t i = 0; i < nx; ++i) { + + for (size_t j = 0; j < ny; ) { + + size_t jj = j + 1; + if (am1.get (i, j) == am1.pixel_area () && (!am1_excl || am1_excl->get (i, j) == 0)) { + + while (jj != ny && am1.get (i, jj) == am1.pixel_area () && (!am1_excl || am1_excl->get (i, jj) == 0)) { + ++jj; + } + + db::Vector p0 = (am1.p0 () - db::Point ()) - kernel_origin; + p0 += db::Vector (i * am1.d ().x (), j * am1.d ().y ()); + + db::CellInstArray array; + + // try to expand the array in x direction + size_t ii = i + 1; + for ( ; ii < nx; ++ii) { + bool all = true; + for (size_t k = j; k < jj && all; ++k) { + all = (am1.get (ii, k) == am1.pixel_area () && (!am1_excl || am1_excl->get (ii, k) == 0)); + } + if (all) { + for (size_t k = j; k < jj; ++k) { + // disable pixel, so we do not see it again in the following columns + am1.get (ii, k) = 0; + } + } else { + break; + } + } + + ninsts += (jj - j) * (ii - i); + + if (jj > j + 1 || ii > i + 1) { + array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (am1.d ().x (), 0), (unsigned long) (jj - j), (unsigned long) (ii - i)); + } else { + array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0)); + } + + { + // In case we run this from a tiling processor we need to lock against multithread races + tl_assert (cell->layout () != 0); + tl::MutexLocker locker (&cell->layout ()->lock ()); + cell->insert (array); + } + + if (filled_regions) { + if (am1.d ().y () == am1.p ().y () && am1.d ().x () == am1.p ().x ()) { + db::Box fill_box (db::Point (), db::Point (am1.p ().x () * db::Coord (ii - i), am1.p ().y () * db::Coord (jj - j))); + filled_regions->push_back (db::Polygon (fill_box.enlarged (fill_margin).moved (kernel_origin + p0))); + } else { + db::Box fill_box (db::Point (), db::Point () + am1.p ()); + fill_box.enlarge (fill_margin); + for (size_t k = 0; k < jj - j; ++k) { + for (size_t l = 0; l < ii - i; ++l) { + filled_regions->push_back (db::Polygon (fill_box.moved (kernel_origin + p0 + db::Vector (am1.d ().x () * db::Coord (l), am1.d ().y () * db::Coord (k))))); + } + } + } + } + + } + + j = jj; + + } + + } + + } + + return ninsts; +} static bool fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { if (row_step.x () <= 0 || column_step.y () <= 0) { throw tl::Exception (tl::to_string (tr ("Invalid row or column step vectors in fill_region: row step must have a positive x component while column step must have a positive y component"))); @@ -197,153 +310,133 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f db::Vector kernel_origin (fc_bbox.left (), fc_bbox.bottom ()); - std::vector filled_regions; - db::EdgeProcessor ep; - - // under- and oversize the polygon to remove slivers that cannot be filled. db::Coord dx = fc_bbox.width () / 2 - 1, dy = fc_bbox.height () / 2 - 1; - std::vector fpa; - std::vector fpb; - fpa.push_back (fp0); + db::Region fr (fp0); + db::Box rasterized_area = fp0.box (); - ep.size (fpa, -dx, 0, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + std::unique_ptr exclude_rasterized; - ep.size (fpa, dx, 0, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + if (! exclude_area.empty ()) { - ep.size (fpa, 0, -dy, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + auto iter = exclude_area.iter (); + iter.confine_region (fp0.box ()); - ep.size (fpa, 0, dy, fpb, 3 /*mode*/, false /*=don't resolve holes*/); - fpa.swap (fpb); - fpb.clear (); + // over- and undersize the polygons to fill gaps that cannot be filled. + db::Region excluded (iter); + excluded.size (dx, 0); + excluded.size (-dx, 0); + excluded.size (dy, 0); + excluded.size (-dy, 0); + excluded.merge (); - ep.simple_merge (fpa, fpb, false /*=don't resolve holes*/); + if (enhanced_fill || remaining_parts != 0) { - filled_regions.clear (); + tl::warn << "@@@ using booleans for exclude"; + + // In enhanced fill or if the remaining parts are requested, it is better to implement the + // exclude area by a boolean NOT + fr -= excluded; + + } else { + + tl::warn << "@@@ using exclude area rasterizer"; + + // Otherwise use a second rasterizer for the exclude polygons that must have a zero pixel coverage for the + // pixel to be filled. + + std::vector excluded_poly; + excluded_poly.reserve (excluded.count ()); + for (auto i = excluded.begin (); ! i.at_end (); ++i) { + excluded_poly.push_back (*i); + } + excluded.clear (); + + exclude_rasterized.reset (new GenericRasterizer (excluded_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ())); + + } + + } + + std::vector filled_poly; + + // under- and oversize the polygon to remove slivers that cannot be filled. + fr.size (-dx, 0); + fr.size (dx, 0); + fr.size (0, -dy); + fr.size (0, dy); + fr.merge (); + + filled_poly.reserve (fr.count ()); + for (auto i = fr.begin (); ! i.at_end (); ++i) { + filled_poly.push_back (*i); + } + + fr.clear (); + + if (filled_poly.empty ()) { + return false; + } + + std::vector filled_regions; bool any_fill = false; - for (std::vector ::const_iterator fp = fpb.begin (); fp != fpb.end (); ++fp) { + if (exclude_rasterized.get ()) { - if (fp->hull ().size () == 0) { - continue; - } - - // disable enhanced mode an obey the origin if the polygon is not entirely inside and not at the boundary of the glue box - bool ef = enhanced_fill; - if (ef && ! glue_box.empty () && ! fp->box ().enlarged (db::Vector (1, 1)).inside (glue_box)) { - ef = false; - } - - // pick a heuristic "good" starting point in enhanced mode - // TODO: this is a pretty weak optimization. - db::Point o = origin; - if (ef) { - o = fp->hull () [0]; - } - - size_t ninsts = 0; - - GenericRasterizer am (*fp, row_step, column_step, o, fc_bbox.p2 () - fc_bbox.p1 ()); - - for (unsigned int i = 0; i < am.area_maps (); ++i) { - - db::AreaMap &am1 = am.area_map (i); - - size_t nx = am1.nx (); - size_t ny = am1.ny (); - - // Create the fill cell instances - for (size_t i = 0; i < nx; ++i) { - - for (size_t j = 0; j < ny; ) { - - size_t jj = j + 1; - if (am1.get (i, j) == am1.pixel_area ()) { - - while (jj != ny && am1.get (i, jj) == am1.pixel_area ()) { - ++jj; - } - - db::Vector p0 = (am1.p0 () - db::Point ()) - kernel_origin; - p0 += db::Vector (i * am1.d ().x (), j * am1.d ().y ()); - - db::CellInstArray array; - - // try to expand the array in x direction - size_t ii = i + 1; - for ( ; ii < nx; ++ii) { - bool all = true; - for (size_t k = j; k < jj && all; ++k) { - all = am1.get (ii, k) == am1.pixel_area (); - } - if (all) { - for (size_t k = j; k < jj; ++k) { - // disable pixel, so we do not see it again in the following columns - am1.get (ii, k) = 0; - } - } else { - break; - } - } - - ninsts += (jj - j) * (ii - i); - - if (jj > j + 1 || ii > i + 1) { - array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0), db::Vector (0, am1.d ().y ()), db::Vector (am1.d ().x (), 0), (unsigned long) (jj - j), (unsigned long) (ii - i)); - } else { - array = db::CellInstArray (db::CellInst (fill_cell_index), db::Trans (p0)); - } - - { - // In case we run this from a tiling processor we need to lock against multithread races - tl_assert (cell->layout () != 0); - tl::MutexLocker locker (&cell->layout ()->lock ()); - cell->insert (array); - } - - if (remaining_parts) { - if (am1.d ().y () == am1.p ().y () && am1.d ().x () == am1.p ().x ()) { - db::Box fill_box (db::Point (), db::Point (am1.p ().x () * db::Coord (ii - i), am1.p ().y () * db::Coord (jj - j))); - filled_regions.push_back (db::Polygon (fill_box.enlarged (fill_margin).moved (kernel_origin + p0))); - } else { - db::Box fill_box (db::Point (), db::Point () + am1.p ()); - fill_box.enlarge (fill_margin); - for (size_t k = 0; k < jj - j; ++k) { - for (size_t l = 0; l < ii - i; ++l) { - filled_regions.push_back (db::Polygon (fill_box.moved (kernel_origin + p0 + db::Vector (am1.d ().x () * db::Coord (l), am1.d ().y () * db::Coord (k))))); - } - } - } - } - - any_fill = true; - - } - - j = jj; - - } - - } + tl_assert (remaining_parts == 0); + GenericRasterizer am (filled_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ()); + size_t ninsts = create_instances (am, cell, fill_cell_index, kernel_origin, fill_margin, exclude_rasterized.get (), 0); + if (ninsts > 0) { + any_fill = true; } if (tl::verbosity () >= 30 && ninsts > 0) { - tl::info << "Part " << fp->to_string (); + tl::info << "Part " << fp0.to_string (); tl::info << "Created " << ninsts << " instances"; } + } else { + + for (auto fp = filled_poly.begin (); fp != filled_poly.end (); ++fp) { + + if (fp->is_empty ()) { + continue; + } + + // disable enhanced mode an obey the origin if the polygon is not entirely inside and not at the boundary of the glue box + bool ef = enhanced_fill; + if (ef && ! glue_box.empty () && ! fp->box ().enlarged (db::Vector (1, 1)).inside (glue_box)) { + ef = false; + } + + // pick a heuristic "good" starting point in enhanced mode + // TODO: this is a pretty weak optimization. + db::Point o = origin; + if (ef) { + o = fp->hull () [0]; + } + + GenericRasterizer am (*fp, rasterized_area, row_step, column_step, o, fc_bbox.p2 () - fc_bbox.p1 ()); + + size_t ninsts = create_instances (am, cell, fill_cell_index, kernel_origin, fill_margin, 0, remaining_parts ? &filled_regions : 0); + if (ninsts > 0) { + any_fill = true; + } + + if (tl::verbosity () >= 30 && ninsts > 0) { + tl::info << "Part " << fp->to_string (); + tl::info << "Created " << ninsts << " instances"; + } + + } + } if (any_fill) { if (remaining_parts) { + db::EdgeProcessor ep; std::vector fp1; fp1.push_back (fp0); ep.boolean (fp1, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); @@ -358,25 +451,25 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f DB_PUBLIC bool fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { - return fill_polygon_impl (cell, fp0, fill_cell_index, fc_box, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, glue_box); + return fill_polygon_impl (cell, fp0, fill_cell_index, fc_box, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, glue_box, exclude_area); } DB_PUBLIC bool fill_region (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box) + std::vector *remaining_parts, const db::Vector &fill_margin, const db::Box &glue_box, const db::Region &exclude_area) { if (fc_bbox.empty () || fc_bbox.width () == 0 || fc_bbox.height () == 0) { throw tl::Exception (tl::to_string (tr ("Invalid fill cell footprint (empty or zero width/height)"))); } - return fill_polygon_impl (cell, fp0, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), origin, enhanced_fill, remaining_parts, fill_margin, glue_box); + return fill_polygon_impl (cell, fp0, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), origin, enhanced_fill, remaining_parts, fill_margin, glue_box, exclude_area); } static void fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, int iteration, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, int iteration, const db::Box &glue_box, const db::Region &exclude_area) { if (row_step.x () <= 0 || column_step.y () <= 0) { throw tl::Exception (tl::to_string (tr ("Invalid row or column step vectors in fill_region: row step must have a positive x component while column step must have a positive y component"))); @@ -403,7 +496,7 @@ fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill tl::RelativeProgress progress (progress_title, n); for (db::Region::const_iterator p = fr.begin_merged (); !p.at_end (); ++p) { - if (! fill_polygon_impl (cell, *p, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts ? &rem_pp : 0, fill_margin, glue_box)) { + if (! fill_polygon_impl (cell, *p, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts ? &rem_pp : 0, fill_margin, glue_box, exclude_area)) { if (remaining_polygons) { rem_poly.push_back (*p); } @@ -433,27 +526,27 @@ fill_region_impl (db::Cell *cell, const db::Region &fr, db::cell_index_type fill DB_PUBLIC void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - fill_region_impl (cell, fr, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box); + fill_region_impl (cell, fr, fill_cell_index, fc_bbox, row_step, column_step, origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box, exclude_area); } DB_PUBLIC void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_bbox, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { if (fc_bbox.empty () || fc_bbox.width () == 0 || fc_bbox.height () == 0) { throw tl::Exception (tl::to_string (tr ("Invalid fill cell footprint (empty or zero width/height)"))); } fill_region_impl (cell, fr, fill_cell_index, fc_bbox, db::Vector (fc_bbox.width (), 0), db::Vector (0, fc_bbox.height ()), - origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box); + origin, enhanced_fill, remaining_parts, fill_margin, remaining_polygons, 0, glue_box, exclude_area); } DB_PUBLIC void fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { const db::Region *fill_region = &fr; @@ -467,7 +560,7 @@ fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fi ++iteration; remaining.clear (); - fill_region_impl (cell, *fill_region, fill_cell_index, fc_box, row_step, column_step, db::Point (), true, &remaining, fill_margin, remaining_polygons, iteration, glue_box); + fill_region_impl (cell, *fill_region, fill_cell_index, fc_box, row_step, column_step, db::Point (), true, &remaining, fill_margin, remaining_polygons, iteration, glue_box, exclude_area); new_fill_region.swap (remaining); fill_region = &new_fill_region; diff --git a/src/db/db/dbFillTool.h b/src/db/db/dbFillTool.h index 67d23c8cb..7dab850c3 100644 --- a/src/db/db/dbFillTool.h +++ b/src/db/db/dbFillTool.h @@ -23,12 +23,12 @@ #include "dbTypes.h" #include "dbPolygon.h" +#include "dbRegion.h" namespace db { class Cell; -class Region; /** * @brief Creates a tiling pattern for a single polygon using a fill cell which is repeated periodically @@ -41,6 +41,7 @@ class Region; * @param column_step (some_versions) The column advance vector of the fill cell. By default this is (0, fc_bbox.height()) * @param origin Specifies the origin of the fill raster if enhanced_fill is false * @param enhanced_fill If set, the tiling offset will be optimized such that as much tiling cells fit into each polygon + * @param exclude_area The region which fill cells must not overlap * * Optional parameters: * @@ -79,12 +80,16 @@ class Region; */ DB_PUBLIC bool -fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Point &origin, bool enhanced_fill, + std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), + const db::Box &glue_box = db::Box ()); DB_PUBLIC bool -fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, + std::vector *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); /** @@ -98,12 +103,16 @@ fill_region (db::Cell *cell, const db::Polygon &fp, db::cell_index_type fill_cel */ DB_PUBLIC void -fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Point &origin, bool enhanced_fill, + db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); DB_PUBLIC void -fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, - db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); +fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell_index, const db::Box &fc_box, + const db::Vector &row_step, const db::Vector &column_step, const db::Point &origin, bool enhanced_fill, + db::Region *remaining_parts = 0, const db::Vector &fill_margin = db::Vector (), db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); /** * @brief An iterative version for enhanced fill @@ -118,6 +127,7 @@ fill_region (db::Cell *cell, const db::Region &fp, db::cell_index_type fill_cell DB_PUBLIC void fill_region_repeat (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons = 0, const db::Box &glue_box = db::Box ()); + const db::Vector &fill_margin, db::Region *remaining_polygons = 0, + const db::Box &glue_box = db::Box (), const db::Region &exclude_area = db::Region ()); } diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 6bcb72581..44731b59f 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -1510,23 +1510,23 @@ static void move_tree_shapes3 (db::Cell *cell, db::Cell &source_cell, const db:: static void fill_region (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_skew (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_multi (db::Cell *cell, const db::Region &fr, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region_repeat (cell, fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box); + db::fill_region_repeat (cell, fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box, exclude_area); } static db::Instance cell_inst_dtransform_simple (db::Cell *cell, const db::Instance &inst, const db::DTrans &t) @@ -2215,6 +2215,7 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type (extended version)\n" "@param region The region to fill\n" "@param fill_cell_index The fill cell to place\n" @@ -2224,6 +2225,7 @@ Class decl_Cell ("db", "Cell", "@param fill_margin See explanation below\n" "@param remaining_polygons See explanation below\n" "@param glue_box Guarantees fill cell compatibility to neighbor regions in enhanced mode\n" + "@param exclude_area A region that defines the areas which are not be filled\n" "\n" "This method creates a regular pattern of fill cells to cover the interior of the given region as far as possible. " "This process is also known as tiling. This implementation supports rectangular (not necessarily square) tile cells. " @@ -2236,6 +2238,7 @@ Class decl_Cell ("db", "Cell", "\n" "The implementation will basically try to find a repetition pattern of the tile cell's footprint " "and produce instances which fit entirely into the fill region.\n" + "If an exclude area is given, the fill cells also must not overlap that region.\n" "\n" "There is also a version available which offers skew step vectors as a generalization of the orthogonal ones.\n" "\n" @@ -2275,7 +2278,7 @@ Class decl_Cell ("db", "Cell", "at the raster implied by origin at the glue box border and beyond. To ensure fill cell compatibility inside the tiling processor, it is sufficient to use the tile " "box as the glue box.\n" "\n" - "This method has been introduced in version 0.23 and enhanced in version 0.27.\n" + "This method has been introduced in version 0.23 and enhanced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_region", &fill_region_skew, gsi::arg ("region"), gsi::arg ("fill_cell_index"), @@ -2287,16 +2290,18 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type (skew step version)\n" "@param region The region to fill\n" "@param fill_cell_index The fill cell to place\n" - "@param fc_bbox The fill cell's box to place\n" + "@param fc_bbox The fill cell's box, defining the box that needs to be inside the fill region\n" "@param row_step The 'rows' step vector\n" "@param column_step The 'columns' step vector\n" "@param origin The global origin of the fill pattern or nil to allow local (per-polygon) optimization\n" "@param remaining_parts See explanation in other version\n" "@param fill_margin See explanation in other version\n" "@param remaining_polygons See explanation in other version\n" + "@param exclude_area A region that defines the areas which are not be filled\n" "\n" "This version is similar to the version providing an orthogonal fill, but it offers more generic stepping of the fill cell.\n" "The step pattern is defined by an origin and two vectors (row_step and column_step) which span the axes of the fill cell pattern.\n" @@ -2305,7 +2310,7 @@ Class decl_Cell ("db", "Cell", "be overlapping and there can be space between the fill box instances. Fill boxes are placed where they fit entirely into a polygon of the region. " "The fill boxes lower left corner is the reference for the fill pattern and aligns with the origin if given.\n" "\n" - "This variant has been introduced in version 0.27.\n" + "This variant has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_region_multi", &fill_region_multi, gsi::arg ("region"), gsi::arg ("fill_cell_index"), @@ -2315,6 +2320,7 @@ Class decl_Cell ("db", "Cell", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief Fills the given region with cells of the given type in enhanced mode with iterations\n" "This version operates like \\fill_region, but repeats the fill generation until no further fill cells can be placed. " "As the fill pattern origin changes between the iterations, narrow regions can be filled which cannot with a fixed fill pattern origin. " @@ -2323,7 +2329,7 @@ Class decl_Cell ("db", "Cell", "\n" "The origin is ignored unless a glue box is given. See \\fill_region for a description of this concept.\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("begin_shapes_rec", &begin_shapes_rec, gsi::arg ("layer"), "@brief Delivers a recursive shape iterator for the shapes below the cell on the given layer\n" diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 3e72eef94..870927267 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1309,23 +1309,23 @@ tl::Variant complex_op (db::Region *region, db::CompoundRegionOperationNode *nod static void fill_region (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, *fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, *fr, fill_cell_index, fc_box, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_skew (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, const db::Point *origin, - db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + db::Region *remaining_parts, const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region (cell, *fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box); + db::fill_region (cell, *fr, fill_cell_index, fc_box, row_step, column_step, origin ? *origin : db::Point (), origin == 0, remaining_parts, fill_margin, remaining_polygons, glue_box, exclude_area); } static void fill_region_multi (const db::Region *fr, db::Cell *cell, db::cell_index_type fill_cell_index, const db::Box &fc_box, const db::Vector &row_step, const db::Vector &column_step, - const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box) + const db::Vector &fill_margin, db::Region *remaining_polygons, const db::Box &glue_box, const db::Region &exclude_area) { - db::fill_region_repeat (cell, *fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box); + db::fill_region_repeat (cell, *fr, fill_cell_index, fc_box, row_step, column_step, fill_margin, remaining_polygons, glue_box, exclude_area); } static db::Region @@ -4340,6 +4340,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" @@ -4348,14 +4349,15 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", ) + gsi::method_ext ("fill", &fill_region_skew, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), - gsi::arg ("fc_origin"), + gsi::arg ("fc_bbox"), gsi::arg ("row_step"), gsi::arg ("column_step"), gsi::arg ("origin", &default_origin, "(0, 0)"), gsi::arg ("remaining_parts", (db::Region *)0, "nil"), gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), - gsi::arg ("glue_box", db::Box ()), + gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" @@ -4364,12 +4366,13 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", ) + gsi::method_ext ("fill_multi", &fill_region_multi, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), - gsi::arg ("fc_origin"), + gsi::arg ("fc_bbox"), gsi::arg ("row_step"), gsi::arg ("column_step"), gsi::arg ("fill_margin", db::Vector ()), gsi::arg ("remaining_polygons", (db::Region *)0, "nil"), gsi::arg ("glue_box", db::Box ()), + gsi::arg ("exclude_area", db::Region (), "empty"), "@brief A mapping of \\Cell#fill_region to the Region class\n" "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 40f770d77..627a64cc2 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -644,6 +644,10 @@ module DRC DRCFillStep::new(false, x, y) end + def fill_exclude(excl) + DRCFillExclude::new(excl) + end + def auto_origin DRCFillOrigin::new end diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index 7eee5f905..b672bd877 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -5755,6 +5755,8 @@ CODE # @li @b multi_origin @/b: lets the algorithm choose the origin and repeats the fill with different origins # until no further fill cell can be fitted. @/li # @li @b fill_pattern(..) @/b: specifies the fill pattern. @/li + # @li @b fill_exclude(excl) @/b: specifies a fill exclude region ('excl' is a polygon layer). This is conceptually + # equivalent to subtracting that layer from the fill region, but usually more efficient. @/li # @/ul # # "fill_pattern" generates a fill pattern object. This object is used for configuring the fill pattern @@ -5895,6 +5897,7 @@ CODE pattern = nil origin = RBA::DPoint::new repeat = false + excl = RBA::Region::new args.each_with_index do |a,ai| if a.is_a?(DRCSource) @@ -5907,6 +5910,11 @@ CODE raise("Duplicate fill pattern specification for '#{m}' at argument ##{ai+1}") end pattern = a + elsif a.is_a?(DRCFillExclude) + if excl + raise("Duplicate exclude region specification for '#{m}' at argument ##{ai+1}") + end + excl = a.excl elsif a.is_a?(DRCFillStep) if a.for_row if row_step @@ -5988,6 +5996,7 @@ CODE tp.var("fc_index", fc_index) tp.var("repeat", repeat) tp.var("with_left", with_left) + tp.var("excl", excl) tp.queue(<<"END") var tc_box = _frame.bbox; @@ -5997,8 +6006,8 @@ CODE tile_box = tile_box & tc_box; var left = with_left ? Region.new : nil; repeat ? - (region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, left, _tile.bbox) : - (region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, fill_margin, left, _tile.bbox); + (region & tile_box).fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, left, _tile.bbox, excl) : + (region & tile_box).fill(top_cell, fc_index, fc_box, rs, cs, origin, left, fill_margin, left, _tile.bbox, excl); with_left && _output(#{result_arg}, left) ) END @@ -6020,9 +6029,9 @@ END @engine.run_timed("\"#{m}\" in: #{@engine.src_line}", self.data) do if repeat - self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, result) + self.data.fill_multi(top_cell, fc_index, fc_box, rs, cs, fill_margin, result, RBA::Box::new, excl) else - self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, fill_margin, result) + self.data.fill(top_cell, fc_index, fc_box, rs, cs, origin, result, fill_margin, result, RBA::Box::new, excl) end end diff --git a/src/drc/drc/built-in-macros/_drc_tags.rb b/src/drc/drc/built-in-macros/_drc_tags.rb index 923ff803e..0999a166a 100644 --- a/src/drc/drc/built-in-macros/_drc_tags.rb +++ b/src/drc/drc/built-in-macros/_drc_tags.rb @@ -475,6 +475,18 @@ module DRC end + # A wrapper for the fill step definition + class DRCFillExclude + def initialize(excl) + excl.is_a?(DRCLayer) || raise("Exclude layer argument needs to be a DRC layer") + excl.requires_region("Exclude layer") + @excl = excl.data + end + def excl + @excl + end + end + # A wrapper for the fill step definition class DRCFillStep def initialize(for_row, x, y = nil) From feda8680cad12bf001a73e1b08d7f23186e822e4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Sep 2025 19:15:12 +0200 Subject: [PATCH 35/52] WIP: exclude area implemented. Needs optimization. --- src/db/db/dbFillTool.cc | 14 ++++---------- src/db/db/gsiDeclDbRegion.cc | 6 +++--- src/drc/drc/built-in-macros/_drc_layer.rb | 6 +++++- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index 0cd298fe2..cf08a437a 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -319,11 +319,11 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f if (! exclude_area.empty ()) { - auto iter = exclude_area.iter (); - iter.confine_region (fp0.box ()); + auto it = exclude_area.begin_iter (); + it.first.confine_region (fp0.box ()); // over- and undersize the polygons to fill gaps that cannot be filled. - db::Region excluded (iter); + db::Region excluded (it.first, it.second); excluded.size (dx, 0); excluded.size (-dx, 0); excluded.size (dy, 0); @@ -332,16 +332,12 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f if (enhanced_fill || remaining_parts != 0) { - tl::warn << "@@@ using booleans for exclude"; - // In enhanced fill or if the remaining parts are requested, it is better to implement the // exclude area by a boolean NOT fr -= excluded; } else { - tl::warn << "@@@ using exclude area rasterizer"; - // Otherwise use a second rasterizer for the exclude polygons that must have a zero pixel coverage for the // pixel to be filled. @@ -437,9 +433,7 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f if (remaining_parts) { db::EdgeProcessor ep; - std::vector fp1; - fp1.push_back (fp0); - ep.boolean (fp1, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); + ep.boolean (filled_poly, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); } return true; diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 870927267..386fcfee4 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -4345,7 +4345,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill", &fill_region_skew, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), @@ -4362,7 +4362,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("fill_multi", &fill_region_multi, gsi::arg ("in_cell"), gsi::arg ("fill_cell_index"), @@ -4377,7 +4377,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method is equivalent to \\Cell#fill_region, but is based on Region (with the cell being the first parameter).\n" "\n" - "This method has been introduced in version 0.27.\n" + "This method has been introduced in version 0.27. The 'exclude_area' argument has been added in version 0.30.4.\n" ) + gsi::method_ext ("nets", &nets, gsi::arg ("extracted"), gsi::arg ("net_prop_name", tl::Variant (), "nil"), gsi::arg ("net_filter", (const std::vector *) (0), "nil"), "@brief Pulls the net shapes from a LayoutToNetlist database\n" diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index b672bd877..44466da10 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -5897,7 +5897,7 @@ CODE pattern = nil origin = RBA::DPoint::new repeat = false - excl = RBA::Region::new + excl = nil args.each_with_index do |a,ai| if a.is_a?(DRCSource) @@ -5946,6 +5946,10 @@ CODE column_step = RBA::DVector::new(0, pattern.default_ypitch) end + if !excl + excl = RBA::Region::new + end + dbu_trans = RBA::VCplxTrans::new(1.0 / @engine.dbu) result = nil From 810f5fb8aa0604fbcddd196881f7302e4cf84569 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Sep 2025 21:25:47 +0200 Subject: [PATCH 36/52] Added basic fill tool tests for exclude_area --- src/db/db/dbFillTool.cc | 22 ++++++- src/db/unit_tests/dbFillToolTests.cc | 95 +++++++++++++++++++++++++++ testdata/algo/fill_tool7.gds | Bin 0 -> 560 bytes testdata/algo/fill_tool8.gds | Bin 0 -> 560 bytes testdata/algo/fill_tool9.gds | Bin 0 -> 688 bytes testdata/algo/fill_tool_au7.oas | Bin 0 -> 1358 bytes testdata/algo/fill_tool_au8.oas | Bin 0 -> 818 bytes testdata/algo/fill_tool_au9.oas | Bin 0 -> 876 bytes 8 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 testdata/algo/fill_tool7.gds create mode 100644 testdata/algo/fill_tool8.gds create mode 100644 testdata/algo/fill_tool9.gds create mode 100644 testdata/algo/fill_tool_au7.oas create mode 100644 testdata/algo/fill_tool_au8.oas create mode 100644 testdata/algo/fill_tool_au9.oas diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index cf08a437a..0c60f8c64 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -324,9 +324,13 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f // over- and undersize the polygons to fill gaps that cannot be filled. db::Region excluded (it.first, it.second); + excluded.set_merged_semantics (false); excluded.size (dx, 0); + excluded.set_merged_semantics (true); excluded.size (-dx, 0); + excluded.set_merged_semantics (false); excluded.size (dy, 0); + excluded.set_merged_semantics (true); excluded.size (-dy, 0); excluded.merge (); @@ -354,13 +358,27 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f } - std::vector filled_poly; + std::vector filled_poly, filled_poly_uncleaned; + + // save the uncleaned polygons, so we subtract the filled parts to + // form the remaining parts + if (remaining_parts) { + filled_poly_uncleaned.reserve (fr.count ()); + for (auto i = fr.begin (); ! i.at_end (); ++i) { + filled_poly_uncleaned.push_back (*i); + } + } // under- and oversize the polygon to remove slivers that cannot be filled. + fr.set_merged_semantics (true); fr.size (-dx, 0); + fr.set_merged_semantics (false); fr.size (dx, 0); + fr.set_merged_semantics (true); fr.size (0, -dy); + fr.set_merged_semantics (false); fr.size (0, dy); + fr.set_merged_semantics (true); fr.merge (); filled_poly.reserve (fr.count ()); @@ -433,7 +451,7 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f if (remaining_parts) { db::EdgeProcessor ep; - ep.boolean (filled_poly, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); + ep.boolean (filled_poly_uncleaned, filled_regions, *remaining_parts, db::BooleanOp::ANotB, false /*=don't resolve holes*/); } return true; diff --git a/src/db/unit_tests/dbFillToolTests.cc b/src/db/unit_tests/dbFillToolTests.cc index 4ce7b53bb..e36af2eb6 100644 --- a/src/db/unit_tests/dbFillToolTests.cc +++ b/src/db/unit_tests/dbFillToolTests.cc @@ -377,3 +377,98 @@ TEST(6) CHECKPOINT(); db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au6.oas", db::WriteOAS); } + +// exclude_area +TEST(7) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool7.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Region remaining_polygons; + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), false, &remaining_polygons, db::Vector (), 0, db::Box (), excl_region); + + unsigned int l100 = ly.insert_layer (db::LayerProperties (100, 0)); + remaining_polygons.insert_into (&ly, top_cell, l100); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au7.oas", db::WriteOAS); +} + +// exclude_area +TEST(8) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool8.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), false, 0, db::Vector (), 0, db::Box (), excl_region); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au8.oas", db::WriteOAS); +} + +// exclude_area +TEST(9) +{ + db::Layout ly; + { + std::string fn (tl::testdata ()); + fn += "/algo/fill_tool9.gds"; + tl::InputStream stream (fn); + db::Reader reader (stream); + reader.read (ly); + } + + db::cell_index_type fill_cell = ly.cell_by_name ("FILL_CELL").second; + db::cell_index_type top_cell = ly.cell_by_name ("TOP").second; + + unsigned int fill_layer = ly.get_layer (db::LayerProperties (1, 0)); + db::Region fill_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), fill_layer)); + + unsigned int excl_layer = ly.get_layer (db::LayerProperties (2, 0)); + db::Region excl_region (db::RecursiveShapeIterator (ly, ly.cell (top_cell), excl_layer)); + + db::Vector rs (2500, 0); + db::Vector cs (650, 2500); + db::Box fc_box = ly.cell (fill_cell).bbox (); + db::fill_region (&ly.cell (top_cell), fill_region, fill_cell, fc_box, rs, cs, db::Point (), true, 0, db::Vector (), 0, db::Box (), excl_region); + + CHECKPOINT(); + db::compare_layouts (_this, ly, tl::testdata () + "/algo/fill_tool_au9.oas", db::WriteOAS); +} diff --git a/testdata/algo/fill_tool7.gds b/testdata/algo/fill_tool7.gds new file mode 100644 index 0000000000000000000000000000000000000000..b9beb8fde66acaa67e971408c5d6b668a7f1da57 GIT binary patch literal 560 zcmaKpJxc>Y5Qd-KyX-~KppY_!oy~y?0h1>9aSFi$Ee;8CU?uni@&nRX*w|Wlg`Hq+ zkr&42mjE8g5Qms<^!gRj<_A zI9uC&y#8oL{pG96!-QICichIzZy)?Js5yy5RU{D3d(c~i)hy?62>pAow7Q|cP~gA0 z1sg9We}4u`s~Z;hwZ1Tejq4MNaDbRiv9!8jksr=iV5yt)Ac3a_SX$lC?Js`cAN{<4 z*wH#0z^R$xb)@xIYU0i;Y8^h)yjJ%F9wJjaL&!R`dVju2-GHh4uPC+Cj^m@P?KuA5 SqUZhs&I!ighI3`R`99xC-ekl8 literal 0 HcmV?d00001 diff --git a/testdata/algo/fill_tool8.gds b/testdata/algo/fill_tool8.gds new file mode 100644 index 0000000000000000000000000000000000000000..b9beb8fde66acaa67e971408c5d6b668a7f1da57 GIT binary patch literal 560 zcmaKpJxc>Y5Qd-KyX-~KppY_!oy~y?0h1>9aSFi$Ee;8CU?uni@&nRX*w|Wlg`Hq+ zkr&42mjE8g5Qms<^!gRj<_A zI9uC&y#8oL{pG96!-QICichIzZy)?Js5yy5RU{D3d(c~i)hy?62>pAow7Q|cP~gA0 z1sg9We}4u`s~Z;hwZ1Tejq4MNaDbRiv9!8jksr=iV5yt)Ac3a_SX$lC?Js`cAN{<4 z*wH#0z^R$xb)@xIYU0i;Y8^h)yjJ%F9wJjaL&!R`dVju2-GHh4uPC+Cj^m@P?KuA5 SqUZhs&I!ighI3`R`99xC-ekl8 literal 0 HcmV?d00001 diff --git a/testdata/algo/fill_tool9.gds b/testdata/algo/fill_tool9.gds new file mode 100644 index 0000000000000000000000000000000000000000..06267cd02b9cdac4bed4d16445887367348193fe GIT binary patch literal 688 zcmaKqJxc>Y5Qd-ITs9FjC}<-o>}&!m1k@(@5ryD^7D0jpOe6RM@&nRX*w|Vm0Xs3Z zMM?`lf>@-q5J{;ZM6j}2=Pp@u7Uvyv&oaxq@9fN>QL04RQHgiP(9Fm?8(x!C+AD0p zbS$1*KV9Crzx-Uyw-+wz2Sw8HXkAL{?SkhWZfZzn z+$qr~_}49|b#ZrZA~xhG2dm08~GvD;Hx~iOZ6D;@^ZHZ Rj*>cawO?IuuFT(c`2i#whJ644 literal 0 HcmV?d00001 diff --git a/testdata/algo/fill_tool_au7.oas b/testdata/algo/fill_tool_au7.oas new file mode 100644 index 0000000000000000000000000000000000000000..d0bd1c1ecb69353a1893ce751371efe0062b0755 GIT binary patch literal 1358 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKC?n3q!6L)YEF;ds&!EJR>XUoMnybM;fb~F; z;|1o9>4KX(8~>b$4qRRSKbqrK6n{_8gJqfK;s{4u4aE59`6&JF7p2S^LtL- zN4>OXpFHNE?EmS(ypGwOH&4rn3Cn%k@+Q^rVhVq;x6iCMPua9HSSllr{9e>gsFs)0fX<2+eSKk$X&{@!h+=4=OEt!YqqA6ZC~;>ppr( z$Q;g#PwL(yKfU?l*F_%$(}dTF?~3$cQu^6+;*s&fD$g?pFJ=oHWwuRkHLu*J&%;#v z)bi|=g|06x!`5;zx@EL2w{o%mq_isDWtH3V*GyMWNq!1Tc~i?hInesLU3BPfrd6EBlhbD89nO|m z$M^hyx#PSqMS1Vub6ACxmCg^k7%_d*@yCbQ*y19;KVgs6Z84NDeWrf!R;bzACpnOe4t;aFzfPh~sj?Q5P1S?Z_;9G)zs=_K{< z;H90u6D3t#Hz%a}Su0tEMQx5~OPN1;r^oK%**_+$PI#Y@*BgBN!0nFIhi~HxT)fWI zd)7X$+H|d4@pERTO2-n#^1M?258?A37BJqins)GpkM8~mtIof#zUs>Kk_4aCRn|A!n^Y1qqN= zOF5FHH?NlZ75b4S@B5K>&fNbKuO0vK%sgu9wB?>BOC^43NAYcBTJuZ5yn4H1-A?2G z(>*d)?GX*ynv=5X!e8A3@h3m-_TT$>SM5YjwsT39%PK;Y_)R}aF@y52j5q^F10yr1 io2QRYytAv14@jVbiE%RrBLkx{V=<6qXc%FDfdK#qe}BdR literal 0 HcmV?d00001 diff --git a/testdata/algo/fill_tool_au8.oas b/testdata/algo/fill_tool_au8.oas new file mode 100644 index 0000000000000000000000000000000000000000..3e1e067a48e173d7ac11fa5566c2ed796fe52d3c GIT binary patch literal 818 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKC?n3q!6L)YEF;ds&!EJR>XUoMnybM;fb~F; z;|1o9>4KX(8~>b$4qRRSKbqrK6n{_8gJqfK;z#s_cQDE@Af_%AabnueC?KN z>A9<3sB!u2vflk$eg~&$Oo#xJo@2-(?w~UPjtjHcw8gYEmIZ7$8l)An;=zT5S*}+! z_(a)RMfbLIG!}pQz3=ng;->jW{wQ7)%sAWLbnE+vAo~?JB){7{XVAEDo&9mP6K9sI z(0!E)Egpdjm{*G${8}1hFQ&n`=;xXn*2+Rw>PoL0a=x*BF;{yXI9u@XjLW(6lGd7e zDVsdH&~(0Y(bXmYm9@8&xf)u|sZtbh=)W7Y$Mx(>9sNZqq7_zL4gssb9hEcObS2m` zh{N0D=ik%k^A%Uz-8x(OLgzB>`W&~aq~bpTX+bW})=l^+bj$N$?u5$c_l%#)|NHOv z{<-Bk`Tq&`WLGNPo_3v!XN5t&|L;cCCx$XNv%7z{=IlGL=VWvCE<1Z4)$=&kIpuBszmQV<9BNH?0*`kx^1#5 z=2Ee7nGxY{aJb>O^nPo>UW@*H-gE8iFWzY{@Sae;D0B8wp+~t>|1mOy;z&lEfun(u knbXbF$0y#|)yD@UP{G7ForRHsQJJwANHR2xFu=e705j)tnE(I) literal 0 HcmV?d00001 diff --git a/testdata/algo/fill_tool_au9.oas b/testdata/algo/fill_tool_au9.oas new file mode 100644 index 0000000000000000000000000000000000000000..3454e4e7b0a14638f283c864dbc76afcd7e6be8b GIT binary patch literal 876 zcmY!lcJ=kt^>+;R4CduxWH!_@V0gjKC?n3q!6L)YEF;ds&!EJR>XUoMnybM;fb~F; z;|1o9>4KX(8~>b$4qRRSKbqrK6n{_8gJqfj7ki>EGL;&C*1cI2^2a0eO~qX zyQ-0yB|7p|oSaSn7(Oa;RPp}d{^I#@;q*EOo-an5IGl5LZcwa0_YJFs`-%NqY<*py3Hs?Wvvkb#y=Ls0ulaB_4}{ zx-WNcT(?H^j!kw(qSG<;mehmmzfLIMz5euH*%-r*=Q+=ZE)8$EBR1c&+g$il{*?Jl t%$#nXK0fizu0B38;tU)W%%Io?u^Jee7%#FgGB7GL76VC!h7krB7yvuci`oDH literal 0 HcmV?d00001 From cd34125b0c8cb9a4b2bafd0954bb40ae2699bbe8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 6 Sep 2025 22:28:01 +0200 Subject: [PATCH 37/52] Bugfixing new implementation of fill tool, added tests for DRC implementation --- src/db/db/dbFillTool.cc | 63 ++++++++++++++++--------- src/drc/unit_tests/drcSimpleTests.cc | 10 ++++ testdata/drc/drcSimpleTests_47c.drc | 21 +++++++++ testdata/drc/drcSimpleTests_47c.gds | Bin 0 -> 10458 bytes testdata/drc/drcSimpleTests_au47c.gds | Bin 0 -> 96388 bytes testdata/drc/drcSimpleTests_au47cd.gds | Bin 0 -> 96788 bytes 6 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 testdata/drc/drcSimpleTests_47c.drc create mode 100644 testdata/drc/drcSimpleTests_47c.gds create mode 100644 testdata/drc/drcSimpleTests_au47c.gds create mode 100644 testdata/drc/drcSimpleTests_au47cd.gds diff --git a/src/db/db/dbFillTool.cc b/src/db/db/dbFillTool.cc index 0c60f8c64..bf8abba3e 100644 --- a/src/db/db/dbFillTool.cc +++ b/src/db/db/dbFillTool.cc @@ -77,6 +77,16 @@ public: return (unsigned int) m_area_maps.size (); } + int index_for_p0 (const db::Point &p0) const + { + for (auto i = m_area_maps.begin (); i != m_area_maps.end (); ++i) { + if (i->p0 () == p0) { + return int (i - m_area_maps.begin ()); + } + } + return -1; + } + const db::AreaMap &area_map (unsigned int i) const { return m_area_maps [i]; @@ -212,8 +222,10 @@ create_instances (GenericRasterizer &am, db::Cell *cell, db::cell_index_type fil db::AreaMap &am1 = am.area_map (i); const db::AreaMap *am1_excl = 0; if (exclude_rasterized) { - tl_assert (i < exclude_rasterized->area_maps ()); - am1_excl = &exclude_rasterized->area_map (i); + int ie = exclude_rasterized->index_for_p0 (am1.p0 ()); + if (ie >= 0) { + am1_excl = &exclude_rasterized->area_map (ie); + } } size_t nx = am1.nx (); @@ -316,6 +328,7 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f db::Box rasterized_area = fp0.box (); std::unique_ptr exclude_rasterized; + bool has_exclude_area = false; if (! exclude_area.empty ()) { @@ -334,25 +347,31 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f excluded.size (-dy, 0); excluded.merge (); - if (enhanced_fill || remaining_parts != 0) { + if (! excluded.empty ()) { - // In enhanced fill or if the remaining parts are requested, it is better to implement the - // exclude area by a boolean NOT - fr -= excluded; + has_exclude_area = true; - } else { + if (enhanced_fill || remaining_parts != 0) { - // Otherwise use a second rasterizer for the exclude polygons that must have a zero pixel coverage for the - // pixel to be filled. + // In enhanced fill or if the remaining parts are requested, it is better to implement the + // exclude area by a boolean NOT + fr -= excluded; + + } else { + + // Otherwise use a second rasterizer for the exclude polygons that must have a zero pixel coverage for the + // pixel to be filled. + + std::vector excluded_poly; + excluded_poly.reserve (excluded.count ()); + for (auto i = excluded.begin (); ! i.at_end (); ++i) { + excluded_poly.push_back (*i); + } + excluded.clear (); + + exclude_rasterized.reset (new GenericRasterizer (excluded_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ())); - std::vector excluded_poly; - excluded_poly.reserve (excluded.count ()); - for (auto i = excluded.begin (); ! i.at_end (); ++i) { - excluded_poly.push_back (*i); } - excluded.clear (); - - exclude_rasterized.reset (new GenericRasterizer (excluded_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ())); } @@ -388,14 +407,14 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f fr.clear (); - if (filled_poly.empty ()) { - return false; - } - std::vector filled_regions; bool any_fill = false; - if (exclude_rasterized.get ()) { + if (filled_poly.empty ()) { + + // not need to do anything + + } else if (exclude_rasterized.get ()) { tl_assert (remaining_parts == 0); GenericRasterizer am (filled_poly, rasterized_area, row_step, column_step, origin, fc_bbox.p2 () - fc_bbox.p1 ()); @@ -447,7 +466,7 @@ fill_polygon_impl (db::Cell *cell, const db::Polygon &fp0, db::cell_index_type f } - if (any_fill) { + if (any_fill || has_exclude_area) { if (remaining_parts) { db::EdgeProcessor ep; diff --git a/src/drc/unit_tests/drcSimpleTests.cc b/src/drc/unit_tests/drcSimpleTests.cc index 258c5969d..6ccccb378 100644 --- a/src/drc/unit_tests/drcSimpleTests.cc +++ b/src/drc/unit_tests/drcSimpleTests.cc @@ -1453,6 +1453,16 @@ TEST(47bd_fillWithUsingOutputDeep) run_test (_this, "47b", true); } +TEST(47c_fillWithExcludeArea) +{ + run_test (_this, "47c", false); +} + +TEST(47cd_fillWithExcludeAreaDeep) +{ + run_test (_this, "47c", true); +} + TEST(48_drcWithFragments) { run_test (_this, "48", false); diff --git a/testdata/drc/drcSimpleTests_47c.drc b/testdata/drc/drcSimpleTests_47c.drc new file mode 100644 index 000000000..b2b9b1f41 --- /dev/null +++ b/testdata/drc/drcSimpleTests_47c.drc @@ -0,0 +1,21 @@ + +source $drc_test_source +target $drc_test_target + +if $drc_test_deep + deep +end + +l1 = input(1, 0) +l2 = input(2, 0) + +p = fill_pattern("PAT1").shape(100, 0, box(0, 0, 5.um, 5.um)).origin(-2.5.um, -2.5.um) +l1.fill(p, hstep(10.0, 5.0), vstep(-5.0, 10.0), fill_exclude(l2)) + +p = fill_pattern("PAT1").shape(110, 0, box(0, 0, 5.um, 5.um)).origin(-2.5.um, -2.5.um) +left = l1.fill_with_left(p, hstep(10.0, 5.0), vstep(-5.0, 10.0), fill_exclude(l2)) + +l1.output(1, 0) +l2.output(2, 0) +left.output(111, 0) + diff --git a/testdata/drc/drcSimpleTests_47c.gds b/testdata/drc/drcSimpleTests_47c.gds new file mode 100644 index 0000000000000000000000000000000000000000..5bcf8d5a77855fd83b6a28b7bd95ae0d72089e6e GIT binary patch literal 10458 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 literal 0 HcmV?d00001 diff --git a/testdata/drc/drcSimpleTests_au47cd.gds b/testdata/drc/drcSimpleTests_au47cd.gds new file mode 100644 index 0000000000000000000000000000000000000000..bb300a375d2037f255193fe8833a3528848032f4 GIT binary patch literal 96788 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 SrR(SZHoIK3{_Jo#9uI#9TkNL* literal 0 HcmV?d00001 From 7997d1375c52d4b0d0d0d297825fd0976afdb268 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 00:03:42 +0200 Subject: [PATCH 38/52] Fixing tests for non-Qt builds --- testdata/ruby/layPluginTests.rb | 235 +++++++++++++++++--------------- 1 file changed, 123 insertions(+), 112 deletions(-) diff --git a/testdata/ruby/layPluginTests.rb b/testdata/ruby/layPluginTests.rb index 41b91c053..245a91e1e 100644 --- a/testdata/ruby/layPluginTests.rb +++ b/testdata/ruby/layPluginTests.rb @@ -37,141 +37,152 @@ class PluginFactory < RBA::PluginFactory end end -class Plugin2EditorOptionsPage < RBA::EditorOptionsPage - def initialize - super("title", 1) - end -end +has_qt = RBA.constants.member?(:EditorOptionsPage) && RBA.constants.member?(:MainWindow) +if has_qt -class Plugin2ConfigPage < RBA::ConfigPage - def initialize - super("title") + class Plugin2EditorOptionsPage < RBA::EditorOptionsPage + def initialize + super("title", 1) + end end -end -class Plugin2 < RBA::Plugin - def set_tp(tp) - @tp = tp + class Plugin2ConfigPage < RBA::ConfigPage + def initialize + super("title") + end end - def has_tracking_position - !!@tp - end - def tracking_position - @tp || RBA::DPoint::new - end -end -class PluginFactory2 < RBA::PluginFactory - def initialize() - @ep = 0 - @cp = 0 - @pi = nil - register(1001, "plugin_for_test2", "Plugin2") + class Plugin2 < RBA::Plugin + def set_tp(tp) + @tp = tp + end + def has_tracking_position + !!@tp + end + def tracking_position + @tp || RBA::DPoint::new + end end - def create_plugin(manager, unused, view) - @pi = Plugin2::new - @pi - end - def create_editor_options_pages - add_editor_options_page(Plugin2EditorOptionsPage::new) - @ep += 1 - end - def create_config_pages - add_config_page(Plugin2ConfigPage::new) - @cp += 1 - end - def pi - @pi - end - def ep - @ep - end - def cp - @cp - end -end -class LayPlugin_TestClass < TestBase + class PluginFactory2 < RBA::PluginFactory + def initialize() + @ep = 0 + @cp = 0 + @pi = nil + register(1001, "plugin_for_test2", "Plugin2") + end + def create_plugin(manager, unused, view) + @pi = Plugin2::new + @pi + end + def create_editor_options_pages + add_editor_options_page(Plugin2EditorOptionsPage::new) + @ep += 1 + end + def create_config_pages + add_config_page(Plugin2ConfigPage::new) + @cp += 1 + end + def pi + @pi + end + def ep + @ep + end + def cp + @cp + end + end - def test_1 + class LayPlugin_TestClass < TestBase - assert_equal(RBA::Plugin::AC_Global.to_s, "AC_Global") - assert_equal(RBA::Plugin::AC_Any.to_s, "AC_Any") - assert_equal(RBA::Plugin::AC_Diagonal.to_s, "AC_Diagonal") - assert_equal(RBA::Plugin::AC_Horizontal.to_s, "AC_Horizontal") - assert_equal(RBA::Plugin::AC_Vertical.to_s, "AC_Vertical") - - assert_equal(RBA::Plugin::ac_from_buttons(0), RBA::Plugin::AC_Global) - assert_equal(RBA::Plugin::ac_from_buttons(1), RBA::Plugin::AC_Ortho) - assert_equal(RBA::Plugin::ac_from_buttons(2), RBA::Plugin::AC_Diagonal) + def test_1 - begin - - dpi = PluginFactory::new - dpi2 = PluginFactory2::new + assert_equal(RBA::Plugin::AC_Global.to_s, "AC_Global") + assert_equal(RBA::Plugin::AC_Any.to_s, "AC_Any") + assert_equal(RBA::Plugin::AC_Diagonal.to_s, "AC_Diagonal") + assert_equal(RBA::Plugin::AC_Horizontal.to_s, "AC_Horizontal") + assert_equal(RBA::Plugin::AC_Vertical.to_s, "AC_Vertical") - # Create a new layout - main_window = RBA::MainWindow.instance() - main_window.close_all - main_window.create_layout(2) + assert_equal(RBA::Plugin::ac_from_buttons(0), RBA::Plugin::AC_Global) + assert_equal(RBA::Plugin::ac_from_buttons(1), RBA::Plugin::AC_Ortho) + assert_equal(RBA::Plugin::ac_from_buttons(2), RBA::Plugin::AC_Diagonal) - pi = dpi.pi - pi2 = dpi2.pi - - # smoke test - pi.grab_mouse - pi.ungrab_mouse - pi.set_cursor(RBA::Cursor::Wait) - pi.add_edge_marker(RBA::DEdge::new) - pi.add_mouse_cursor(RBA::DPoint::new) - pi.clear_mouse_cursors + begin - # virtual methods - assert_equal(pi.has_tracking_position_test, false) - pi.clear_mouse_cursors - pi.add_mouse_cursor(RBA::DPoint::new(1, 2)) - assert_equal(pi.has_tracking_position_test, true) - assert_equal(pi.tracking_position_test.to_s, "1,2") - pi.clear_mouse_cursors - assert_equal(pi.has_tracking_position_test, false) + dpi = PluginFactory::new + dpi2 = PluginFactory2::new + + # Create a new layout + main_window = RBA::MainWindow.instance() + main_window.close_all + main_window.create_layout(2) - assert_equal(pi2.has_tracking_position_test, false) - pi2.set_tp(RBA::DPoint::new(2, 3)) - assert_equal(pi2.has_tracking_position_test, true) - assert_equal(pi2.tracking_position_test.to_s, "2,3") - pi2.set_tp(nil) - assert_equal(pi2.has_tracking_position_test, false) + pi = dpi.pi + pi2 = dpi2.pi + + # smoke test + pi.grab_mouse + pi.ungrab_mouse + pi.set_cursor(RBA::Cursor::Wait) + pi.add_edge_marker(RBA::DEdge::new) + pi.add_mouse_cursor(RBA::DPoint::new) + pi.clear_mouse_cursors - assert_equal(dpi2.ep, 1) - assert_equal(dpi2.cp, 1) - assert_equal(pi2.editor_options_pages.size, 1) - assert_equal(pi2.editor_options_pages[0].class.to_s, "Plugin2EditorOptionsPage") + # virtual methods + assert_equal(pi.has_tracking_position_test, false) + pi.clear_mouse_cursors + pi.add_mouse_cursor(RBA::DPoint::new(1, 2)) + assert_equal(pi.has_tracking_position_test, true) + assert_equal(pi.tracking_position_test.to_s, "1,2") + pi.clear_mouse_cursors + assert_equal(pi.has_tracking_position_test, false) - pi.configure_test("edit-grid", "0.0") - assert_equal(pi.snap(RBA::DPoint::new(0.01, 0.02)).to_s, "0.01,0.02") - assert_equal(pi.snap(RBA::DVector::new(0.01, 0.02)).to_s, "0.01,0.02") - pi.configure_test("edit-grid", "0.1") - assert_equal(pi.snap(RBA::DPoint::new(0.11, 0.18)).to_s, "0.1,0.2") - assert_equal(pi.snap(RBA::DVector::new(0.11, 0.18)).to_s, "0.1,0.2") + assert_equal(pi2.has_tracking_position_test, false) + pi2.set_tp(RBA::DPoint::new(2, 3)) + assert_equal(pi2.has_tracking_position_test, true) + assert_equal(pi2.tracking_position_test.to_s, "2,3") + pi2.set_tp(nil) + assert_equal(pi2.has_tracking_position_test, false) - pi.configure_test("edit-connect-angle-mode", "ortho") - assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), true).to_s, "1.5,2") - assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false).to_s, "1.5,2.1") - assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false, RBA::Plugin::AC_Ortho).to_s, "1.5,2") + assert_equal(dpi2.ep, 1) + assert_equal(dpi2.cp, 1) + assert_equal(pi2.editor_options_pages.size, 1) + assert_equal(pi2.editor_options_pages[0].class.to_s, "Plugin2EditorOptionsPage") - pi.configure_test("edit-connect-angle-mode", "ortho") - assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), true).to_s, "0,2.1") - assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false).to_s, "0.5,2.1") - assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false, RBA::Plugin::AC_Ortho).to_s, "0,2.1") + pi.configure_test("edit-grid", "0.0") + assert_equal(pi.snap(RBA::DPoint::new(0.01, 0.02)).to_s, "0.01,0.02") + assert_equal(pi.snap(RBA::DVector::new(0.01, 0.02)).to_s, "0.01,0.02") + pi.configure_test("edit-grid", "0.1") + assert_equal(pi.snap(RBA::DPoint::new(0.11, 0.18)).to_s, "0.1,0.2") + assert_equal(pi.snap(RBA::DVector::new(0.11, 0.18)).to_s, "0.1,0.2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), true).to_s, "1.5,2") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false).to_s, "1.5,2.1") + assert_equal(pi.snap(RBA::DPoint::new(1.5, 2.1), RBA::DPoint::new(1, 2), false, RBA::Plugin::AC_Ortho).to_s, "1.5,2") + + pi.configure_test("edit-connect-angle-mode", "ortho") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), true).to_s, "0,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false).to_s, "0.5,2.1") + assert_equal(pi.snap(RBA::DVector::new(0.5, 2.1), false, RBA::Plugin::AC_Ortho).to_s, "0,2.1") + + ensure + main_window.close_all + dpi._destroy + dpi2._destroy + end - ensure - main_window.close_all - dpi._destroy - dpi2._destroy end end +else + + # at least one test is needed + class LayPlugin_TestClass < TestBase + end + end load("test_epilogue.rb") From eb6369f90f32a4c814e6df8f6ebad395f6da8e52 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 00:31:08 +0200 Subject: [PATCH 39/52] Updated doc --- src/db/db/gsiDeclDbCell.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 44731b59f..52543c93b 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -2249,8 +2249,11 @@ Class decl_Cell ("db", "Cell", "If the 'remaining_polygons' argument is non-nil, the corresponding region will receive all polygons from the input region " "which could not be filled and where there is no chance of filling because not a single tile will fit into them.\n" "\n" - "'remaining_parts' and 'remaining_polygons' can be identical with the input. In that case the input will be overwritten with " + "'remaining_parts' and 'remaining_polygons' can point to the same Region object.\n"" + "They can also be identical with the input. In that case the input will be overwritten with " "the respective output. Otherwise, the respective polygons are added to these regions.\n" + "'remaining_polygons' is not used if 'exclude_area' is present and non-empty. In that case, the\n" + "original polygons, which cannot be filled at all, are copied to 'remaining_parts'.\n" "\n" "This allows setting up a more elaborate fill scheme using multiple iterations and local origin-optimization ('origin' is nil):\n" "\n" @@ -2263,7 +2266,7 @@ Class decl_Cell ("db", "Cell", "fill_margin = RBA::Point::new(0, 0) # x/y distance between tile cells with different origin\n" "\n" "# Iteration: fill a region and fill the remaining parts as long as there is anything left.\n" - "# Polygons not worth being considered further are dropped (last argument is nil).\n" + "# Polygons not worth being considered further are dropped ('remaining_polygons' argument is nil).\n" "while !r.is_empty?\n" " c.fill_region(r, fc_index, fc_box, nil, r, fill_margin, nil)\n" "end\n" From 3d5833d3233a36ad9fa0a4d2ac29b03372130b4b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 18:49:14 +0200 Subject: [PATCH 40/52] Updated doc --- src/db/db/gsiDeclDbCell.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 52543c93b..7138418e7 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -2249,7 +2249,7 @@ Class decl_Cell ("db", "Cell", "If the 'remaining_polygons' argument is non-nil, the corresponding region will receive all polygons from the input region " "which could not be filled and where there is no chance of filling because not a single tile will fit into them.\n" "\n" - "'remaining_parts' and 'remaining_polygons' can point to the same Region object.\n"" + "'remaining_parts' and 'remaining_polygons' can point to the same Region object.\n" "They can also be identical with the input. In that case the input will be overwritten with " "the respective output. Otherwise, the respective polygons are added to these regions.\n" "'remaining_polygons' is not used if 'exclude_area' is present and non-empty. In that case, the\n" From 128b1e596c2828a6ea646422017a2aac659fba3e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 21:40:07 +0200 Subject: [PATCH 41/52] [consider merging] MainWindow should be managed (derived from gsi::ObjectBase) --- src/lay/lay/layMainWindow.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index 56c3dd597..dee59857d 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -95,6 +95,7 @@ class ProgressWidget; class LAY_PUBLIC MainWindow : public QMainWindow, public tl::Object, + public gsi::ObjectBase, public lay::DispatcherDelegate { Q_OBJECT From aa9d951cf7ec5396e94035fad1c3d5d020f2580d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 21:43:23 +0200 Subject: [PATCH 42/52] Fixing build for old Qt versions --- src/laybasic/laybasic/layEditorOptionsPages.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/laybasic/laybasic/layEditorOptionsPages.cc b/src/laybasic/laybasic/layEditorOptionsPages.cc index 6d9b97f92..994369917 100644 --- a/src/laybasic/laybasic/layEditorOptionsPages.cc +++ b/src/laybasic/laybasic/layEditorOptionsPages.cc @@ -297,7 +297,9 @@ EditorOptionsModalPages::EditorOptionsModalPages (EditorOptionsPages *parent) ly->addLayout (ly4); mp_pages = new QTabWidget (this); ly4->addWidget (mp_pages, 1); +#if QT_VERSION >= 0x50400 mp_pages->setTabBarAutoHide (true); +#endif mp_pages->hide (); mp_single_page_frame = new QFrame (this); From aba1f87b0d952a2f8ae0f00f161eb72c8738c677 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 22:51:31 +0200 Subject: [PATCH 43/52] Enhancement to layer binding for images: now saving as img::ObjectV2 if layer binding is present. This does not break old KLayout versions trying to read session files. But old KLayout version will still crash when trying to save such a session. --- src/img/img/imgObject.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index 6801059f7..d1a63dffb 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -1358,6 +1358,9 @@ Object::from_string (const char *str, const char *base_dir) color = true; } else if (ex.test ("mono:")) { color = false; + } else { + // unrecognized token + return; } size_t w = 0; @@ -2499,13 +2502,19 @@ Object::mem_stat (db::MemStatistics *stat, db::MemStatistics::purpose_t purpose, const char * Object::class_name () const { - return "img::Object"; + if (m_layer_binding != db::LayerProperties ()) { + // This makes old KLayout versions ignore these images and not crash + return "img::ObjectV2"; + } else { + return "img::Object"; + } } /** * @brief Registration of the img::Object class in the DUserObject space */ static db::DUserObjectDeclaration class_registrar (new db::user_object_factory_impl ("img::Object")); +static db::DUserObjectDeclaration class_registrar_v2 (new db::user_object_factory_impl ("img::ObjectV2")); } // namespace img From 7fd01a64f69126563ed06b21d73670f82045216a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 7 Sep 2025 23:54:40 +0200 Subject: [PATCH 44/52] WIP: adding tab key to move tool (calls 'move by' menu), enhancing 'move_to' and 'move_by' dialogs. --- src/laybasic/laybasic/layEditorOptionsPage.cc | 4 +- src/laybasic/laybasic/layLayoutViewBase.cc | 6 ++ src/laybasic/laybasic/layLayoutViewBase.h | 5 + src/laybasic/laybasic/layMove.cc | 19 +++- src/laybasic/laybasic/layMove.h | 2 + src/layui/layui/MoveOptionsDialog.ui | 100 +++++++++++------- src/layui/layui/MoveToOptionsDialog.ui | 67 ++++-------- src/layview/layview/layLayoutView_qt.cc | 13 ++- src/layview/layview/layLayoutView_qt.h | 5 + 9 files changed, 134 insertions(+), 87 deletions(-) diff --git a/src/laybasic/laybasic/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc index 0a31eb3eb..eb2dd7435 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -98,9 +98,7 @@ BEGIN_PROTECTED // The Return key on a non-modal page commits the values and gives back the focus // to the view apply (dispatcher ()); - if (view ()->canvas ()->widget ()) { - view ()->canvas ()->widget ()->setFocus (Qt::TabFocusReason); - } + view ()->set_focus (); event->accept (); } else { QWidget::keyPressEvent (event); diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 22c6aea44..7f399ff29 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -691,6 +691,12 @@ LayoutViewBase::message (const std::string & /*s*/, int /*timeout*/) // .. nothing yet .. } +void +LayoutViewBase::set_focus () +{ + // .. nothing yet .. +} + bool LayoutViewBase::is_dirty () const { diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index a19effbf0..2bfb8f5b6 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -295,6 +295,11 @@ public: */ virtual void message (const std::string &s = "", int timeout = 10); + /** + * @brief Sets the keyboard focus to the view + */ + virtual void set_focus (); + /** * @brief The "dirty" flag indicates that one of the layout has been modified * diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 35c86188c..3c15c3dd3 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -39,6 +39,7 @@ MoveService::MoveService (lay::LayoutViewBase *view) lay::Plugin (view), m_dragging (false), m_dragging_transient (false), + m_active (false), mp_editables (view), mp_view (view), m_global_grid (0.001) @@ -51,10 +52,17 @@ MoveService::~MoveService () drag_cancel (); } -void +void +MoveService::activated () +{ + m_active = true; +} + +void MoveService::deactivated () { m_shift = db::DPoint (); + m_active = false; mp_view->clear_transient_selection (); drag_cancel (); } @@ -87,8 +95,15 @@ MoveService::configure (const std::string &name, const std::string &value) } bool -MoveService::key_event (unsigned int key, unsigned int /*buttons*/) +MoveService::key_event (unsigned int key, unsigned int buttons) { + if (m_active && key == Qt::Key_Tab && buttons == 0) { + if (dispatcher ()) { + dispatcher ()->menu_activated ("cm_sel_move"); + } + return true; + } + double dx = 0.0, dy = 0.0; if (int (key) == lay::KeyDown) { dy = -1.0; diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 0fe112aef..485c12aaa 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -61,12 +61,14 @@ private: virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); virtual bool key_event (unsigned int key, unsigned int buttons); virtual void drag_cancel (); + virtual void activated (); virtual void deactivated (); bool handle_click (const db::DPoint &p, unsigned int buttons, bool drag_transient, db::Transaction *transaction); bool m_dragging; bool m_dragging_transient; + bool m_active; lay::Editables *mp_editables; lay::LayoutViewBase *mp_view; double m_global_grid; diff --git a/src/layui/layui/MoveOptionsDialog.ui b/src/layui/layui/MoveOptionsDialog.ui index 297f68ed3..f85443448 100644 --- a/src/layui/layui/MoveOptionsDialog.ui +++ b/src/layui/layui/MoveOptionsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 233 + 240 168 @@ -17,7 +17,7 @@ 6 - + 9 @@ -26,12 +26,16 @@ Displacement - - 9 - 6 + + + + µm + + + @@ -45,13 +49,6 @@ - - - - µm - - - @@ -69,13 +66,6 @@ - - - - x - - - @@ -86,6 +76,13 @@ + + + + x + + + @@ -117,26 +114,9 @@ disp_x_le disp_y_le - buttonBox - - buttonBox - accepted() - MoveOptionsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - buttonBox rejected() @@ -153,5 +133,53 @@ + + buttonBox + accepted() + MoveOptionsDialog + accept() + + + 119 + 146 + + + 119 + 83 + + + + + disp_x_le + returnPressed() + MoveOptionsDialog + accept() + + + 119 + 53 + + + 119 + 83 + + + + + disp_y_le + returnPressed() + MoveOptionsDialog + accept() + + + 119 + 84 + + + 119 + 83 + + + diff --git a/src/layui/layui/MoveToOptionsDialog.ui b/src/layui/layui/MoveToOptionsDialog.ui index 448a5e6b7..b61c7cf2b 100644 --- a/src/layui/layui/MoveToOptionsDialog.ui +++ b/src/layui/layui/MoveToOptionsDialog.ui @@ -7,7 +7,7 @@ 0 0 396 - 345 + 357 @@ -354,6 +354,13 @@ + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -369,44 +376,14 @@ 0 - - - - Qt::Horizontal - - - - 209 - 20 - - - - - - - - Ok - - - true - - - - - - - Cancel - - - - pushButton - pushButton_2 + x_le + y_le lt ct rt @@ -422,34 +399,34 @@ - pushButton - clicked() + buttonBox + accepted() MoveToOptionsDialog accept() - 237 - 203 + 197 + 323 - 147 - 81 + 197 + 175 - pushButton_2 - clicked() + buttonBox + rejected() MoveToOptionsDialog reject() - 325 - 202 + 197 + 323 - 325 - 57 + 197 + 175 diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index 692ca8e2b..4502aec59 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -1577,12 +1577,21 @@ LayoutView::message (const std::string &s, int timeout) } } +void +LayoutView::set_focus () +{ + if (canvas () && canvas ()->widget ()) { + canvas ()->widget ()->setFocus (Qt::TabFocusReason); + } +} + void LayoutView::mode (int m) { if (mode () != m) { LayoutViewBase::mode (m); activate_editor_option_pages (); + set_focus (); } } @@ -1599,10 +1608,12 @@ void LayoutView::switch_mode (int m) { if (mode () != m) { - mode (m); + LayoutViewBase::mode (m); + activate_editor_option_pages (); if (mp_widget) { mp_widget->emit_mode_change (m); } + set_focus (); } } diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 59b0a1757..5867dbf60 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -183,6 +183,11 @@ public: */ void message (const std::string &s = "", int timeout = 10); + /** + * @brief Sets the keyboard focus to the view + */ + virtual void set_focus (); + /** * @brief Select a certain mode (by index) */ From e9584c289a26959c4dc0a79aee1748b27e79f42c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Sep 2025 00:11:24 +0200 Subject: [PATCH 45/52] Tab order of some dialogs --- src/edt/edt/AlignOptionsDialog.ui | 5 +- src/edt/edt/AreaAndPerimeterDialog.ui | 4 + src/edt/edt/BoxPropertiesPage.ui | 454 +++++++++---------- src/edt/edt/CopyModeDialog.ui | 9 +- src/edt/edt/DistributeOptionsDialog.ui | 15 +- src/edt/edt/EditorOptionsGeneric.ui | 1 + src/edt/edt/EditorOptionsPath.ui | 7 + src/edt/edt/EditorOptionsText.ui | 11 +- src/edt/edt/InstPropertiesPage.ui | 4 +- src/edt/edt/MakeCellOptionsDialog.ui | 13 + src/edt/edt/PointPropertiesPage.ui | 2 + src/edt/edt/RoundCornerOptionsDialog.ui | 8 +- src/edt/edt/TextPropertiesPage.ui | 306 ++++++------- src/layui/layui/LayoutViewConfigPage7.ui | 8 + src/layui/layui/LibraryCellSelectionForm.ui | 9 + src/layui/layui/MarkerBrowserConfigPage.ui | 5 + src/layui/layui/MarkerBrowserConfigPage2.ui | 124 ++--- src/layui/layui/NetlistBrowserConfigPage.ui | 5 + src/layui/layui/NetlistBrowserConfigPage2.ui | 20 +- src/layui/layui/UserPropertiesForm.ui | 10 +- 20 files changed, 554 insertions(+), 466 deletions(-) diff --git a/src/edt/edt/AlignOptionsDialog.ui b/src/edt/edt/AlignOptionsDialog.ui index d94795de2..62e60e6a7 100644 --- a/src/edt/edt/AlignOptionsDialog.ui +++ b/src/edt/edt/AlignOptionsDialog.ui @@ -419,8 +419,6 @@ - all_layers_rb - visible_layers_rb h_none_rb h_left_rb h_center_rb @@ -429,7 +427,8 @@ v_top_rb v_center_rb v_bottom_rb - buttonBox + all_layers_rb + visible_layers_rb diff --git a/src/edt/edt/AreaAndPerimeterDialog.ui b/src/edt/edt/AreaAndPerimeterDialog.ui index 9fd1a263a..1e8ffb29d 100644 --- a/src/edt/edt/AreaAndPerimeterDialog.ui +++ b/src/edt/edt/AreaAndPerimeterDialog.ui @@ -108,6 +108,10 @@ The perimeter calculation only takes true outside edges into account. Internal e + + area_le + perimeter_le + diff --git a/src/edt/edt/BoxPropertiesPage.ui b/src/edt/edt/BoxPropertiesPage.ui index 0d0abe629..03f7d3493 100644 --- a/src/edt/edt/BoxPropertiesPage.ui +++ b/src/edt/edt/BoxPropertiesPage.ui @@ -1,7 +1,8 @@ - + + BoxPropertiesPage - - + + 0 0 @@ -9,63 +10,60 @@ 370 - + Form - - - 9 - - + + 6 + + 9 + - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + Sans Serif 12 - 75 false true false false - + Box Properties - - - - 7 - 5 + + + 0 0 - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -75,13 +73,13 @@ - + Qt::Vertical - + QSizePolicy::Fixed - + 20 10 @@ -90,420 +88,396 @@ - - + + 1 - - + + Corners - - + + 9 - + 6 - - - + + + x = - - - + + + Qt::Horizontal - - - - - 7 - 0 + + + + 1 0 - - - - - 7 - 0 + + + + 1 0 - - - + + + h = - - - - - 7 - 0 + + + + 1 0 - - - + + + Width/height - - - - - 7 - 0 + + + + 1 0 - - - + + + w = - - - + + + Upper right (x/y) - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + Lower left (x/y) - - - + + + y = - - - + + + x = - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + y = - - - + + + Center (x/y) - - - + + + x = - - - + + + y = - - - + + + false - + true - - - + + + false - + true - - + + Center/Size - - + + 9 - + 6 - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - - - 7 - 0 + + + + 1 0 - - - + + + Size (w/h) - - - + + + Qt::Horizontal - - - - - 7 - 0 + + + + 1 0 - - - + + + w = - - - + + + Center (x/y) - - - - - 7 - 0 + + + + 1 0 - - - + + + Lower left (x/y) - - - + + + y = - - - + + + y = - - - - - 7 - 0 + + + + 1 0 - - - + + + h = - - - + + + false - - - 7 - 0 + + 1 0 - + true - - - + + + x = - - - + + + x = - - - + + + false - + true - - - + + + false - + true - - - + + + Upper right (x/y) - - - + + + x = - - - + + + y = @@ -514,13 +488,13 @@ - + Qt::Vertical - + QSizePolicy::Fixed - + 478 10 @@ -529,25 +503,25 @@ - - + + Coordinates in database units - - + + Absolute (accumulated) transformations - + Qt::Vertical - + 478 16 @@ -556,26 +530,26 @@ - - + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - + Qt::Horizontal - + 211 20 @@ -584,15 +558,15 @@ - - + + User Properties - - + + Instantiation diff --git a/src/edt/edt/CopyModeDialog.ui b/src/edt/edt/CopyModeDialog.ui index 563c8580c..188a5ca23 100644 --- a/src/edt/edt/CopyModeDialog.ui +++ b/src/edt/edt/CopyModeDialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 164 + 464 + 180 @@ -99,6 +99,11 @@ + + shallow_rb + deep_rb + dont_ask_cbx + diff --git a/src/edt/edt/DistributeOptionsDialog.ui b/src/edt/edt/DistributeOptionsDialog.ui index f70b9c04a..756442336 100644 --- a/src/edt/edt/DistributeOptionsDialog.ui +++ b/src/edt/edt/DistributeOptionsDialog.ui @@ -671,9 +671,22 @@ + h_distribute + h_pitch + h_space + v_distribute + v_pitch + v_space + h_none_rb + h_left_rb + h_center_rb + h_right_rb + v_none_rb + v_top_rb + v_center_rb + v_bottom_rb all_layers_rb visible_layers_rb - buttonBox diff --git a/src/edt/edt/EditorOptionsGeneric.ui b/src/edt/edt/EditorOptionsGeneric.ui index 84c102c71..fb2845d53 100644 --- a/src/edt/edt/EditorOptionsGeneric.ui +++ b/src/edt/edt/EditorOptionsGeneric.ui @@ -470,6 +470,7 @@ grid_cb edit_grid_le snap_objects_cbx + snap_objects_to_grid_cbx conn_angle_cb move_angle_cb hier_sel_cbx diff --git a/src/edt/edt/EditorOptionsPath.ui b/src/edt/edt/EditorOptionsPath.ui index c50521d5a..e42456409 100644 --- a/src/edt/edt/EditorOptionsPath.ui +++ b/src/edt/edt/EditorOptionsPath.ui @@ -268,6 +268,13 @@ + + scrollArea + width_le + type_cb + start_ext_le + end_ext_le + diff --git a/src/edt/edt/EditorOptionsText.ui b/src/edt/edt/EditorOptionsText.ui index 1cc821fd4..5574d8688 100644 --- a/src/edt/edt/EditorOptionsText.ui +++ b/src/edt/edt/EditorOptionsText.ui @@ -42,8 +42,8 @@ 0 0 - 542 - 243 + 528 + 248 @@ -292,6 +292,13 @@ + + scrollArea + text_le + halign_cbx + valign_cbx + size_le + diff --git a/src/edt/edt/InstPropertiesPage.ui b/src/edt/edt/InstPropertiesPage.ui index e233b788f..67461d54d 100644 --- a/src/edt/edt/InstPropertiesPage.ui +++ b/src/edt/edt/InstPropertiesPage.ui @@ -7,7 +7,7 @@ 0 0 666 - 649 + 668 @@ -182,7 +182,7 @@ - 0 + 1 diff --git a/src/edt/edt/MakeCellOptionsDialog.ui b/src/edt/edt/MakeCellOptionsDialog.ui index e40f36a75..e1c533f9f 100644 --- a/src/edt/edt/MakeCellOptionsDialog.ui +++ b/src/edt/edt/MakeCellOptionsDialog.ui @@ -365,6 +365,19 @@ + + cell_name_le + origin_groupbox + lt + ct + rt + lc + cc + rc + lb + cb + rb + diff --git a/src/edt/edt/PointPropertiesPage.ui b/src/edt/edt/PointPropertiesPage.ui index 325d018b9..2cef295b1 100644 --- a/src/edt/edt/PointPropertiesPage.ui +++ b/src/edt/edt/PointPropertiesPage.ui @@ -272,6 +272,8 @@ + x_le + y_le dbu_cb abs_cb prop_pb diff --git a/src/edt/edt/RoundCornerOptionsDialog.ui b/src/edt/edt/RoundCornerOptionsDialog.ui index 1a43d2892..7cfd7c675 100644 --- a/src/edt/edt/RoundCornerOptionsDialog.ui +++ b/src/edt/edt/RoundCornerOptionsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 469 + 472 271 @@ -138,6 +138,12 @@ Leave empty to get the same radius than for outer corners)
+ + amend_cb + router_le + rinner_le + points_le + diff --git a/src/edt/edt/TextPropertiesPage.ui b/src/edt/edt/TextPropertiesPage.ui index 1ac85ca2d..125981400 100644 --- a/src/edt/edt/TextPropertiesPage.ui +++ b/src/edt/edt/TextPropertiesPage.ui @@ -1,52 +1,51 @@ - + + TextPropertiesPage - - + + 0 0 - 555 - 366 + 568 + 375 - + Form - - + + 9 - + 6 - - - + + + y = - - - - - 7 - 0 + + + + 1 0 - + - + Qt::Vertical - + QSizePolicy::Fixed - + 20 8 @@ -54,19 +53,19 @@ - - - + + + Text size - + - + Qt::Vertical - + 457 51 @@ -74,22 +73,22 @@ - - - + + + Text - + - + Qt::Vertical - + QSizePolicy::Fixed - + 431 8 @@ -97,34 +96,34 @@ - - - + + + x = - - - + + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - + Qt::Horizontal - + 211 20 @@ -133,15 +132,15 @@ - - + + User Properties - - + + Instantiation @@ -149,121 +148,114 @@ - - - + + + Hint: orientation, alignments and size cannot be saved to OASIS files Enable a vector font and text scaling in the setup dialog to show text objects scaled and rotated - - - + + + Orientation - - - + + + Position (x/y) - - - + + + (Leave empty for default) - - - - - 7 - 0 + + + + 0 0 - - - + + + Absolute (accumulated) transformations - - - - - 7 - 0 + + + + 1 0 - - - + + + Coordinates in database units - - - + + + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + - - + + Sans Serif 12 - 75 false true false false - + Text Properties - - - - 7 - 5 + + + 0 0 - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -271,141 +263,149 @@ to show text objects scaled and rotated - - + + - + (r0) - - :/r0_24px.png + + + :/r0_24px.png:/r0_24px.png - + (r90) - - :/r90_24px.png + + + :/r90_24px.png:/r90_24px.png - + (r180) - - :/r180_24px.png + + + :/r180_24px.png:/r180_24px.png - + (r270) - - :/r270_24px.png + + + :/r270_24px.png:/r270_24px.png - + (m0) - - :/m0_24px.png + + + :/m0_24px.png:/m0_24px.png - + (m45) - - :/m45_24px.png + + + :/m45_24px.png:/m45_24px.png - + (m90) - - :/m90_24px.png + + + :/m90_24px.png:/m90_24px.png - + (m135) - - :/m135_24px.png + + + :/m135_24px.png:/m135_24px.png - - + + - - + + - + (Default) - + Left - + Center - + Right - - - + + + Alignment - - - + + + h = - - - + + + v = - - + + - + (Default) - + Top - + Center - + Bottom @@ -427,7 +427,7 @@ to show text objects scaled and rotated inst_pb - + diff --git a/src/layui/layui/LayoutViewConfigPage7.ui b/src/layui/layui/LayoutViewConfigPage7.ui index 7f7939b75..e2a57aebf 100644 --- a/src/layui/layui/LayoutViewConfigPage7.ui +++ b/src/layui/layui/LayoutViewConfigPage7.ui @@ -390,6 +390,14 @@ + + oversampling + subres_mode + highres_mode + default_font_size + global_trans + def_depth + diff --git a/src/layui/layui/LibraryCellSelectionForm.ui b/src/layui/layui/LibraryCellSelectionForm.ui index aadc63877..d592336ec 100644 --- a/src/layui/layui/LibraryCellSelectionForm.ui +++ b/src/layui/layui/LibraryCellSelectionForm.ui @@ -240,6 +240,15 @@ p, li { white-space: pre-wrap; }
layWidgets.h
+ + lib_cb + le_cell_name + find_next + lv_cells + cb_show_all_cells + ok_button + cancel_button + diff --git a/src/layui/layui/MarkerBrowserConfigPage.ui b/src/layui/layui/MarkerBrowserConfigPage.ui index 227942cc4..9417066d1 100644 --- a/src/layui/layui/MarkerBrowserConfigPage.ui +++ b/src/layui/layui/MarkerBrowserConfigPage.ui @@ -144,6 +144,11 @@ 1 + + cbx_context + cbx_window + le_max_markers +
diff --git a/src/layui/layui/MarkerBrowserConfigPage2.ui b/src/layui/layui/MarkerBrowserConfigPage2.ui index 94b14426c..47a49477d 100644 --- a/src/layui/layui/MarkerBrowserConfigPage2.ui +++ b/src/layui/layui/MarkerBrowserConfigPage2.ui @@ -1,7 +1,8 @@ - + + MarkerBrowserConfigPage2 - - + + 0 0 @@ -9,57 +10,57 @@ 174 - + Marker Database Browser - - - 9 - - + + 6 + + 9 + - - + + Marker Appearance - - + + 9 - + 6 - - - + + + With halo - + true - - + + - - + + - - - + + + pixel - + - + Qt::Horizontal - + 71 31 @@ -67,37 +68,35 @@ - - - - - 0 - 0 + + + + 0 0 - + pixel - - - + + + Line width - + - + Qt::Horizontal - + QSizePolicy::Fixed - + 41 31 @@ -105,40 +104,40 @@ - - - + + + Vertex size - - - + + + The color in which the markers are drawn - + - - - + + + Marker color - - - + + + Stipple - - - + + + @@ -148,7 +147,7 @@ - + lay::DitherPatternSelectionButton @@ -161,6 +160,13 @@
layWidgets.h
+ + color_pb + lw_le + stipple_pb + vs_le + halo_cb +
diff --git a/src/layui/layui/NetlistBrowserConfigPage.ui b/src/layui/layui/NetlistBrowserConfigPage.ui index c24ae94ae..25c9219d7 100644 --- a/src/layui/layui/NetlistBrowserConfigPage.ui +++ b/src/layui/layui/NetlistBrowserConfigPage.ui @@ -138,6 +138,11 @@ + + cbx_window + le_window + le_max_markers +
diff --git a/src/layui/layui/NetlistBrowserConfigPage2.ui b/src/layui/layui/NetlistBrowserConfigPage2.ui index 9139b7fa9..9a5093e33 100644 --- a/src/layui/layui/NetlistBrowserConfigPage2.ui +++ b/src/layui/layui/NetlistBrowserConfigPage2.ui @@ -6,7 +6,7 @@ 0 0 - 649 + 667 351 @@ -529,6 +529,24 @@ Non-net objects are drawn using the highlight color.
layWidgets.h
+ + stipple_pb + lw_le + halo_cb + vs_le + color_pb + brightness_cb + brightness_sb + cycle_colors_cb + cc0 + cc1 + cc5 + cc6 + cc3 + cc7 + cc4 + cc2 + diff --git a/src/layui/layui/UserPropertiesForm.ui b/src/layui/layui/UserPropertiesForm.ui index 8faf01105..983221f8d 100644 --- a/src/layui/layui/UserPropertiesForm.ui +++ b/src/layui/layui/UserPropertiesForm.ui @@ -49,7 +49,7 @@ - 0 + 2 @@ -234,7 +234,13 @@ - buttonBox + mode_tab + prop_list + add_pb + remove_pb + edit_pb + text_edit + meta_info_list From 20a3dbeaf8e3bfc688e078b9e2b223150053a559 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Sep 2025 18:54:00 +0200 Subject: [PATCH 46/52] Some refactoring (generalizing edt:: functions, basing move tool on lay::EditorService and support for 'tab' key (calls 'move by') --- src/edt/edt/edt.pro | 2 - src/edt/edt/edtConfig.cc | 5 -- src/edt/edt/edtConfig.h | 2 - src/edt/edt/edtDialogs.cc | 2 +- src/edt/edt/edtEditorOptionsPages.cc | 2 +- src/edt/edt/edtInstService.cc | 6 +- src/edt/edt/edtMainService.h | 2 +- src/edt/edt/edtMoveTrackerService.cc | 2 +- src/edt/edt/edtPartialService.cc | 36 +++++------ src/edt/edt/edtPartialService.h | 4 +- src/edt/edt/edtPathService.cc | 2 +- src/edt/edt/edtRecentConfigurationPage.cc | 8 +-- src/edt/edt/edtService.cc | 20 +++---- src/edt/edt/edtService.h | 14 ++++- src/edt/edt/edtShapeService.cc | 2 +- src/lay/lay/gsiDeclLayPlugin.cc | 12 ++-- src/laybasic/laybasic/layEditorOptionsPage.cc | 2 + src/laybasic/laybasic/layEditorServiceBase.cc | 7 +-- .../laybasic/layEditorUtils.cc} | 11 +++- .../laybasic/layEditorUtils.h} | 41 ++++++------- src/laybasic/laybasic/layLayoutViewBase.cc | 2 +- src/laybasic/laybasic/layMove.cc | 59 +++++++------------ src/laybasic/laybasic/layMove.h | 17 ++---- src/laybasic/laybasic/laybasic.pro | 2 + src/layui/layui/layLayoutViewFunctions.cc | 2 +- 25 files changed, 122 insertions(+), 142 deletions(-) rename src/{edt/edt/edtUtils.cc => laybasic/laybasic/layEditorUtils.cc} (99%) rename src/{edt/edt/edtUtils.h => laybasic/laybasic/layEditorUtils.h} (81%) diff --git a/src/edt/edt/edt.pro b/src/edt/edt/edt.pro index 886028431..b46609432 100644 --- a/src/edt/edt/edt.pro +++ b/src/edt/edt/edt.pro @@ -80,7 +80,6 @@ HEADERS += \ edtPartialService.h \ edtPlugin.h \ edtService.h \ - edtUtils.h \ edtCommon.h \ edtDistribute.h \ @@ -91,7 +90,6 @@ SOURCES += \ edtPartialService.cc \ edtPlugin.cc \ edtService.cc \ - edtUtils.cc \ gsiDeclEdt.cc \ edtDistribute.cc \ diff --git a/src/edt/edt/edtConfig.cc b/src/edt/edt/edtConfig.cc index 9de28292d..255b9ccbd 100644 --- a/src/edt/edt/edtConfig.cc +++ b/src/edt/edt/edtConfig.cc @@ -28,11 +28,6 @@ namespace edt { -int snap_range_pixels () -{ - return 8; // TODO: make variable -} - // ----------------------------------------------------------------------------- std::string cfg_edit_grid ("edit-grid"); diff --git a/src/edt/edt/edtConfig.h b/src/edt/edt/edtConfig.h index fd8d0bd8f..97964a9e0 100644 --- a/src/edt/edt/edtConfig.h +++ b/src/edt/edt/edtConfig.h @@ -118,8 +118,6 @@ struct EDT_PUBLIC VAlignConverter void from_string (const std::string &s, db::VAlign &a); }; -int EDT_PUBLIC snap_range_pixels (); - } #endif diff --git a/src/edt/edt/edtDialogs.cc b/src/edt/edt/edtDialogs.cc index b31315e86..120a7495f 100644 --- a/src/edt/edt/edtDialogs.cc +++ b/src/edt/edt/edtDialogs.cc @@ -26,7 +26,7 @@ #include "dbLayout.h" #include "edtDialogs.h" -#include "edtUtils.h" +#include "layEditorUtils.h" #include "layObjectInstPath.h" #include "layCellView.h" #include "layLayoutViewBase.h" diff --git a/src/edt/edt/edtEditorOptionsPages.cc b/src/edt/edt/edtEditorOptionsPages.cc index 9db9db7c6..5ed1ac75a 100644 --- a/src/edt/edt/edtEditorOptionsPages.cc +++ b/src/edt/edt/edtEditorOptionsPages.cc @@ -725,7 +725,7 @@ EditorOptionsInstPCellParam::apply (lay::Dispatcher *root) if (pc.first) { const db::PCellDeclaration *pc_decl = layout->pcell_declaration (pc.second); if (pc_decl) { - param = pcell_parameters_to_string (pc_decl->named_parameters (mp_pcell_parameters->get_parameters (&ok))); + param = lay::pcell_parameters_to_string (pc_decl->named_parameters (mp_pcell_parameters->get_parameters (&ok))); } } } diff --git a/src/edt/edt/edtInstService.cc b/src/edt/edt/edtInstService.cc index 765a8513b..3e92d981a 100644 --- a/src/edt/edt/edtInstService.cc +++ b/src/edt/edt/edtInstService.cc @@ -197,7 +197,7 @@ InstService::sync_to_config () dispatcher ()->config_set (cfg_edit_inst_lib_name, m_lib_name); dispatcher ()->config_set (cfg_edit_inst_cell_name, m_cell_or_pcell_name); if (m_is_pcell) { - dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters)); + dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, lay::pcell_parameters_to_string (m_pcell_parameters)); } else { dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ()); } @@ -531,7 +531,7 @@ InstService::configure (const std::string &name, const std::string &value) if (name == cfg_edit_inst_pcell_parameters) { - std::map pcp = pcell_parameters_from_string (value); + std::map pcp = lay::pcell_parameters_from_string (value); if (pcp != m_pcell_parameters) { m_pcell_parameters = pcp; @@ -772,7 +772,7 @@ InstService::config_finalize () // TODO: it's somewhat questionable to do this inside "config_finalize" as this method is supposed // to reflect changes rather than induce some. if (m_is_pcell) { - dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, pcell_parameters_to_string (m_pcell_parameters)); + dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, lay::pcell_parameters_to_string (m_pcell_parameters)); } else { dispatcher ()->config_set (cfg_edit_inst_pcell_parameters, std::string ()); } diff --git a/src/edt/edt/edtMainService.h b/src/edt/edt/edtMainService.h index c21e94d7c..d8fa67136 100644 --- a/src/edt/edt/edtMainService.h +++ b/src/edt/edt/edtMainService.h @@ -29,10 +29,10 @@ #include "layPlugin.h" #include "layViewObject.h" #include "layMarker.h" +#include "layEditorUtils.h" #include "dbLayout.h" #include "dbShape.h" #include "dbClipboard.h" -#include "edtUtils.h" #include #include diff --git a/src/edt/edt/edtMoveTrackerService.cc b/src/edt/edt/edtMoveTrackerService.cc index 1798ece0d..32a7e2cfa 100644 --- a/src/edt/edt/edtMoveTrackerService.cc +++ b/src/edt/edt/edtMoveTrackerService.cc @@ -61,7 +61,7 @@ MoveTrackerService::issue_edit_events () call_editor_hooks (m_editor_hooks, &edt::EditorHooks::begin_edits); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); std::vector services = view ()->get_plugins (); std::vector sel; diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index fc04e4a08..0bad58cb0 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1213,7 +1213,7 @@ PartialService::timeout () partial_objects::const_iterator r = transient_selection.begin (); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const lay::CellView &cv = view ()->cellview (r->first.cv_index ()); @@ -1468,7 +1468,7 @@ PartialService::issue_editor_hook_calls (const tl::weak_collection &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points) +PartialService::modify_shape (lay::TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points) { tl_assert (shape_in.shapes () != 0); db::Shape shape = shape_in; @@ -1642,7 +1642,7 @@ void PartialService::transform_selection (const db::DTrans &move_trans) { // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); // since a shape reference may become invalid while moving it and // because it creates ambiguities, we treat each shape separately: @@ -1798,7 +1798,7 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo set_cursor (lay::Cursor::size_all); - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); // drag the vertex or edge/segment if (is_single_point_selection () || is_single_edge_selection ()) { @@ -1862,7 +1862,7 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo if (mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); m_p2 = p; mp_box->set_points (m_p1, m_p2); @@ -1922,7 +1922,7 @@ PartialService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bo } else if (! mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); if (m_selection.empty ()) { @@ -2015,7 +2015,7 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo if (m_dragging) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); if (m_current != m_start) { @@ -2059,7 +2059,7 @@ PartialService::mouse_click_event (const db::DPoint &p, unsigned int buttons, bo view ()->clear_selection (); m_selection = selection; - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); lay::Editable::SelectionMode mode = lay::Editable::Replace; bool shift = ((buttons & lay::ShiftButton) != 0); @@ -2192,7 +2192,7 @@ PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int butt if ((buttons & lay::LeftButton) != 0 && prio) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); close_editor_hooks (false); @@ -2215,7 +2215,7 @@ PartialService::mouse_double_click_event (const db::DPoint &p, unsigned int butt db::DPoint new_point_d = snap (p); // build the transformation variants cache - TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/); + lay::TransformationVariants tv (view (), true /*per cv and layer*/, false /*per cv*/); const std::vector *tv_list = tv.per_cv_and_layer (r->first.cv_index (), r->first.layer ()); if (tv_list && ! tv_list->empty ()) { @@ -2304,7 +2304,7 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, if (prio && mp_box) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); ui ()->ungrab_mouse (this); @@ -2402,7 +2402,7 @@ PartialService::snap_marker_to_grid (const db::DVector &v, bool &snapped) const db::DVector snapped_to (1.0, 1.0); db::DVector vv = lay::snap_angle (v, move_ac (), &snapped_to); - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); for (auto r = m_selection.begin (); r != m_selection.end (); ++r) { @@ -2548,7 +2548,7 @@ PartialService::selection_bbox () { // build the transformation variants cache // TODO: this is done multiple times - once for each service! - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const db::DCplxTrans &vp = view ()->viewport ().trans (); lay::TextInfo text_info (view ()); @@ -2938,7 +2938,7 @@ PartialService::single_selected_point () const // build the transformation variants cache and // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ()); const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ()); @@ -2960,7 +2960,7 @@ PartialService::single_selected_edge () const // build the transformation variants cache and // use only the first one of the explicit transformations // TODO: clarify how this can be implemented in a more generic form or leave it thus. - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (m_selection.begin ()->first.cv_index (), m_selection.begin ()->first.layer ()); const lay::CellView &cv = view ()->cellview (m_selection.begin ()->first.cv_index ()); @@ -3042,7 +3042,7 @@ PartialService::do_selection_to_view () if (! m_selection.empty ()) { // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); for (partial_objects::const_iterator r = m_selection.begin (); r != m_selection.end (); ++r) { @@ -3418,7 +3418,7 @@ PartialService::handle_guiding_shape_changes () // Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed // guiding shapes. We must not access s->shape after that. - if (! get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) { + if (! lay::get_parameters_from_pcell_and_guiding_shapes (layout, s->first.cell_index (), parameters_for_pcell)) { return false; } diff --git a/src/edt/edt/edtPartialService.h b/src/edt/edt/edtPartialService.h index 03cdb5bc0..594c73108 100644 --- a/src/edt/edt/edtPartialService.h +++ b/src/edt/edt/edtPartialService.h @@ -30,9 +30,9 @@ #include "layViewObject.h" #include "layRubberBox.h" #include "laySnap.h" +#include "layEditorUtils.h" #include "tlAssert.h" #include "tlDeferredExecution.h" -#include "edtUtils.h" #include "edtConfig.h" #include "edtEditorHooks.h" @@ -400,7 +400,7 @@ private: db::DEdge single_selected_edge () const; bool handle_guiding_shape_changes (); void transform_selection (const db::DTrans &move_trans); - db::Shape modify_shape (TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points); + db::Shape modify_shape (lay::TransformationVariants &tv, const db::Shape &shape_in, const lay::ObjectInstPath &path, const std::set &sel, const db::DTrans &move_trans, std::map &new_edges, std::map &new_points); void open_editor_hooks (); void close_editor_hooks (bool commit); diff --git a/src/edt/edt/edtPathService.cc b/src/edt/edt/edtPathService.cc index f5a39eee2..6879dec56 100644 --- a/src/edt/edt/edtPathService.cc +++ b/src/edt/edt/edtPathService.cc @@ -325,7 +325,7 @@ db::Instance PathService::make_via (const db::SelectedViaDefinition &via_def, double w_bottom, double h_bottom, double w_top, double h_top, const db::DPoint &via_pos) { if (! via_def.via_type.cut.is_null ()) { - edt::set_or_request_current_layer (view (), via_def.via_type.cut, cv_index (), false /*don't make current*/); + lay::set_or_request_current_layer (view (), via_def.via_type.cut, cv_index (), false /*don't make current*/); } std::map params; diff --git a/src/edt/edt/edtRecentConfigurationPage.cc b/src/edt/edt/edtRecentConfigurationPage.cc index e599cf2d4..c2df2e655 100644 --- a/src/edt/edt/edtRecentConfigurationPage.cc +++ b/src/edt/edt/edtRecentConfigurationPage.cc @@ -23,11 +23,11 @@ #if defined(HAVE_QT) #include "edtRecentConfigurationPage.h" -#include "edtUtils.h" #include "layDispatcher.h" #include "layLayoutViewBase.h" #include "layLayerTreeModel.h" #include "layBusy.h" +#include "layEditorUtils.h" #include "dbLibraryManager.h" #include "dbLibrary.h" #include "tlLog.h" @@ -267,7 +267,7 @@ RecentConfigurationPage::render_to (QTreeWidgetItem *item, int column, const std std::map pcp; for (std::list::const_iterator c = m_cfg.begin (); c != m_cfg.end (); ++c, ++pcp_column) { if (c->rendering == RecentConfigurationPage::PCellParameters) { - pcp = pcell_parameters_from_string (values [pcp_column]); + pcp = lay::pcell_parameters_from_string (values [pcp_column]); break; } } @@ -296,7 +296,7 @@ RecentConfigurationPage::render_to (QTreeWidgetItem *item, int column, const std { std::map pcp; try { - pcp = pcell_parameters_from_string (values [column]); + pcp = lay::pcell_parameters_from_string (values [column]); } catch (tl::Exception &ex) { tl::error << tl::to_string (tr ("Configuration error (PCellParameters): ")) << ex.msg (); } @@ -384,7 +384,7 @@ RecentConfigurationPage::item_clicked (QTreeWidgetItem *item) ex.read (cv_index); } - edt::set_or_request_current_layer (view (), lp, cv_index); + lay::set_or_request_current_layer (view (), lp, cv_index); } else { dispatcher ()->config_set (c->cfg_name, v); diff --git a/src/edt/edt/edtService.cc b/src/edt/edt/edtService.cc index d039c7e33..23377fe4f 100644 --- a/src/edt/edt/edtService.cc +++ b/src/edt/edt/edtService.cc @@ -283,7 +283,7 @@ Service::snap (const db::DPoint &p, const db::DPoint &plast, bool connect) const lay::PointSnapToObjectResult Service::snap2_details (const db::DPoint &p) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); } @@ -296,7 +296,7 @@ Service::snap2 (const db::DPoint &p) const db::DPoint Service::snap2 (const db::DPoint &p, const db::DPoint &plast, bool connect) const { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); return lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac () : move_ac (), snap_range).snapped_point; } @@ -593,7 +593,7 @@ Service::selection_bbox () { // build the transformation variants cache // TODO: this is done multiple times - once for each service! - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const db::DCplxTrans &vp = view ()->viewport ().trans (); lay::TextInfo text_info (view ()); @@ -687,7 +687,7 @@ Service::transform (const db::DCplxTrans &trans, const std::vectoris_editable () && prio && (buttons & lay::RightButton) != 0 && m_editing) { - m_alt_ac = ac_from_buttons (buttons); + m_alt_ac = lay::ac_from_buttons (buttons); do_mouse_transform (p, db::DFTrans (db::DFTrans::r90)); m_alt_ac = lay::AC_Global; return true; @@ -1754,7 +1754,7 @@ Service::do_selection_to_view () m_markers.reserve (selection_size ()); // build the transformation variants cache - TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); // prepare a default transformation for empty variants std::vector empty_tv; @@ -1932,7 +1932,7 @@ Service::handle_guiding_shape_changes (const lay::ObjectInstPath &obj, bool comm // Hint: get_parameters_from_pcell_and_guiding_shapes invalidates the shapes because it resets the changed // guiding shapes. We must not access s->shape after that. - if (! get_parameters_from_pcell_and_guiding_shapes (layout, obj.cell_index (), parameters_for_pcell)) { + if (! lay::get_parameters_from_pcell_and_guiding_shapes (layout, obj.cell_index (), parameters_for_pcell)) { return std::make_pair (false, lay::ObjectInstPath ()); } diff --git a/src/edt/edt/edtService.h b/src/edt/edt/edtService.h index fe61999ef..a791187e5 100644 --- a/src/edt/edt/edtService.h +++ b/src/edt/edt/edtService.h @@ -33,10 +33,10 @@ #include "laySnap.h" #include "layObjectInstPath.h" #include "layTextInfo.h" +#include "layEditorUtils.h" #include "tlColor.h" #include "dbLayout.h" #include "dbShape.h" -#include "edtUtils.h" #include "edtConfig.h" #include "tlAssert.h" #include "tlException.h" @@ -55,6 +55,18 @@ class PluginDeclarationBase; // ------------------------------------------------------------- +/** + * @brief A helper class that identifies clipboard data for edt:: + */ +class EDT_PUBLIC ClipboardData + : public db::ClipboardData +{ +public: + ClipboardData () { } +}; + +// ------------------------------------------------------------- + /** * @brief A utility class to implement a selection iterator across all editor services */ diff --git a/src/edt/edt/edtShapeService.cc b/src/edt/edt/edtShapeService.cc index 28dc9a461..4664f777b 100644 --- a/src/edt/edt/edtShapeService.cc +++ b/src/edt/edt/edtShapeService.cc @@ -167,7 +167,7 @@ ShapeEditService::change_edit_layer (const db::LayerProperties &lp) } m_layer = (unsigned int) layer; - edt::set_or_request_current_layer (view (), lp, m_cv_index); + lay::set_or_request_current_layer (view (), lp, m_cv_index); if (editing ()) { close_editor_hooks (false); diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index 88050e125..b5f7c00ce 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -27,8 +27,8 @@ #include "gsiEnums.h" #include "layEditorOptionsPages.h" #include "layCursor.h" +#include "layEditorUtils.h" #include "edtConfig.h" -#include "edtUtils.h" namespace gsi { @@ -290,7 +290,7 @@ PluginImpl::snap_delta (const db::DVector &v, bool connect, lay::angle_constrain db::DPoint PluginImpl::snap2 (const db::DPoint &p, bool visualize) { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, snap_range); if (visualize) { mouse_cursor_from_snap_details (details); @@ -301,7 +301,7 @@ PluginImpl::snap2 (const db::DPoint &p, bool visualize) db::DPoint PluginImpl::snap2_from_to (const db::DPoint &p, const db::DPoint &plast, bool connect, lay::angle_constraint_type ac, bool visualize) { - double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (edt::snap_range_pixels ()); + double snap_range = ui ()->mouse_event_trans ().inverted ().ctrans (lay::snap_range_pixels ()); auto details = lay::obj_snap (m_snap_to_objects ? view () : 0, plast, p, m_edit_grid == db::DVector () ? m_global_grid : m_edit_grid, connect ? connect_ac (ac) : move_ac (ac), snap_range); if (visualize) { mouse_cursor_from_snap_details (details); @@ -575,7 +575,7 @@ PluginImpl::add_mouse_cursor_point (const db::Point &p, int cv_index, const db:: return; } - edt::TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); if (! tv_list || tv_list->empty ()) { return; @@ -603,7 +603,7 @@ PluginImpl::add_edge_marker_edge (const db::Edge &p, int cv_index, const db::Lay return; } - edt::TransformationVariants tv (view ()); + lay::TransformationVariants tv (view ()); const std::vector *tv_list = tv.per_cv_and_layer (cv_index, (unsigned int) layer); if (! tv_list || tv_list->empty ()) { return; @@ -861,7 +861,7 @@ Class decl_Plugin (decl_PluginBase, "lay", "Plugin", "\n" "This method has been added in version 0.30.4." ) + - method ("ac_from_buttons", &edt::ac_from_buttons, gsi::arg ("buttons"), + method ("ac_from_buttons", &lay::ac_from_buttons, gsi::arg ("buttons"), "@brief Creates an angle constraint from a button combination\n" "This method provides the angle constraints implied by a specific modifier combination, i.e. " "'Shift' will render ortho snapping. Use this function to generate angle constraints following " diff --git a/src/laybasic/laybasic/layEditorOptionsPage.cc b/src/laybasic/laybasic/layEditorOptionsPage.cc index eb2dd7435..0efde1109 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.cc +++ b/src/laybasic/laybasic/layEditorOptionsPage.cc @@ -123,6 +123,8 @@ EditorOptionsPage::show () } else { return mp_owner->exec_modal (this) ? 1 : 0; } + } else { + return -1; } } diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index 784889dac..21e162963 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -379,10 +379,7 @@ bool EditorServiceBase::key_event (unsigned int key, unsigned int buttons) { if (is_active () && key == Qt::Key_Tab && buttons == 0) { - EditorOptionsPage *fp = focus_page (); - if (fp) { - focus_page_open (fp); - } + focus_page_open (focus_page ()); return true; } else { return false; @@ -392,7 +389,7 @@ EditorServiceBase::key_event (unsigned int key, unsigned int buttons) int EditorServiceBase::focus_page_open (EditorOptionsPage *fp) { - return fp->show (); + return fp ? fp->show () : 0; } void diff --git a/src/edt/edt/edtUtils.cc b/src/laybasic/laybasic/layEditorUtils.cc similarity index 99% rename from src/edt/edt/edtUtils.cc rename to src/laybasic/laybasic/layEditorUtils.cc index f4f8d12fa..377fe8f86 100644 --- a/src/edt/edt/edtUtils.cc +++ b/src/laybasic/laybasic/layEditorUtils.cc @@ -25,8 +25,7 @@ #include "dbLayout.h" #include "dbLibrary.h" -#include "edtUtils.h" -#include "edtService.h" +#include "layEditorUtils.h" #include "layCellView.h" #include "layLayoutViewBase.h" @@ -38,10 +37,16 @@ # include #endif -namespace edt { +namespace lay +{ // ------------------------------------------------------------- +int snap_range_pixels () +{ + return 8; // TODO: make variable +} + // Convert buttons to an angle constraint lay::angle_constraint_type ac_from_buttons (unsigned int buttons) diff --git a/src/edt/edt/edtUtils.h b/src/laybasic/laybasic/layEditorUtils.h similarity index 81% rename from src/edt/edt/edtUtils.h rename to src/laybasic/laybasic/layEditorUtils.h index 8c15696a3..50cef1179 100644 --- a/src/edt/edt/edtUtils.h +++ b/src/laybasic/laybasic/layEditorUtils.h @@ -21,15 +21,15 @@ */ -#ifndef HDR_edtUtils -#define HDR_edtUtils +#ifndef HDR_layEditorUtils +#define HDR_layEditorUtils #include #include #include #include -#include "edtCommon.h" +#include "laybasicCommon.h" #include "layObjectInstPath.h" #include "laySnap.h" @@ -41,33 +41,34 @@ namespace lay { - class LayoutViewBase; - class Dispatcher; -} -namespace edt { - -class Service; +class LayoutViewBase; +class Dispatcher; // ------------------------------------------------------------- +/** + * @brief Gets the snap range in pixels + */ +LAYBASIC_PUBLIC int snap_range_pixels (); + /** * @brief Convert a button flag set to an angle constraint * * This implements the standard modifiers for angle constraints - i.e. * ortho for "Shift". */ -EDT_PUBLIC lay::angle_constraint_type ac_from_buttons (unsigned int buttons); +LAYBASIC_PUBLIC lay::angle_constraint_type ac_from_buttons (unsigned int buttons); /** * @brief Serializes PCell parameters to a string */ -EDT_PUBLIC std::string pcell_parameters_to_string (const std::map ¶meters); +LAYBASIC_PUBLIC std::string pcell_parameters_to_string (const std::map ¶meters); /** * @brief Deserializes PCell parameters from a string */ -EDT_PUBLIC std::map pcell_parameters_from_string (const std::string &s); +LAYBASIC_PUBLIC std::map pcell_parameters_from_string (const std::string &s); /** * @brief Fetch PCell parameters from a cell and merge the guiding shapes into them @@ -77,30 +78,22 @@ EDT_PUBLIC std::map pcell_parameters_from_string (cons * @param parameters_for_pcell Will receive the parameters * @return true, if the cell is a PCell and parameters have been fetched */ -EDT_PUBLIC bool +LAYBASIC_PUBLIC +bool get_parameters_from_pcell_and_guiding_shapes (db::Layout *layout, db::cell_index_type cell_index, db::pcell_parameters_type ¶meters_for_pcell); /** * @brief Request to make the given layer the current one (asks whether to create the layer if needed) */ +LAYBASIC_PUBLIC bool set_or_request_current_layer (lay::LayoutViewBase *view, const db::LayerProperties &lp, unsigned int cv_index, bool make_current = true); -/** - * @brief A helper class that identifies clipboard data for edt:: - */ -class EDT_PUBLIC ClipboardData - : public db::ClipboardData -{ -public: - ClipboardData () { } -}; - /** * @brief A cache for the transformation variants for a certain layer and cell view index for a lay::LayoutView */ -class EDT_PUBLIC TransformationVariants +class LAYBASIC_PUBLIC TransformationVariants { public: TransformationVariants (const lay::LayoutViewBase *view, bool per_cv_and_layer = true, bool per_cv = true); diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 7f399ff29..2a39c5f7a 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -5506,7 +5506,7 @@ LayoutViewBase::paste_interactive (bool transient_mode) // operations. trans->close (); - if (mp_move_service && mp_move_service->begin_move (trans.release (), transient_mode)) { + if (mp_move_service && mp_move_service->start_move (trans.release (), transient_mode)) { switch_mode (-1); // move mode } } diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 3c15c3dd3..920643689 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -20,11 +20,10 @@ */ - - #include "layMove.h" #include "layEditable.h" #include "layLayoutViewBase.h" +#include "layEditorUtils.h" #include "laySelector.h" #include "laybasicConfig.h" @@ -35,11 +34,9 @@ namespace lay // MoveService implementation MoveService::MoveService (lay::LayoutViewBase *view) - : lay::ViewService (view->canvas ()), - lay::Plugin (view), + : lay::EditorServiceBase (view), m_dragging (false), m_dragging_transient (false), - m_active (false), mp_editables (view), mp_view (view), m_global_grid (0.001) @@ -52,55 +49,33 @@ MoveService::~MoveService () drag_cancel (); } -void -MoveService::activated () -{ - m_active = true; -} - void MoveService::deactivated () { + EditorServiceBase::deactivated (); m_shift = db::DPoint (); - m_active = false; - mp_view->clear_transient_selection (); + mp_editables->clear_transient_selection (); drag_cancel (); } -lay::angle_constraint_type -ac_from_buttons (unsigned int buttons) -{ - if ((buttons & lay::ShiftButton) != 0) { - if ((buttons & lay::ControlButton) != 0) { - return lay::AC_Any; - } else { - return lay::AC_Ortho; - } - } else { - if ((buttons & lay::ControlButton) != 0) { - return lay::AC_Diagonal; - } else { - return lay::AC_Global; - } - } -} - bool MoveService::configure (const std::string &name, const std::string &value) { + if (lay::EditorServiceBase::configure (name, value)) { + return true; + } + if (name == cfg_grid) { tl::from_string (value, m_global_grid); } + return false; // not taken } bool MoveService::key_event (unsigned int key, unsigned int buttons) { - if (m_active && key == Qt::Key_Tab && buttons == 0) { - if (dispatcher ()) { - dispatcher ()->menu_activated ("cm_sel_move"); - } + if (lay::EditorServiceBase::key_event (key, buttons)) { return true; } @@ -145,6 +120,16 @@ MoveService::key_event (unsigned int key, unsigned int buttons) } } +int +MoveService::focus_page_open (EditorOptionsPage * /*fp*/) +{ + // This method is called on "Tab" by "key_event". "fp" is null as we don't have a focus page registered. + if (is_active () && dispatcher ()) { + dispatcher ()->menu_activated ("cm_sel_move"); + } + return 0; +} + bool MoveService::mouse_move_event (const db::DPoint &p, unsigned int buttons, bool prio) { @@ -256,7 +241,7 @@ MoveService::mouse_press_event (const db::DPoint &p, unsigned int buttons, bool } bool -MoveService::begin_move (db::Transaction *transaction, bool transient_selection) +MoveService::start_move (db::Transaction *transaction, bool transient_selection) { if (m_dragging) { return false; @@ -324,7 +309,7 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_ ui ()->hover_reset (); - mp_view->clear_transient_selection (); + mp_editables->clear_transient_selection (); m_dragging = true; m_dragging_transient = drag_transient; diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 485c12aaa..b721516b3 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -24,34 +24,28 @@ #define HDR_layMove #include "laybasicCommon.h" -#include "layViewObject.h" -#include "layPlugin.h" +#include "layEditorServiceBase.h" #include "dbManager.h" #include namespace lay { -class Editables; class LayoutViewBase; class LAYBASIC_PUBLIC MoveService : - public lay::ViewService, public lay::Plugin + public lay::EditorServiceBase { public: MoveService (lay::LayoutViewBase *view); ~MoveService (); + bool start_move (db::Transaction *transaction = 0, bool transient_selection = false); + bool configure (const std::string &name, const std::string &value); - bool begin_move (db::Transaction *transaction = 0, bool transient_selection = false); void finish (); void cancel (); - lay::ViewService *view_service_interface () - { - return this; - } - private: virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); virtual bool mouse_click_event (const db::DPoint &p, unsigned int buttons, bool prio); @@ -61,14 +55,13 @@ private: virtual bool wheel_event (int delta, bool horizontal, const db::DPoint &p, unsigned int buttons, bool prio); virtual bool key_event (unsigned int key, unsigned int buttons); virtual void drag_cancel (); - virtual void activated (); virtual void deactivated (); + int focus_page_open (EditorOptionsPage *fp); bool handle_click (const db::DPoint &p, unsigned int buttons, bool drag_transient, db::Transaction *transaction); bool m_dragging; bool m_dragging_transient; - bool m_active; lay::Editables *mp_editables; lay::LayoutViewBase *mp_view; double m_global_grid; diff --git a/src/laybasic/laybasic/laybasic.pro b/src/laybasic/laybasic/laybasic.pro index 52ce019fc..f6998f621 100644 --- a/src/laybasic/laybasic/laybasic.pro +++ b/src/laybasic/laybasic/laybasic.pro @@ -37,6 +37,7 @@ SOURCES += \ layAbstractMenu.cc \ layEditorOptionsPage.cc \ layEditorOptionsPages.cc \ + layEditorUtils.cc \ layLayoutViewConfig.cc \ layMargin.cc \ laybasicForceLink.cc \ @@ -91,6 +92,7 @@ SOURCES += \ HEADERS += \ layEditorOptionsPage.h \ layEditorOptionsPages.h \ + layEditorUtils.h \ layMargin.h \ laybasicConfig.h \ laybasicForceLink.h \ diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index a0b04f0c8..6f1417813 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -1543,7 +1543,7 @@ void LayoutViewFunctions::cm_sel_move_interactive () { view ()->cancel_edits (); - if (view ()->move_service ()->begin_move ()) { + if (view ()->move_service ()->start_move ()) { view ()->switch_mode (-1); // move mode } } From e2b5c29131de672a2db6733e7b9b5235d3496602 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Sep 2025 19:30:07 +0200 Subject: [PATCH 47/52] Some refactoring, bug fix --- src/lay/lay/gsiDeclLayPlugin.cc | 11 ++++++----- src/lay/lay/gsiDeclLayPlugin.h | 2 +- src/lay/lay/macro_templates/drag_box_sample.lym | 7 +++++-- .../lay/macro_templates/drag_box_sample_python.lym | 4 +++- src/laybasic/laybasic/layEditorServiceBase.cc | 7 ++++--- src/laybasic/laybasic/layEditorServiceBase.h | 14 +++++++------- src/laybasic/laybasic/layMove.cc | 2 +- src/laybasic/laybasic/layMove.h | 2 +- 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/lay/lay/gsiDeclLayPlugin.cc b/src/lay/lay/gsiDeclLayPlugin.cc index b5f7c00ce..418a85bc9 100644 --- a/src/lay/lay/gsiDeclLayPlugin.cc +++ b/src/lay/lay/gsiDeclLayPlugin.cc @@ -646,12 +646,12 @@ PluginImpl::tracking_position () const } } -int PluginImpl::focus_page_open(lay::EditorOptionsPage *fp) +int PluginImpl::focus_page_open () { if (f_focus_page_open.can_issue ()) { - return f_focus_page_open.issue (&lay::EditorServiceBase::focus_page_open, fp); + return f_focus_page_open.issue (&lay::EditorServiceBase::focus_page_open); } else { - return lay::EditorServiceBase::focus_page_open (fp); + return lay::EditorServiceBase::focus_page_open (); } } @@ -987,9 +987,10 @@ Class decl_Plugin (decl_PluginBase, "lay", "Plugin", "\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"), + callback ("focus_page_open", &gsi::PluginImpl::focus_page_open, &gsi::PluginImpl::f_focus_page_open, "@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 " + "The default implementation calls \\EditorOptionsPage#show on the focus page.\n" + "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" diff --git a/src/lay/lay/gsiDeclLayPlugin.h b/src/lay/lay/gsiDeclLayPlugin.h index 9fcd8a1bc..c1a442633 100644 --- a/src/lay/lay/gsiDeclLayPlugin.h +++ b/src/lay/lay/gsiDeclLayPlugin.h @@ -95,7 +95,7 @@ public: db::DPoint tracking_position_test () const; virtual db::DPoint tracking_position () const; - virtual int focus_page_open (lay::EditorOptionsPage *fp); + virtual int focus_page_open (); virtual lay::ViewService *view_service_interface () { diff --git a/src/lay/lay/macro_templates/drag_box_sample.lym b/src/lay/lay/macro_templates/drag_box_sample.lym index fbcea2c12..454c0d8d3 100644 --- a/src/lay/lay/macro_templates/drag_box_sample.lym +++ b/src/lay/lay/macro_templates/drag_box_sample.lym @@ -291,8 +291,8 @@ class DragBoxPlugin < RBA::Plugin end # overloaded callback: the focus page is requested - def focus_page_open(fp) - + def focus_page_open + # stop unless dragging if !@marker return @@ -301,6 +301,7 @@ class DragBoxPlugin < RBA::Plugin # configure the focus page and show it: # the page will call the handler of "update_box" to commit # changes to the box + fp = self.focus_page fp.box = @box fp.pfix = @start_point fp.update_box = lambda { |box| self._update_box(box) } @@ -310,7 +311,9 @@ class DragBoxPlugin < RBA::Plugin # accepted: stop dragging now, we are done. self._finish end + return ret + end # overloaded callback: diff --git a/src/lay/lay/macro_templates/drag_box_sample_python.lym b/src/lay/lay/macro_templates/drag_box_sample_python.lym index 3eb35164b..7edd306f2 100644 --- a/src/lay/lay/macro_templates/drag_box_sample_python.lym +++ b/src/lay/lay/macro_templates/drag_box_sample_python.lym @@ -307,7 +307,7 @@ class DragBoxPlugin(pya.Plugin): self.box = box self._update_marker() - def focus_page_open(self, fp): + def focus_page_open(self): """ overloaded callback: the focus page is requested @@ -320,6 +320,7 @@ class DragBoxPlugin(pya.Plugin): # configure the focus page and show it: # the page will call the handler of "update_box" to commit # changes to the box + fp = self.focus_page() fp.box = self.box fp.pfix = self.start_point fp.update_box = self._update_box @@ -328,6 +329,7 @@ class DragBoxPlugin(pya.Plugin): if ret == 1: # accepted: stop dragging now, we are done. self._finish() + return ret def activated(self): diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index 21e162963..d6e9ef7ee 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -379,7 +379,7 @@ bool EditorServiceBase::key_event (unsigned int key, unsigned int buttons) { if (is_active () && key == Qt::Key_Tab && buttons == 0) { - focus_page_open (focus_page ()); + focus_page_open (); return true; } else { return false; @@ -387,8 +387,9 @@ EditorServiceBase::key_event (unsigned int key, unsigned int buttons) } int -EditorServiceBase::focus_page_open (EditorOptionsPage *fp) +EditorServiceBase::focus_page_open () { + auto fp = focus_page (); return fp ? fp->show () : 0; } @@ -414,7 +415,7 @@ EditorServiceBase::show_error (tl::Exception &ex) } void -EditorServiceBase::focus_page_enter () +EditorServiceBase::focus_page_open () { // .. nothing yet .. } diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 3fffcfcce..16cdd4838 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -265,6 +265,13 @@ public: // The default implementation does nothing } + /** + * @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 (); + #if defined(HAVE_QT) /** * @brief Gets the editor options pages associated with this plugin @@ -275,13 +282,6 @@ public: * @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: diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 920643689..a2a6ba96c 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -121,7 +121,7 @@ MoveService::key_event (unsigned int key, unsigned int buttons) } int -MoveService::focus_page_open (EditorOptionsPage * /*fp*/) +MoveService::focus_page_open () { // This method is called on "Tab" by "key_event". "fp" is null as we don't have a focus page registered. if (is_active () && dispatcher ()) { diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index b721516b3..a8cd8cdb9 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -56,7 +56,7 @@ private: virtual bool key_event (unsigned int key, unsigned int buttons); virtual void drag_cancel (); virtual void deactivated (); - int focus_page_open (EditorOptionsPage *fp); + int focus_page_open (); bool handle_click (const db::DPoint &p, unsigned int buttons, bool drag_transient, db::Transaction *transaction); From 0ddc07392b7fcbf9126530655163da3560b31179 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Sep 2025 21:16:05 +0200 Subject: [PATCH 48/52] [consider merging] suppress normalization of orientation on GSI DPolygon#to_itype and Polygon#to_dtype --- src/db/db/dbMeasureEval.cc | 2 +- src/db/db/dbPolygon.h | 58 ++++++++++++++++++++-------------- src/db/db/gsiDeclDbPolygon.cc | 16 +++++----- testdata/ruby/dbPolygonTest.rb | 23 ++++++++++++++ 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/src/db/db/dbMeasureEval.cc b/src/db/db/dbMeasureEval.cc index b85291022..b2a29c10b 100644 --- a/src/db/db/dbMeasureEval.cc +++ b/src/db/db/dbMeasureEval.cc @@ -511,7 +511,7 @@ MeasureNetEval::put_func (const tl::Variant &name, const tl::Variant &value) con MeasureNetEval::AreaAndPerimeter MeasureNetEval::compute_area_and_perimeter (int layer_index) const { - if (layer_index < 0 || layer_index >= (unsigned int) m_layers.size ()) { + if (layer_index < 0 || layer_index >= (int) m_layers.size ()) { return AreaAndPerimeter (); } diff --git a/src/db/db/dbPolygon.h b/src/db/db/dbPolygon.h index b56a36271..a75a41564 100644 --- a/src/db/db/dbPolygon.h +++ b/src/db/db/dbPolygon.h @@ -1512,16 +1512,17 @@ public: * @param tr The transformation to apply on assignment * @param compress True, if the contours shall be compressed * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - polygon (const db::polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false) + polygon (const db::polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); m_ctrs.resize (p.holes () + 1); - m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); for (unsigned int i = 0; i < m_ctrs.size () - 1; ++i) { - m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, normalize, remove_reflected); } } @@ -1533,16 +1534,16 @@ public: * @param remove_reflected True, if reflecting spikes shall be removed on compression */ template - explicit polygon (const db::polygon &p, bool compress = default_compression (), bool remove_reflected = false) + explicit polygon (const db::polygon &p, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { db::point_coord_converter tr; // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); m_ctrs.resize (p.holes () + 1); - m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); for (unsigned int i = 0; i < m_ctrs.size () - 1; ++i) { - m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [i + 1].assign (p.begin_hole (i), p.end_hole (i), tr, true, compress, normalize, remove_reflected); } } @@ -2072,11 +2073,12 @@ public: * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hull (I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [0].assign (start, end, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (start, end, false, compress, normalize, remove_reflected); m_bbox = m_ctrs [0].bbox (); } @@ -2089,14 +2091,15 @@ public: * so it is oriented properly. * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized * * @param start The start of the sequence of points for the contour * @param end The end of the sequence of points for the contour */ template - void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [0].assign (start, end, op, false, compress, true /*normalize*/, remove_reflected); + m_ctrs [0].assign (start, end, op, false, compress, normalize, remove_reflected); m_bbox = m_ctrs [0].bbox (); } @@ -2128,11 +2131,12 @@ public: * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hole (unsigned int h, I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void assign_hole (unsigned int h, I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [h + 1].assign (start, end, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [h + 1].assign (start, end, true, compress, normalize, remove_reflected); } /** @@ -2144,14 +2148,15 @@ public: * so it is oriented properly. * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized * * @param start The start of the sequence of points for the contour * @param end The end of the sequence of points for the contour */ template - void assign_hole (unsigned int h, I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hole (unsigned int h, I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_ctrs [h + 1].assign (start, end, op, true, compress, true /*normalize*/, remove_reflected); + m_ctrs [h + 1].assign (start, end, op, true, compress, normalize, remove_reflected); } /** @@ -2183,11 +2188,12 @@ public: * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void insert_hole (I start, I end, bool compress = default_compression (), bool remove_reflected = false) + void insert_hole (I start, I end, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - insert_hole (start, end, db::unit_trans (), compress, remove_reflected); + insert_hole (start, end, db::unit_trans (), compress, remove_reflected, normalize); } /** @@ -2204,13 +2210,14 @@ public: * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void insert_hole (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void insert_hole (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // add the hole contour_type &h = add_hole (); - h.assign (start, end, op, true, compress, true /*normalize*/, remove_reflected); + h.assign (start, end, op, true, compress, normalize, remove_reflected); } /** @@ -2637,13 +2644,14 @@ public: * @param tr The transformation to apply * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - simple_polygon (const db::simple_polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false) + simple_polygon (const db::simple_polygon &p, const T &tr, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); - m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); } /** @@ -2652,15 +2660,16 @@ public: * @param p The source polygon * @param compress true, if the sequence shall be compressed (colinear points removed) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - explicit simple_polygon (const db::simple_polygon &p, bool compress = default_compression (), bool remove_reflected = false) + explicit simple_polygon (const db::simple_polygon &p, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { db::point_coord_converter tr; // create an entry for the hull contour m_bbox = box_type (tr (p.box ().p1 ()), tr (p.box ().p2 ())); - m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (p.begin_hull (), p.end_hull (), tr, false, compress, normalize, remove_reflected); } /** @@ -2992,11 +3001,12 @@ public: * @param end The end of the sequence of points for the contour * @param compress true, if the sequence shall be compressed (colinear segments joined) * @param remove_reflected True, if reflecting spikes shall be removed on compression + * @param normalize If true, the orientation is normalized */ template - void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false) + void assign_hull (I start, I end, T op, bool compress = default_compression (), bool remove_reflected = false, bool normalize = true) { - m_hull.assign (start, end, op, false, compress, true /*normalize*/, remove_reflected); + m_hull.assign (start, end, op, false, compress, normalize, remove_reflected); m_bbox = m_hull.bbox (); } diff --git a/src/db/db/gsiDeclDbPolygon.cc b/src/db/db/gsiDeclDbPolygon.cc index a5cb6f994..850887318 100644 --- a/src/db/db/gsiDeclDbPolygon.cc +++ b/src/db/db/gsiDeclDbPolygon.cc @@ -868,12 +868,12 @@ static db::SimplePolygon transformed_icplx_sp (const db::SimplePolygon *p, const static db::SimplePolygon *spolygon_from_dspolygon (const db::DSimplePolygon &p) { - return new db::SimplePolygon (p, false); + return new db::SimplePolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::DSimplePolygon spolygon_to_dspolygon (const db::SimplePolygon *p, double dbu) { - return db::DSimplePolygon (*p * dbu, false); + return db::DSimplePolygon (*p, db::CplxTrans (dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } Class decl_SimplePolygon ("db", "SimplePolygon", @@ -1031,12 +1031,12 @@ Class decl_SimplePolygonWithProperties (decl_Si static db::DSimplePolygon *dspolygon_from_ispolygon (const db::SimplePolygon &p) { - return new db::DSimplePolygon (p, false); + return new db::DSimplePolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::SimplePolygon dspolygon_to_spolygon (const db::DSimplePolygon *p, double dbu) { - return db::SimplePolygon (*p * (1.0 / dbu), false); + return db::SimplePolygon (*p, db::VCplxTrans (1.0 / dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } static db::SimplePolygon transformed_vplx_sp (const db::DSimplePolygon *p, const db::VCplxTrans &t) @@ -2036,12 +2036,12 @@ static db::Polygon minkowski_sum_pc (const db::Polygon *p, const std::vector decl_PolygonWithProperties (decl_Polygon, "db", static db::DPolygon *dpolygon_from_ipolygon (const db::Polygon &p) { - return new db::DPolygon (p, false); + return new db::DPolygon (p, false, false /*don't remove reflected*/, false /*no normalize*/); } static db::Polygon dpolygon_to_polygon (const db::DPolygon *p, double dbu) { - return db::Polygon (*p * (1.0 / dbu), false); + return db::Polygon (*p, db::VCplxTrans (1.0 / dbu), false, false /*don't remove reflected*/, false /*no normalize*/); } static db::Polygon transformed_vcplx_dp (const db::DPolygon *p, const db::VCplxTrans &t) diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index fc039c9a7..6838ebb24 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -1032,6 +1032,29 @@ class DBPolygon_TestClass < TestBase end + def test_sized_transform + + # to_itype and to_dtype need to preserve the non-orientation of + # the sized() result, so they are useful for this application + + p = RBA::DPolygon::new(RBA::DBox::new(0, 0, 0.4, 0.5)) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.12, -0.22).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, ["(120,120;120,380;280,380;280,120)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.22, -0.22).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, []) + + p = RBA::Polygon::new(RBA::Box::new(0, 0, 400, 500)) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-120, -220).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, ["(120,120;120,380;280,380;280,120)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-220, -220).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + res = res.collect { |p| p.to_s } + assert_equal(res, []) + + end + end load("test_epilogue.rb") From 820ab779030c07c683dc7f4ebeb176341b4c52c6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 8 Sep 2025 23:33:14 +0200 Subject: [PATCH 49/52] Fixed sizing test --- testdata/ruby/dbPolygonTest.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/testdata/ruby/dbPolygonTest.rb b/testdata/ruby/dbPolygonTest.rb index 6838ebb24..40cabd1fd 100644 --- a/testdata/ruby/dbPolygonTest.rb +++ b/testdata/ruby/dbPolygonTest.rb @@ -1038,18 +1038,18 @@ class DBPolygon_TestClass < TestBase # the sized() result, so they are useful for this application p = RBA::DPolygon::new(RBA::DBox::new(0, 0, 0.4, 0.5)) - res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.12, -0.22).to_itype(0.001) ], false, false, 1) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.12, -0.22, 2).to_itype(0.001) ], false, false, 1) res = res.collect { |p| p.to_s } - assert_equal(res, ["(120,120;120,380;280,380;280,120)"]) - res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.22, -0.22).to_itype(0.001) ], false, false, 1) + assert_equal(res, ["(120,220;120,280;280,280;280,220)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-0.22, -0.22, 2).to_itype(0.001) ], false, false, 1) res = res.collect { |p| p.to_s } assert_equal(res, []) p = RBA::Polygon::new(RBA::Box::new(0, 0, 400, 500)) - res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-120, -220).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-120, -220, 2).to_dtype(0.001).to_itype(0.001) ], false, false, 1) res = res.collect { |p| p.to_s } - assert_equal(res, ["(120,120;120,380;280,380;280,120)"]) - res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-220, -220).to_dtype(0.001).to_itype(0.001) ], false, false, 1) + assert_equal(res, ["(120,220;120,280;280,280;280,220)"]) + res = RBA::EdgeProcessor::new.simple_merge_p2p([ p.sized(-220, -220, 2).to_dtype(0.001).to_itype(0.001) ], false, false, 1) res = res.collect { |p| p.to_s } assert_equal(res, []) From 879df5a85b2ddec6cb5886a5582fa872808b5547 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 9 Sep 2025 19:38:18 +0200 Subject: [PATCH 50/52] [consider merging] LayoutView#clear_layer_list preserves layer list name now --- src/laybasic/laybasic/layLayoutViewBase.cc | 16 +++++++++ src/laybasic/laybasic/layLayoutViewBase.h | 10 ++---- testdata/ruby/layLayers.rb | 39 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 2a39c5f7a..50a160e5d 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -1961,6 +1961,22 @@ LayoutViewBase::set_properties (unsigned int index, const LayerPropertiesList &p } } +void +LayoutViewBase::clear_layers (unsigned int index) +{ + LayerPropertiesList ll; + ll.set_name (get_properties (index).name ()); + set_properties (index, ll); +} + +void +LayoutViewBase::clear_layers () +{ + LayerPropertiesList ll; + ll.set_name (get_properties ().name ()); + set_properties (ll); +} + void LayoutViewBase::expand_properties () { diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index 2bfb8f5b6..eeda5619a 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -515,18 +515,12 @@ public: /** * @brief Clear the given layer view list */ - void clear_layers (unsigned int index) - { - set_properties (index, LayerPropertiesList ()); - } + void clear_layers (unsigned int index); /** * @brief Clear the current layer view list */ - void clear_layers () - { - set_properties (LayerPropertiesList ()); - } + void clear_layers (); /** * @brief Access the current layer properties list diff --git a/testdata/ruby/layLayers.rb b/testdata/ruby/layLayers.rb index 168ab141c..76b0f7a6b 100644 --- a/testdata/ruby/layLayers.rb +++ b/testdata/ruby/layLayers.rb @@ -1038,6 +1038,45 @@ class LAYLayers_TestClass < TestBase end + # clear_layers with index and layer list with name + def test_8 + + if !RBA.constants.member?(:Application) + return + end + + app = RBA::Application.instance + mw = app.main_window + mw.close_all + + mw.load_layout( ENV["TESTSRC"] + "/testdata/gds/t11.gds", 1 ) + + cv = mw.current_view + + cv.clear_layers + assert_equal(lnodes_str("", cv.begin_layers(0)), "") + + cv.rename_layer_list(0, "x") + assert_equal(cv.layer_list_name(0), "x") + + cv.insert_layer(0, cv.end_layers(0), RBA::LayerProperties::new) + assert_equal(lnodes_str("", cv.begin_layers(0)), "*/*@*\n") + + cv.clear_layers(0) + assert_equal(lnodes_str("", cv.begin_layers(0)), "") + assert_equal(cv.layer_list_name(0), "x") + + cv.rename_layer_list(cv.current_layer_list, "y") + assert_equal(cv.layer_list_name(0), "y") + cv.insert_layer(cv.end_layers, RBA::LayerProperties::new) + assert_equal(lnodes_str("", cv.begin_layers), "*/*@*\n") + + cv.clear_layers + assert_equal(lnodes_str("", cv.begin_layers), "") + assert_equal(cv.layer_list_name(cv.current_layer_list), "y") + + end + end load("test_epilogue.rb") From e4789f7f1b4b098ede94ddfb6bdd1b29dac20d2e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 9 Sep 2025 21:49:40 +0200 Subject: [PATCH 51/52] Enhanced object snapping for box edit mode - other corners snap too second order --- src/edt/edt/edtBoxService.cc | 31 +++++++++++++++++-- src/laybasic/laybasic/layEditorServiceBase.cc | 6 ++-- src/laybasic/laybasic/layEditorServiceBase.h | 2 +- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/edt/edt/edtBoxService.cc b/src/edt/edt/edtBoxService.cc index 109302a04..8b1cdd173 100644 --- a/src/edt/edt/edtBoxService.cc +++ b/src/edt/edt/edtBoxService.cc @@ -108,10 +108,37 @@ BoxService::do_mouse_move_inactive (const db::DPoint &p) void BoxService::do_mouse_move (const db::DPoint &p) { - do_mouse_move_inactive (p); + lay::PointSnapToObjectResult snap_details = snap2_details (p); + db::DPoint ps = snap_details.snapped_point; + + if (snap_details.object_snap == lay::PointSnapToObjectResult::NoObject) { + + clear_mouse_cursors (); + + db::DPoint px (p.x (), m_p1.y ()); + lay::PointSnapToObjectResult snap_details_x = snap2_details (px); + + db::DPoint py (m_p1.x (), p.y ()); + lay::PointSnapToObjectResult snap_details_y = snap2_details (py); + + if (snap_details_x.object_snap != lay::PointSnapToObjectResult::NoObject) { + ps = db::DPoint (snap_details_x.snapped_point.x (), ps.y ()); + mouse_cursor_from_snap_details (snap_details_x, true /*add*/); + } + + if (snap_details_y.object_snap != lay::PointSnapToObjectResult::NoObject) { + ps = db::DPoint (ps.x (), snap_details_y.snapped_point.y ()); + mouse_cursor_from_snap_details (snap_details_y, true /*add*/); + } + + add_mouse_cursor (ps); + + } else { + mouse_cursor_from_snap_details (snap_details); + } set_cursor (lay::Cursor::cross); - m_p2 = snap2 (p); + m_p2 = ps; update_marker (); } diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index d6e9ef7ee..ffa4b03b5 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -277,9 +277,11 @@ EditorServiceBase::clear_mouse_cursors () } void -EditorServiceBase::mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details) +EditorServiceBase::mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details, bool noclear) { - clear_mouse_cursors (); + if (! noclear) { + clear_mouse_cursors (); + } add_mouse_cursor (snap_details.snapped_point, snap_details.object_snap == lay::PointSnapToObjectResult::ObjectVertex || diff --git a/src/laybasic/laybasic/layEditorServiceBase.h b/src/laybasic/laybasic/layEditorServiceBase.h index 16cdd4838..d23b37755 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.h +++ b/src/laybasic/laybasic/layEditorServiceBase.h @@ -110,7 +110,7 @@ public: /** * @brief Provides a nice mouse tracking cursor from the given snap details */ - void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details); + void mouse_cursor_from_snap_details (const lay::PointSnapToObjectResult &snap_details, bool noclear = false); /** * @brief Gets the tracking cursor color From 788dd2b1bcdde06018f994ac575de4cb16b72259 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 9 Sep 2025 22:36:41 +0200 Subject: [PATCH 52/52] Fixing non-Qt builds --- src/laybasic/laybasic/layEditorServiceBase.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/laybasic/laybasic/layEditorServiceBase.cc b/src/laybasic/laybasic/layEditorServiceBase.cc index ffa4b03b5..9be73bf37 100644 --- a/src/laybasic/laybasic/layEditorServiceBase.cc +++ b/src/laybasic/laybasic/layEditorServiceBase.cc @@ -416,10 +416,10 @@ EditorServiceBase::show_error (tl::Exception &ex) tl::error << ex.msg (); } -void +int EditorServiceBase::focus_page_open () { - // .. nothing yet .. + return 0; } #endif