diff --git a/src/lay/lay/doc/about/macro_in_menu.xml b/src/lay/lay/doc/about/macro_in_menu.xml
index 701bf2277..ce78eda0b 100644
--- a/src/lay/lay/doc/about/macro_in_menu.xml
+++ b/src/lay/lay/doc/about/macro_in_menu.xml
@@ -19,15 +19,49 @@
The menu path specifies the position where the macro shall be put. A macro path is a
- sequence of symbolic names separated by a dot. For example "edit_menu.end" is the end
- of the "Edit" menu and "edit_menu.undo" is the "Undo" entry in the "Edit" menu. To obtain
- a list of the paths available, have a look at the "Key Bindings" page in the "Application"
- section of the setup dialog ("File/Setup"). The pseudo element "end" denotes the position
- after the last entry. The same way "begin" indicates the first entry in the menu.
- The macro will be inserted before the entry indicated by the path. Hence the if a
- path of "edit_menu.end" is given, the macro will be inserted at the end of the
- "Edit" menu and "edit_menu.undo" will insert the macro before the "Undo" entry.
+ sequence of symbolic names separated by a dot. For example:
+
+
+ edit_menu.end is the end of the "Edit" menu
+ edit_menu.undo is the "Undo" entry in the "Edit" menu
+
+
+ To obtain
+ a list of the paths available, have a look at the "Key Bindings" page in the "Application"
+ section of the setup dialog ("File/Setup").
+
+
+
+ The pseudo element "end" denotes the position
+ after the last entry. The same way "begin" indicates the first entry in the menu.
+ The macro will be inserted before the entry indicated by the path. Hence:
+
+
+
+ - edit_menu.end: the macro will be inserted at the end of the "Edit" menu
+ - edit_menu.undo: the macro will be inserted before the "Undo" entry.
+
+
+
+ If a plus sign follows the macro path element, the new element is inserted after
+ this element. For example:
+
+
+
+ - edit_menu.undo+: the macro will be inserted after the "Undo" menu item.
+
+
+
+ A special form can be used to generate new groups: if the given element does not exist,
+ the menu generator can be instructed to create it by appending the insert point plus
+ the new text string to the element after a ">" character. For example:
+
+
+
+ - edit_menu.my_group>end("My Edit Functions").end: will look for "my_group" and add the new element at the end of this group. If no such
+ group exists, it will be created at the end of the "Edit" menu with the title "My Edit Functions".
+
If no macro path is specified, the macro is inserted in the "Macros" menu.
diff --git a/src/laybasic/laybasic/layAbstractMenu.cc b/src/laybasic/laybasic/layAbstractMenu.cc
index 8f7b9b838..c8a1d808e 100644
--- a/src/laybasic/laybasic/layAbstractMenu.cc
+++ b/src/laybasic/laybasic/layAbstractMenu.cc
@@ -101,13 +101,13 @@ parse_menu_title (const std::string &s, std::string &title, std::string &shortcu
// AbstractMenuItem implementation
AbstractMenuItem::AbstractMenuItem ()
- : mp_menu (0), m_has_submenu (false)
+ : mp_menu (0), m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
AbstractMenuItem::AbstractMenuItem (const AbstractMenuItem &)
- : mp_menu (0), m_has_submenu (false)
+ : mp_menu (0), m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
@@ -173,7 +173,13 @@ AbstractMenuItem::set_has_submenu ()
m_has_submenu = true;
}
-void
+void
+AbstractMenuItem::set_remove_on_empty ()
+{
+ m_remove_on_empty = true;
+}
+
+void
AbstractMenuItem::set_menu (QMenu *menu)
{
mp_menu = menu;
@@ -708,7 +714,7 @@ AbstractMenu::create_action (const std::string &s)
AbstractMenu::AbstractMenu (AbstractMenuProvider *provider)
: mp_provider (provider)
{
- tl_assert (mp_provider != 0);
+ // .. nothing yet ..
}
AbstractMenu::~AbstractMenu ()
@@ -777,6 +783,8 @@ AbstractMenu::build_detached (const std::string &name, QMenuBar *mbar)
void
AbstractMenu::build (QMenuBar *mbar, QToolBar *tbar)
{
+ tl_assert (mp_provider != 0);
+
m_helper_menu_items.clear ();
mbar->clear ();
tbar->clear ();
@@ -942,23 +950,27 @@ AbstractMenu::items (const std::string &path) const
}
void
-AbstractMenu::insert_item (const std::string &path, const std::string &name, const Action &action)
+AbstractMenu::insert_item (const std::string &p, const std::string &name, const Action &action)
{
- std::pair::iterator > ins = find_item (path);
- if (ins.first) {
+ typedef std::vector::iterator > > path_type;
+ path_type path = find_item (p);
+ if (! path.empty ()) {
+
+ AbstractMenuItem *parent = path.back ().first;
+ std::list::iterator iter = path.back ().second;
// insert the new item
- ins.first->children.insert (ins.second, AbstractMenuItem ());
- --ins.second;
+ parent->children.insert (iter, AbstractMenuItem ());
+ --iter;
- ins.second->setup_item (ins.first->name (), name, action);
+ iter->setup_item (parent->name (), name, action);
- // find an item with the same name and remove it
- for (std::list::iterator existing = ins.first->children.begin (); existing != ins.first->children.end (); ) {
+ // find any items with the same name and remove them
+ for (std::list::iterator existing = parent->children.begin (); existing != parent->children.end (); ) {
std::list::iterator existing_next = existing;
++existing_next;
- if (existing->name () == ins.second->name () && &*existing != &*ins.second) {
- ins.first->children.erase (existing);
+ if (existing->name () == iter->name () && existing != iter) {
+ parent->children.erase (existing);
}
existing = existing_next;
}
@@ -969,29 +981,55 @@ AbstractMenu::insert_item (const std::string &path, const std::string &name, con
}
void
-AbstractMenu::insert_separator (const std::string &path, const std::string &name)
+AbstractMenu::insert_separator (const std::string &p, const std::string &name)
{
- std::pair::iterator > ins = find_item (path);
- if (ins.first) {
- ins.first->children.insert (ins.second, AbstractMenuItem ());
- --ins.second;
+ tl_assert (mp_provider != 0); // required to get the parent for the new QAction (via ActionHandle)
+
+ typedef std::vector::iterator > > path_type;
+ path_type path = find_item (p);
+ if (! path.empty ()) {
+
+ AbstractMenuItem *parent = path.back ().first;
+ std::list::iterator iter = path.back ().second;
+
+ parent->children.insert (iter, AbstractMenuItem ());
+ --iter;
Action action (new ActionHandle (mp_provider->menu_parent_widget ()));
action.set_separator (true);
- ins.second->setup_item (ins.first->name (), name, action);
+ iter->setup_item (parent->name (), name, action);
+
}
+
emit changed ();
}
void
-AbstractMenu::insert_menu (const std::string &path, const std::string &name, const Action &action)
+AbstractMenu::insert_menu (const std::string &p, const std::string &name, const Action &action)
{
- std::pair::iterator > ins = find_item (path);
- if (ins.first) {
- ins.first->children.insert (ins.second, AbstractMenuItem ());
- --ins.second;
- ins.second->setup_item (ins.first->name (), name, action);
- ins.second->set_has_submenu ();
+ typedef std::vector::iterator > > path_type;
+ path_type path = find_item (p);
+ if (! path.empty ()) {
+
+ AbstractMenuItem *parent = path.back ().first;
+ std::list::iterator iter = path.back ().second;
+
+ parent->children.insert (iter, AbstractMenuItem ());
+ --iter;
+ iter->setup_item (parent->name (), name, action);
+ iter->set_has_submenu ();
+
+ // find any items with the same name and remove them
+ for (std::list::iterator existing = parent->children.begin (); existing != parent->children.end (); ) {
+ std::list::iterator existing_next = existing;
+ ++existing_next;
+ if (existing->name () == iter->name () && existing != iter) {
+ parent->children.erase (existing);
+ }
+ existing = existing_next;
+ }
+
}
+
emit changed ();
}
@@ -1002,28 +1040,43 @@ AbstractMenu::insert_menu (const std::string &path, const std::string &name, con
}
void
-AbstractMenu::delete_item (const std::string &path)
+AbstractMenu::delete_item (const std::string &p)
{
- std::pair::iterator > ins = find_item (path);
- if (ins.first) {
- if (ins.second == ins.first->children.end ()) {
- throw tl::Exception (tl::to_string (QObject::tr ("delete_item cannot delete past-the-end item")));
+ typedef std::vector::iterator > > path_type;
+ path_type path = find_item (p);
+ if (! path.empty ()) {
+
+ for (path_type::const_reverse_iterator p = path.rbegin (); p != path.rend (); ++p) {
+
+ if (p->second == p->first->children.end ()) {
+ break;
+ } else if (p != path.rbegin () && (! p->second->remove_on_empty () || ! p->second->children.empty ())) {
+ // stop on non-empty parent menues
+ break;
+ }
+
+ reset_menu_objects (*p->second);
+ p->first->children.erase (p->second);
+
}
- reset_menu_objects (*ins.second);
- ins.first->children.erase (ins.second);
+
}
+
emit changed ();
}
-static void do_delete_items (std::list &list, const Action &action)
+static void do_delete_items (AbstractMenuItem &parent, const Action &action)
{
- for (std::list::iterator l = list.begin (); l != list.end (); ) {
+ for (std::list::iterator l = parent.children.begin (); l != parent.children.end (); ) {
std::list::iterator ll = l;
++ll;
if (l->action () == action) {
- list.erase (l);
+ parent.children.erase (l);
} else {
- do_delete_items (l->children, action);
+ do_delete_items (*l, action);
+ if (l->remove_on_empty () && l->children.empty ()) {
+ parent.children.erase (l);
+ }
}
l = ll;
}
@@ -1032,7 +1085,7 @@ static void do_delete_items (std::list &list, const Action &ac
void
AbstractMenu::delete_items (const Action &action)
{
- do_delete_items (m_root.children, action);
+ do_delete_items (m_root, action);
emit changed ();
}
@@ -1099,14 +1152,17 @@ AbstractMenu::find_item_exact (const std::string &path)
return item;
}
-std::pair::iterator>
-AbstractMenu::find_item (const std::string &path)
+std::vector::iterator> >
+AbstractMenu::find_item (const std::string &p)
{
- tl::Extractor extr (path.c_str ());
+ typedef std::vector::iterator> > path_type;
+ path_type path;
+
+ tl::Extractor extr (p.c_str ());
AbstractMenuItem *parent = &m_root;
std::list::iterator iter = m_root.children.end ();
- while (true) {
+ while (parent && ! extr.at_end ()) {
if (extr.test ("#")) {
@@ -1118,70 +1174,119 @@ AbstractMenu::find_item (const std::string &path)
++iter;
}
if (n > 0) {
- return std::make_pair ((AbstractMenuItem *) 0, m_root.children.end ());
+ return path_type ();
}
+ } else if (extr.test ("begin")) {
+
+ iter = parent->children.begin ();
+
+ } else if (extr.test ("end")) {
+
+ iter = parent->children.end ();
+
} else {
- std::string n;
- extr.read (n, ".+");
+ std::string n, nn;
- if (n == "begin") {
- return std::make_pair (parent, parent->children.begin ());
- } else if (n == "end") {
- return std::make_pair (parent, parent->children.end ());
+ extr.read (n, ".+>(");
+
+ if (extr.test (">")) {
+ extr.read (nn, ".+>(");
}
std::string name (parent->name ());
if (! name.empty ()) {
name += ".";
}
+
+ std::string nname;
+ nname = name + nn;
name += n;
bool after = extr.test ("+");
+ std::string ndesc;
+ if (! nn.empty () && extr.test ("(")) {
+ extr.read_word_or_quoted (ndesc, " _.$");
+ extr.test (")");
+ }
+
AbstractMenuItem *p = parent;
parent = 0;
+
+ // Look for the next path item
for (std::list::iterator c = p->children.begin (); c != p->children.end (); ++c) {
-
if (c->name () == name) {
-
- if (after) {
+ if (after && nn.empty ()) {
++c;
- if (c == p->children.end ()) {
- return std::make_pair (parent, parent->children.end ());
- }
}
-
parent = p;
iter = c;
break;
+ }
+ }
+ // If that's not found, check whether we are supposed to create one:
+ // identify the insert position and create a new entry there.
+ if (! parent && ! nn.empty ()) {
+
+ if (nn == "begin") {
+ parent = p;
+ iter = parent->children.begin ();
+ } else if (nn == "end") {
+ parent = p;
+ iter = parent->children.end ();
+ } else {
+ for (std::list::iterator c = p->children.begin (); c != p->children.end (); ++c) {
+ if (c->name () == nname) {
+ if (after) {
+ ++c;
+ }
+ parent = p;
+ iter = c;
+ break;
+ }
+ }
+ }
+
+ if (parent) {
+ parent->children.insert (iter, AbstractMenuItem ());
+ --iter;
+ iter->setup_item (parent->name (), n, Action ());
+ iter->set_has_submenu ();
+ iter->set_remove_on_empty ();
+ iter->set_action_title (ndesc.empty () ? n : ndesc);
}
}
if (! parent) {
- return std::make_pair ((AbstractMenuItem *) 0, m_root.children.end ());
+ return path_type ();
}
}
- if (extr.at_end ()) {
- return std::make_pair (parent, iter);
- }
+ path.push_back (std::make_pair (parent, iter));
extr.test (".");
- parent = &*iter;
+ if (iter == parent->children.end ()) {
+ parent = 0;
+ } else {
+ parent = iter.operator-> ();
+ }
}
+ return path;
}
void
AbstractMenu::transfer (const MenuLayoutEntry *layout, AbstractMenuItem &item)
{
+ tl_assert (mp_provider != 0);
+
while (layout->name) {
item.children.push_back (AbstractMenuItem ());
diff --git a/src/laybasic/laybasic/layAbstractMenu.h b/src/laybasic/laybasic/layAbstractMenu.h
index 4b7e2dc60..171b70982 100644
--- a/src/laybasic/laybasic/layAbstractMenu.h
+++ b/src/laybasic/laybasic/layAbstractMenu.h
@@ -505,11 +505,19 @@ struct LAYBASIC_PUBLIC AbstractMenuItem
return m_has_submenu;
}
+ void set_remove_on_empty ();
+
+ bool remove_on_empty () const
+ {
+ return m_remove_on_empty;
+ }
+
std::list children;
private:
QMenu *mp_menu;
bool m_has_submenu;
+ bool m_remove_on_empty;
Action m_action;
std::string m_name;
std::string m_basename;
@@ -750,6 +758,14 @@ public:
*/
QActionGroup *make_exclusive_group (const std::string &name);
+ /**
+ * @brief Gets the root node of the menu
+ */
+ const AbstractMenuItem &root () const
+ {
+ return m_root;
+ }
+
signals:
/**
* @brief this signal is emitted whenever something changes on the menu
@@ -757,7 +773,7 @@ signals:
void changed ();
private:
- std::pair::iterator> find_item (const std::string &path);
+ std::vector::iterator> > find_item (const std::string &path);
const AbstractMenuItem *find_item_exact (const std::string &path) const;
AbstractMenuItem *find_item_exact (const std::string &path);
void transfer (const MenuLayoutEntry *layout, AbstractMenuItem &item);
diff --git a/src/laybasic/unit_tests/layAbstractMenu.cc b/src/laybasic/unit_tests/layAbstractMenu.cc
new file mode 100644
index 000000000..c5b6b2703
--- /dev/null
+++ b/src/laybasic/unit_tests/layAbstractMenu.cc
@@ -0,0 +1,128 @@
+
+/*
+
+ KLayout Layout Viewer
+ Copyright (C) 2006-2017 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 "layAbstractMenu.h"
+
+#include "tlUnitTest.h"
+
+std::string node_to_string (const lay::AbstractMenuItem &item)
+{
+ std::string s = item.name ();
+ if (! item.children.empty ()) {
+ s += "(";
+ for (std::list::const_iterator c = item.children.begin (); c != item.children.end (); ++c) {
+ if (c != item.children.begin ()) {
+ s += ",";
+ }
+ s += node_to_string (*c);
+ }
+ s += ")";
+ }
+ return s;
+}
+
+std::string menu_to_string (const lay::AbstractMenu &menu)
+{
+ return node_to_string (menu.root ());
+}
+
+TEST(1)
+{
+ lay::AbstractMenu menu (0);
+ EXPECT_EQ (menu_to_string (menu), "");
+
+ try {
+ EXPECT_EQ (menu.action ("n1").get_title (), "");
+ EXPECT_EQ (true, false);
+ } catch (...) {
+ }
+
+ EXPECT_EQ (menu.is_valid ("n1"), false);
+
+ menu.insert_menu ("end", "n1", lay::Action ("title:n1"));
+ EXPECT_EQ (menu_to_string (menu), "(n1)");
+ EXPECT_EQ (tl::join (menu.items (""), ","), "n1");
+ EXPECT_EQ (menu.is_menu ("n1"), true);
+ EXPECT_EQ (menu.action ("n1").get_title (), "title:n1");
+
+ EXPECT_EQ (menu.is_valid ("n1"), true);
+ EXPECT_EQ (menu.is_valid ("n2"), false);
+
+ menu.insert_menu ("end", "n2", lay::Action ("title:n2"));
+ EXPECT_EQ (menu_to_string (menu), "(n1,n2)");
+ EXPECT_EQ (tl::join (menu.items (""), ","), "n1,n2");
+ EXPECT_EQ (menu.is_menu ("n2"), true);
+ EXPECT_EQ (menu.action ("n2").get_title (), "title:n2");
+
+ EXPECT_EQ (menu.is_valid ("n2"), true);
+
+ menu.insert_menu ("end", "n1", lay::Action ("title:n1"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1)");
+ EXPECT_EQ (menu.is_menu ("n1"), true);
+ EXPECT_EQ (menu.action ("n1").get_title (), "title:n1");
+
+ menu.insert_item ("n1.begin", "c1", lay::Action ("title:c1"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c1))");
+ EXPECT_EQ (tl::join (menu.items ("n1"), ","), "n1.c1");
+ EXPECT_EQ (menu.action ("n1.c1").get_title (), "title:c1");
+
+ menu.insert_item ("n1.end", "c2", lay::Action ("title:c2"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c1,n1.c2))");
+ EXPECT_EQ (tl::join (menu.items ("n1"), ","), "n1.c1,n1.c2");
+ EXPECT_EQ (menu.is_menu ("n1.c2"), false);
+ EXPECT_EQ (menu.action ("n1.c2").get_title (), "title:c2");
+
+ menu.insert_item ("n1.begin", "c1", lay::Action ("title:c1a"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c1,n1.c2))");
+ EXPECT_EQ (tl::join (menu.items ("n1"), ","), "n1.c1,n1.c2");
+ EXPECT_EQ (menu.action ("n1.c1").get_title (), "title:c1a");
+
+ menu.insert_item ("n1.c1", "c3", lay::Action ("title:c3"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c3,n1.c1,n1.c2))");
+
+ menu.insert_item ("n1.c1+", "c4", lay::Action ("title:c4"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c3,n1.c1,n1.c4,n1.c2))");
+ EXPECT_EQ (menu.action ("n1.c4").get_title (), "title:c4");
+
+ menu.delete_item ("n1.c1");
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c3,n1.c4,n1.c2))");
+
+ menu.delete_item ("n1");
+ EXPECT_EQ (menu_to_string (menu), "(n2)");
+
+ menu.insert_item ("n1>end(title).end", "c1", lay::Action ("title:c1"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c1))");
+ EXPECT_EQ (menu.action ("n1.c1").get_title (), "title:c1");
+
+ menu.insert_item ("n1>end(title).end", "c2", lay::Action ("title:c2"));
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c1,n1.c2))");
+
+ menu.delete_item ("n1.c1");
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c2))");
+
+ menu.delete_item ("n1.c1");
+ EXPECT_EQ (menu_to_string (menu), "(n2,n1(n1.c2))");
+
+ menu.delete_item ("n1.c2");
+ EXPECT_EQ (menu_to_string (menu), "(n2)");
+}
+
diff --git a/src/laybasic/unit_tests/unit_tests.pro b/src/laybasic/unit_tests/unit_tests.pro
index ae6a63189..5fd39fc2f 100644
--- a/src/laybasic/unit_tests/unit_tests.pro
+++ b/src/laybasic/unit_tests/unit_tests.pro
@@ -14,6 +14,7 @@ SOURCES = \
layParsedLayerSource.cc \
layRenderer.cc \
laySnap.cc \
+ layAbstractMenu.cc
INCLUDEPATH += $$TL_INC $$LAYBASIC_INC $$DB_INC $$GSI_INC
DEPENDPATH += $$TL_INC $$LAYBASIC_INC $$DB_INC $$GSI_INC