New feature: allow group generation in menu paths.

This commit is contained in:
Matthias Koefferlein 2017-10-22 23:06:42 +02:00
parent ba7e9da5a6
commit 4cbfec9c35
5 changed files with 355 additions and 71 deletions

View File

@ -19,15 +19,49 @@
<p>
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:
</p>
<li>
<ul><tt>edit_menu.end</tt> is the end of the "Edit" menu </ul>
<ul><tt>edit_menu.undo</tt> is the "Undo" entry in the "Edit" menu</ul>
</li>
<p>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").
</p>
<p>
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:
</p>
<ul>
<li><tt>edit_menu.end</tt>: the macro will be inserted at the end of the "Edit" menu</li>
<li><tt>edit_menu.undo</tt>: the macro will be inserted before the "Undo" entry.</li>
</ul>
<p>
If a plus sign follows the macro path element, the new element is inserted <b>after</b>
this element. For example:
</p>
<ul>
<li><tt>edit_menu.undo+</tt>: the macro will be inserted after the "Undo" menu item.</li>
</ul>
<p>
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:
</p>
<ul>
<li><tt>edit_menu.my_group>end("My Edit Functions").end</tt>: 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".</li>
</ul>
<p>
If no macro path is specified, the macro is inserted in the "Macros" menu.

View File

@ -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<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > ins = find_item (path);
if (ins.first) {
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
path_type path = find_item (p);
if (! path.empty ()) {
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::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<AbstractMenuItem>::iterator existing = ins.first->children.begin (); existing != ins.first->children.end (); ) {
// find any items with the same name and remove them
for (std::list<AbstractMenuItem>::iterator existing = parent->children.begin (); existing != parent->children.end (); ) {
std::list<AbstractMenuItem>::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<AbstractMenuItem *, std::list<AbstractMenuItem>::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<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
path_type path = find_item (p);
if (! path.empty ()) {
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::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<AbstractMenuItem *, std::list<AbstractMenuItem>::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<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
path_type path = find_item (p);
if (! path.empty ()) {
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::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<AbstractMenuItem>::iterator existing = parent->children.begin (); existing != parent->children.end (); ) {
std::list<AbstractMenuItem>::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<AbstractMenuItem *, std::list<AbstractMenuItem>::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<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::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<AbstractMenuItem> &list, const Action &action)
static void do_delete_items (AbstractMenuItem &parent, const Action &action)
{
for (std::list<AbstractMenuItem>::iterator l = list.begin (); l != list.end (); ) {
for (std::list<AbstractMenuItem>::iterator l = parent.children.begin (); l != parent.children.end (); ) {
std::list<AbstractMenuItem>::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<AbstractMenuItem> &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<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator>
AbstractMenu::find_item (const std::string &path)
std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> >
AbstractMenu::find_item (const std::string &p)
{
tl::Extractor extr (path.c_str ());
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> > path_type;
path_type path;
tl::Extractor extr (p.c_str ());
AbstractMenuItem *parent = &m_root;
std::list<AbstractMenuItem>::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<AbstractMenuItem>::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<AbstractMenuItem>::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 ());

View File

@ -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 <AbstractMenuItem> 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<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> find_item (const std::string &path);
std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::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);

View File

@ -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<lay::AbstractMenuItem>::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)");
}

View File

@ -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