From f701255076c48444fc608bf1a7f29f01d3ed0b30 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:13:39 +0300 Subject: [PATCH 1/6] Add synchronized layer visibility across layout views --- src/lay/lay/layConfig.h | 1 + src/lay/lay/layMainConfigPages.cc | 2 +- src/lay/lay/layMainWindow.cc | 103 +++++++++++++++++++++++++++++- src/lay/lay/layMainWindow.h | 4 ++ 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/lay/lay/layConfig.h b/src/lay/lay/layConfig.h index 7ef712ab2..2bbc80ebe 100644 --- a/src/lay/lay/layConfig.h +++ b/src/lay/lay/layConfig.h @@ -37,6 +37,7 @@ namespace lay static const std::string cfg_default_grids ("default-grids"); static const std::string cfg_circle_points ("circle-points"); static const std::string cfg_synchronized_views ("synchronized-views"); +static const std::string cfg_synchronized_layers ("synchronized-layers"); static const std::string cfg_edit_mode ("edit-mode"); static const std::string cfg_custom_macro_paths ("custom-macro-paths"); static const std::string cfg_mru ("mru"); diff --git a/src/lay/lay/layMainConfigPages.cc b/src/lay/lay/layMainConfigPages.cc index 67f8734a8..f0a0304b1 100644 --- a/src/lay/lay/layMainConfigPages.cc +++ b/src/lay/lay/layMainConfigPages.cc @@ -60,6 +60,7 @@ class MainPluginDeclaration options.push_back (std::pair (cfg_edit_mode, "false")); options.push_back (std::pair (cfg_custom_macro_paths, "")); options.push_back (std::pair (cfg_synchronized_views, "false")); + options.push_back (std::pair (cfg_synchronized_layers, "false")); options.push_back (std::pair (cfg_default_grids, "0.01,0.005,0.001")); options.push_back (std::pair (cfg_mru, "")); options.push_back (std::pair (cfg_mru_sessions, "")); @@ -753,4 +754,3 @@ CustomizeMenuConfigPage::current_changed (QTreeWidgetItem *current, QTreeWidgetI } } - diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 7371b9a44..0a21680d6 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -37,6 +37,7 @@ #include #include #include +#include #if QT_VERSION >= 0x050000 # include #endif @@ -118,6 +119,14 @@ const int max_dirty_files = 15; static MainWindow *mw_instance = 0; +struct LayerIdentityLess +{ + bool operator() (const db::LayerProperties &a, const db::LayerProperties &b) const + { + return a.log_less (b); + } +}; + MainWindow * MainWindow::instance () { @@ -183,6 +192,7 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) m_default_grids_updated (true), m_new_layout_current_panel (false), m_synchronized_views (false), + m_synchronized_layers (false), m_synchronous (false), m_busy (false), mp_app (app), @@ -1223,6 +1233,20 @@ MainWindow::configure (const std::string &name, const std::string &value) m_synchronized_views = flag; return true; + } else if (name == cfg_synchronized_layers) { + + bool flag = false; + tl::from_string (value, flag); + m_synchronized_layers = flag; + if (flag && current_view ()) { + for (int i = 0; i < int (views ()); ++i) { + if (view (i) != current_view ()) { + synchronize_layers (current_view (), view (i)); + } + } + } + return true; + } else if (name == cfg_layout_file_watcher_enabled) { bool flag = false; @@ -2470,6 +2494,10 @@ MainWindow::select_view (int index) box = current_view ()->viewport ().box (); } + if (m_synchronized_layers && current_view () && view (index) != current_view ()) { + synchronize_layers (current_view (), view (index)); + } + view (index)->set_current (); if (current_view ()) { @@ -2504,6 +2532,68 @@ MainWindow::select_view (int index) } } +void +MainWindow::active_layers_changed (lay::LayoutView *source, int flags) +{ + if (! m_synchronized_layers || source != current_view () || ! (flags & 3)) { + return; + } + + for (int i = 0; i < int (views ()); ++i) { + if (view (i) != source) { + synchronize_layers (source, view (i)); + } + } +} + +void +MainWindow::active_layer_list_changed (lay::LayoutView *source, int) +{ + active_layers_changed (source, 1); +} + +void +MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target) +{ + std::map visibility; + for (lay::LayerPropertiesConstIterator l = source->begin_layers (); ! l.at_end (); ++l) { + if (l->has_children () || ! l->is_standard_layer ()) { + continue; + } + db::LayerProperties key = l->source (true).layer_props (); + if (! key.is_null ()) { + visibility [key] = visibility [key] || l->visible (true); + } + } + + for (lay::LayerPropertiesConstIterator l = target->begin_layers (); ! l.at_end (); ++l) { + if (l->has_children () || ! l->is_standard_layer ()) { + continue; + } + db::LayerProperties key = l->source (true).layer_props (); + std::map::const_iterator v = visibility.find (key); + if (v == visibility.end ()) { + continue; + } + + if (v->second) { + for (lay::LayerPropertiesConstIterator p = l.parent (); ! p.is_null (); p = p.parent ()) { + if (! p->visible (false)) { + lay::LayerProperties props (*p); + props.set_visible (true); + target->set_properties (p, props); + } + } + } + + if (l->visible (false) != v->second) { + lay::LayerProperties props (*l); + props.set_visible (v->second); + target->set_properties (l, props); + } + } +} + void MainWindow::cm_open_too () { @@ -3505,6 +3595,8 @@ MainWindow::create_layout (const std::string &technology, int mode) void MainWindow::add_view (lay::LayoutViewWidget *view) { + view->view ()->layer_list_changed_event.add (this, &MainWindow::active_layers_changed, view->view ()); + view->view ()->current_layer_list_changed_event.add (this, &MainWindow::active_layer_list_changed, view->view ()); connect (view, SIGNAL (title_changed (lay::LayoutView *)), this, SLOT (view_title_changed (lay::LayoutView *))); connect (view, SIGNAL (dirty_changed (lay::LayoutView *)), this, SLOT (view_title_changed (lay::LayoutView *))); connect (view, SIGNAL (edits_enabled_changed ()), this, SLOT (edits_enabled_changed ())); @@ -3551,10 +3643,15 @@ MainWindow::do_create_view () int MainWindow::create_view () { + lay::LayoutView *previous_view = current_view (); + // create a new view int view_index = do_create_view (); // add a new tab and make the new view the current one + if (m_synchronized_layers && previous_view) { + synchronize_layers (previous_view, view (view_index)); + } mp_views.back ()->view ()->set_current (); mp_view_stack->add_widget (mp_views.back ()); @@ -3582,6 +3679,7 @@ lay::CellViewRef MainWindow::create_or_load_layout (const std::string *filename, const db::LoadLayoutOptions *options, const std::string &technology, int mode) { lay::LayoutView *vw = 0; + lay::LayoutView *previous_view = current_view (); if (! current_view ()) { mode = 1; @@ -3618,6 +3716,9 @@ MainWindow::create_or_load_layout (const std::string *filename, const db::LoadLa // make the new view the current one if (mode == 1) { + if (m_synchronized_layers && previous_view) { + synchronize_layers (previous_view, vw); + } mp_views.back ()->view ()->set_current (); mp_view_stack->add_widget (mp_views.back ()); @@ -4632,6 +4733,7 @@ class MainWindowPluginDeclaration menu_entries.push_back (lay::config_menu_item ("show_ghost_cells", at, tl::to_string (QObject::tr ("Show Unresolved References")), cfg_ghost_cells_visible, "?")); menu_entries.push_back (lay::config_menu_item ("no_stipples", at, tl::to_string (QObject::tr ("Show Layers Without Fill")), cfg_no_stipple, "?")); menu_entries.push_back (lay::config_menu_item ("synchronized_views", at, tl::to_string (QObject::tr ("Synchronized Views")), cfg_synchronized_views, "?")); + menu_entries.push_back (lay::config_menu_item ("synchronized_layers", at, tl::to_string (QObject::tr ("Synchronized Layers")), cfg_synchronized_layers, "?")); menu_entries.push_back (lay::config_menu_item ("edit_top_level_selection:edit_mode", at, tl::to_string (QObject::tr ("Select Top Level Objects")), edt::cfg_edit_top_level_selection, "?")); menu_entries.push_back (lay::separator ("panels_group", at)); menu_entries.push_back (lay::config_menu_item ("show_toolbar", at, tl::to_string (QObject::tr ("Toolbar")), cfg_show_toolbar, "?")); @@ -4675,4 +4777,3 @@ class MainWindowPluginDeclaration static tl::RegisteredClass config_decl (new MainWindowPluginDeclaration (), -100, "MainWindowPlugin"); } // namespace lay - diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index 7e26be752..e25c3b5ed 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -805,6 +805,7 @@ protected slots: std::vector > m_hidden; bool m_new_layout_current_panel; bool m_synchronized_views; + bool m_synchronized_layers; bool m_synchronous; bool m_busy; QApplication *mp_app; @@ -883,6 +884,9 @@ protected slots: 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 active_layers_changed (lay::LayoutView *source, int flags); + void active_layer_list_changed (lay::LayoutView *source, int index); + void synchronize_layers (lay::LayoutView *source, lay::LayoutView *target); void update_window_title (); void update_tab_title (int i); void add_view (LayoutViewWidget *view); From 98b73b951793468e18776f88f5971d7fcce2b379 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:14:53 +0300 Subject: [PATCH 2/6] Expose synchronized layers in setup and manual --- src/doc/doc/manual/view_options.xml | 6 +++++- src/lay/lay/MainConfigPage2.ui | 24 +++++++++++++++++++++++- src/lay/lay/layMainConfigPages.cc | 5 ++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/doc/doc/manual/view_options.xml b/src/doc/doc/manual/view_options.xml index c7d0d3667..e188bbd6a 100644 --- a/src/doc/doc/manual/view_options.xml +++ b/src/doc/doc/manual/view_options.xml @@ -40,6 +40,11 @@ they show the same region of the layout. This option can be useful if two layouts are loaded in different views for clarity, but identical regions need to be inspected. +
  • + Synchronized Layers: matching layers in different views (tabs) share their visibility. + Visibility changes in the active view are copied to other open views. Layers present in only one + view keep their own visibility. Layer colors and other display properties remain independent. +
  • Select Top Level Objects: this menu item gives a quick access to the respective editor option. If this option is checked, only objects from the top level are selected. This is @@ -60,4 +65,3 @@ - diff --git a/src/lay/lay/MainConfigPage2.ui b/src/lay/lay/MainConfigPage2.ui index fa9489e35..1a57d3c18 100644 --- a/src/lay/lay/MainConfigPage2.ui +++ b/src/lay/lay/MainConfigPage2.ui @@ -9,7 +9,7 @@ 0 0 400 - 80 + 140 @@ -44,6 +44,28 @@ + + + + Synchronized Layers + + + + 11 + + + 6 + + + + + All views show the same layers + + + + + + diff --git a/src/lay/lay/layMainConfigPages.cc b/src/lay/lay/layMainConfigPages.cc index f0a0304b1..7167776a4 100644 --- a/src/lay/lay/layMainConfigPages.cc +++ b/src/lay/lay/layMainConfigPages.cc @@ -101,7 +101,7 @@ class MainPluginDeclaration pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Customize Menu")), new CustomizeMenuConfigPage (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Units")), new MainConfigPage5 (parent))); pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Application|Circles")), new MainConfigPage6 (parent))); - pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Display|Synchronized Views")), new MainConfigPage2 (parent))); + pages.push_back (std::make_pair (tl::to_string (QObject::tr ("Display|Synchronization")), new MainConfigPage2 (parent))); return pages; } }; @@ -246,12 +246,15 @@ MainConfigPage2::setup (lay::Dispatcher *root) bool flag = false; root->config_get (cfg_synchronized_views, flag); mp_ui->sync_views_cbx->setChecked (flag); + root->config_get (cfg_synchronized_layers, flag); + mp_ui->sync_layers_cbx->setChecked (flag); } void MainConfigPage2::commit (lay::Dispatcher *root) { root->config_set (cfg_synchronized_views, mp_ui->sync_views_cbx->isChecked ()); + root->config_set (cfg_synchronized_layers, mp_ui->sync_layers_cbx->isChecked ()); } // ------------------------------------------------------------- From 672dc8a6a8d162ea39d186ddf451ae63e7eae837 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:17:07 +0300 Subject: [PATCH 3/6] Preserve undo and unmatched layers during visibility sync --- src/lay/lay/layMainWindow.cc | 120 +++++++++++++++++++++++++++++++---- src/lay/lay/layMainWindow.h | 7 +- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 0a21680d6..9b094061a 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -127,6 +127,20 @@ struct LayerIdentityLess } }; +static db::LayerProperties +layer_identity (lay::LayoutView *view, const lay::LayerPropertiesConstIterator &layer) +{ + int cv_index = layer->cellview_index (); + int layer_index = layer->layer_index (); + if (cv_index >= 0 && cv_index < int (view->cellviews ()) && layer_index >= 0) { + const db::Layout &layout = view->cellview (cv_index)->layout (); + if (layout.is_valid_layer (layer_index)) { + return layout.get_properties (layer_index); + } + } + return layer->source (true).layer_props (); +} + MainWindow * MainWindow::instance () { @@ -186,6 +200,7 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) dm_do_update_menu (this, &MainWindow::do_update_menu), dm_do_update_grids (this, &MainWindow::do_update_grids), dm_do_update_mru_menus (this, &MainWindow::do_update_mru_menus), + dm_synchronize_layers (this, &MainWindow::do_synchronize_layers), dm_exit (this, &MainWindow::exit), m_grid_micron (0.001), m_default_grid (0.0), @@ -193,6 +208,8 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) m_new_layout_current_panel (false), m_synchronized_views (false), m_synchronized_layers (false), + m_pending_layer_sync (false), + m_pending_layer_sync_transaction_id (0), m_synchronous (false), m_busy (false), mp_app (app), @@ -1238,6 +1255,10 @@ MainWindow::configure (const std::string &name, const std::string &value) bool flag = false; tl::from_string (value, flag); m_synchronized_layers = flag; + if (! flag) { + dm_synchronize_layers.cancel (); + m_pending_layer_sync = false; + } if (flag && current_view ()) { for (int i = 0; i < int (views ()); ++i) { if (view (i) != current_view ()) { @@ -1748,6 +1769,7 @@ MainWindow::cm_reset_window_state () void MainWindow::cm_undo () { + flush_pending_layer_sync (); if (current_view () && m_manager.available_undo ().first) { for (std::vector ::iterator vp = mp_views.begin (); vp != mp_views.end (); ++vp) { (*vp)->view ()->clear_selection (); @@ -1760,6 +1782,7 @@ MainWindow::cm_undo () void MainWindow::cm_undo_list () { + flush_pending_layer_sync (); if (current_view () && m_manager.available_undo ().first) { std::unique_ptr dialog (new lay::UndoRedoListForm (this, &m_manager, true)); @@ -1781,6 +1804,7 @@ MainWindow::cm_undo_list () void MainWindow::cm_redo () { + flush_pending_layer_sync (); if (current_view () && m_manager.available_redo ().first) { for (std::vector ::iterator vp = mp_views.begin (); vp != mp_views.end (); ++vp) { (*vp)->view ()->clear_selection (); @@ -1793,6 +1817,7 @@ MainWindow::cm_redo () void MainWindow::cm_redo_list () { + flush_pending_layer_sync (); if (current_view () && m_manager.available_redo ().first) { std::unique_ptr dialog (new lay::UndoRedoListForm (this, &m_manager, false)); @@ -2477,6 +2502,8 @@ MainWindow::view_selected (int index) void MainWindow::select_view (int index) { + flush_pending_layer_sync (); + bool dis = m_disable_tab_selected; m_disable_tab_selected = true; // prevent recursion @@ -2539,11 +2566,16 @@ MainWindow::active_layers_changed (lay::LayoutView *source, int flags) return; } - for (int i = 0; i < int (views ()); ++i) { - if (view (i) != source) { - synchronize_layers (source, view (i)); - } + if (m_manager.replaying ()) { + // Replay visits intermediate states of every recorded layer change. + dm_synchronize_layers.cancel (); + m_pending_layer_sync = false; + return; } + + m_pending_layer_sync = true; + m_pending_layer_sync_transaction_id = m_manager.transacting () ? m_manager.last_transaction_id () : 0; + dm_synchronize_layers (); } void @@ -2553,25 +2585,77 @@ MainWindow::active_layer_list_changed (lay::LayoutView *source, int) } void -MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target) +MainWindow::flush_pending_layer_sync () +{ + if (m_pending_layer_sync) { + dm_synchronize_layers.cancel (); + do_synchronize_layers (); + } +} + +void +MainWindow::do_synchronize_layers () +{ + if (! m_pending_layer_sync || ! m_synchronized_layers || ! current_view ()) { + return; + } + + m_pending_layer_sync = false; + db::Manager::transaction_id_t join_with = m_pending_layer_sync_transaction_id; + lay::LayoutView *source = current_view (); + for (int i = 0; i < int (views ()); ++i) { + if (view (i) != source) { + synchronize_layers (source, view (i), join_with); + } + } +} + +void +MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target, db::Manager::transaction_id_t join_with) { std::map visibility; for (lay::LayerPropertiesConstIterator l = source->begin_layers (); ! l.at_end (); ++l) { if (l->has_children () || ! l->is_standard_layer ()) { continue; } - db::LayerProperties key = l->source (true).layer_props (); + db::LayerProperties key = layer_identity (source, l); if (! key.is_null ()) { visibility [key] = visibility [key] || l->visible (true); } } + // Unhiding a group must not reveal layers absent from the source view. + std::vector hidden_unmatched; + for (lay::LayerPropertiesConstIterator l = target->begin_layers (); ! l.at_end (); ++l) { + if (l->has_children () || l->visible (true)) { + continue; + } + db::LayerProperties key = layer_identity (target, l); + if (! l->is_standard_layer () || visibility.find (key) == visibility.end ()) { + hidden_unmatched.push_back (l); + } + } + + std::unique_ptr transaction; + auto set_visible = [&] (const lay::LayerPropertiesConstIterator &layer, bool visible) { + if (! transaction && ! m_manager.transacting () && ! m_manager.replaying ()) { + if (join_with && m_manager.last_transaction_id () == join_with && m_manager.transaction_id_for_undo () == join_with) { + transaction.reset (new db::Transaction (&m_manager, "", join_with)); + } else { + transaction.reset (new db::Transaction (&m_manager, tl::to_string (QObject::tr ("Synchronize layers")))); + } + } + lay::LayerProperties props (*layer); + props.set_visible (visible); + target->set_properties (layer, props); + }; + for (lay::LayerPropertiesConstIterator l = target->begin_layers (); ! l.at_end (); ++l) { if (l->has_children () || ! l->is_standard_layer ()) { continue; } - db::LayerProperties key = l->source (true).layer_props (); - std::map::const_iterator v = visibility.find (key); + db::LayerProperties key = layer_identity (target, l); + auto v = visibility.find (key); if (v == visibility.end ()) { continue; } @@ -2579,17 +2663,19 @@ MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target if (v->second) { for (lay::LayerPropertiesConstIterator p = l.parent (); ! p.is_null (); p = p.parent ()) { if (! p->visible (false)) { - lay::LayerProperties props (*p); - props.set_visible (true); - target->set_properties (p, props); + set_visible (p, true); } } } if (l->visible (false) != v->second) { - lay::LayerProperties props (*l); - props.set_visible (v->second); - target->set_properties (l, props); + set_visible (l, v->second); + } + } + + for (const auto &layer : hidden_unmatched) { + if (layer->visible (true)) { + set_visible (layer, false); } } } @@ -3067,6 +3153,10 @@ MainWindow::close_view (int index) { if (view (index)) { + if (m_pending_layer_sync && view (index) == current_view ()) { + flush_pending_layer_sync (); + } + cancel (); // this suppresses view_selected events that would otherwise be created @@ -3595,6 +3685,8 @@ MainWindow::create_layout (const std::string &technology, int mode) void MainWindow::add_view (lay::LayoutViewWidget *view) { + flush_pending_layer_sync (); + view->view ()->layer_list_changed_event.add (this, &MainWindow::active_layers_changed, view->view ()); view->view ()->current_layer_list_changed_event.add (this, &MainWindow::active_layer_list_changed, view->view ()); connect (view, SIGNAL (title_changed (lay::LayoutView *)), this, SLOT (view_title_changed (lay::LayoutView *))); diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index e25c3b5ed..708f3a00d 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -788,6 +788,7 @@ protected slots: tl::DeferredMethod dm_do_update_menu; tl::DeferredMethod dm_do_update_grids; tl::DeferredMethod dm_do_update_mru_menus; + tl::DeferredMethod dm_synchronize_layers; tl::DeferredMethod dm_exit; QTimer m_message_timer; QTimer m_file_changed_timer; @@ -806,6 +807,8 @@ protected slots: bool m_new_layout_current_panel; bool m_synchronized_views; bool m_synchronized_layers; + bool m_pending_layer_sync; + db::Manager::transaction_id_t m_pending_layer_sync_transaction_id; bool m_synchronous; bool m_busy; QApplication *mp_app; @@ -886,7 +889,9 @@ protected slots: void current_view_changed (); void active_layers_changed (lay::LayoutView *source, int flags); void active_layer_list_changed (lay::LayoutView *source, int index); - void synchronize_layers (lay::LayoutView *source, lay::LayoutView *target); + void flush_pending_layer_sync (); + void do_synchronize_layers (); + void synchronize_layers (lay::LayoutView *source, lay::LayoutView *target, db::Manager::transaction_id_t join_with = 0); void update_window_title (); void update_tab_title (int i); void add_view (LayoutViewWidget *view); From af7edaf46233d7de4d053033f3efa25fd3b9da50 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:23:37 +0300 Subject: [PATCH 4/6] Cover synchronized layer visibility and undo --- testdata/ruby/layMainWindow.rb | 73 +++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/testdata/ruby/layMainWindow.rb b/testdata/ruby/layMainWindow.rb index fdd67da91..cb7d5daa0 100644 --- a/testdata/ruby/layMainWindow.rb +++ b/testdata/ruby/layMainWindow.rb @@ -67,7 +67,78 @@ def test_2 end + def test_3 + + if !RBA.constants.member?(:Application) + return + end + + mw = RBA::Application.instance.main_window + synchronized_layers = mw.get_config("synchronized-layers").to_s + mw.set_config("synchronized-layers", "false") + mw.close_all + + begin + path = ENV["TESTSRC"] + "/testdata/gds/t11.gds" + mw.load_layout(path, 1) + first = mw.current_view + mw.load_layout(path, 1) + second = mw.current_view + first.clear_layers + second.clear_layers + + indexed = RBA::LayerProperties::new + indexed_source = "%#{first.cellview(0).layout.layer(1, 0)}@1" + indexed.source = indexed_source + first.insert_layer(first.end_layers, indexed) + + second.insert_layer(second.end_layers, RBA::LayerProperties::new) + group = second.begin_layers + matched = RBA::LayerProperties::new + matched.source = "1/0@1" + group.current.add_child(matched) + unique = RBA::LayerProperties::new + unique.source = "2/0@1" + group.current.add_child(unique) + group.current.visible = false + + mw.current_view_index = 0 + mw.set_config("synchronized-layers", "true") + assert_equal(group.first_child.current.visible?(true), true) + sibling = group.first_child + sibling.next_sibling(1) + assert_equal(sibling.current.visible?(true), false) + + first.transaction("Hide layer") + hidden = RBA::LayerProperties::new + hidden.source = indexed_source + hidden.visible = false + first.set_layer_properties(first.begin_layers, hidden) + first.commit + + mw.cm_undo + assert_equal(first.begin_layers.current.visible?(true), true) + assert_equal(group.first_child.current.visible?(true), true) + mw.cm_redo + assert_equal(first.begin_layers.current.visible?(true), false) + assert_equal(group.first_child.current.visible?(true), false) + assert_equal(sibling.current.visible?(true), false) + + first.transaction("Show layer") + shown = RBA::LayerProperties::new + shown.source = indexed_source + first.set_layer_properties(first.begin_layers, shown) + first.commit + mw.current_view_index = 1 + assert_equal(group.first_child.current.visible?(true), true) + assert_equal(sibling.current.visible?(true), false) + ensure + mw.set_config("synchronized-layers", synchronized_layers) + mw.close_all + end + + end + end load("test_epilogue.rb") - From 50cf189440d2a5fe794c00585694bbe951cc3e3e Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:31:19 +0300 Subject: [PATCH 5/6] Avoid transaction allocation with C++17 --- src/lay/lay/layMainWindow.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 9b094061a..b0887c6a9 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -38,6 +38,9 @@ #include #include #include +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#include +#endif #if QT_VERSION >= 0x050000 # include #endif @@ -2636,14 +2639,20 @@ MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target } } +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + std::optional transaction; +#else std::unique_ptr transaction; +#endif auto set_visible = [&] (const lay::LayerPropertiesConstIterator &layer, bool visible) { if (! transaction && ! m_manager.transacting () && ! m_manager.replaying ()) { - if (join_with && m_manager.last_transaction_id () == join_with && m_manager.transaction_id_for_undo () == join_with) { - transaction.reset (new db::Transaction (&m_manager, "", join_with)); - } else { - transaction.reset (new db::Transaction (&m_manager, tl::to_string (QObject::tr ("Synchronize layers")))); - } + bool join = join_with && m_manager.last_transaction_id () == join_with && m_manager.transaction_id_for_undo () == join_with; + std::string description = join ? std::string () : tl::to_string (QObject::tr ("Synchronize layers")); +#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + transaction.emplace (&m_manager, description, join ? join_with : 0); +#else + transaction.reset (new db::Transaction (&m_manager, description, join ? join_with : 0)); +#endif } lay::LayerProperties props (*layer); props.set_visible (visible); From 954cdf976fbcdb1f69b30fae72a9aca47d5e9a34 Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Thu, 24 Sep 2026 17:44:22 +0300 Subject: [PATCH 6/6] Use existing layer identity comparator --- src/lay/lay/layMainWindow.cc | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index b0887c6a9..7fa0d13da 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -63,6 +63,7 @@ #include "tlUri.h" #include "dbMemStatistics.h" #include "dbManager.h" +#include "dbLayerProperties.h" #include "dbStream.h" #include "dbSaveLayoutOptions.h" #include "dbClipboard.h" @@ -122,14 +123,6 @@ const int max_dirty_files = 15; static MainWindow *mw_instance = 0; -struct LayerIdentityLess -{ - bool operator() (const db::LayerProperties &a, const db::LayerProperties &b) const - { - return a.log_less (b); - } -}; - static db::LayerProperties layer_identity (lay::LayoutView *view, const lay::LayerPropertiesConstIterator &layer) { @@ -2616,7 +2609,7 @@ MainWindow::do_synchronize_layers () void MainWindow::synchronize_layers (lay::LayoutView *source, lay::LayoutView *target, db::Manager::transaction_id_t join_with) { - std::map visibility; + std::map visibility; for (lay::LayerPropertiesConstIterator l = source->begin_layers (); ! l.at_end (); ++l) { if (l->has_children () || ! l->is_standard_layer ()) { continue;