klayout/src/laybasic/laybasic/layAbstractMenu.cc

1681 lines
41 KiB
C++
Raw Normal View History

/*
KLayout Layout Viewer
2020-01-05 00:59:43 +01:00
Copyright (C) 2006-2020 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"
2020-02-07 00:41:37 +01:00
#include "layDispatcher.h"
#include "layPlugin.h"
2020-02-07 00:41:37 +01:00
#include "tlExceptions.h"
#include "tlAssert.h"
#include "gtf.h"
#include "gsi.h"
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QShortcutEvent>
#include <QToolBar>
#include <QToolButton>
#include <QApplication>
#include <QMessageBox>
#include <QHBoxLayout>
#include <ctype.h>
namespace lay
{
// ---------------------------------------------------------------
#if defined(__APPLE__)
// On MacOS, the main menu bar and it's decendent children
// can't be modified using "removeAction", followrd by "addAction"
// to achieve a move operation.If we try to do so, segmentation faults happen
// in the timer event that presumably tries to merge the menu bar
// with the application menu.
// The fallback is to only allow add/delete, not move operations on the
// menu. In effect, the order of the menu items may not be the one desired
// if menus are dynamically created. However, this will only happen when
// new packages or macros are installed.
static const bool s_can_move_menu = false;
#else
static const bool s_can_move_menu = true;
#endif
// ---------------------------------------------------------------
// Serialization of key bindings and hidden menu state
std::vector<std::pair<std::string, std::string> >
unpack_key_binding (const std::string &packed)
{
tl::Extractor ex (packed.c_str ());
std::vector<std::pair<std::string, std::string> > key_bindings;
while (! ex.at_end ()) {
ex.test(";");
key_bindings.push_back (std::make_pair (std::string (), std::string ()));
ex.read_word_or_quoted (key_bindings.back ().first);
ex.test(":");
ex.read_word_or_quoted (key_bindings.back ().second);
}
return key_bindings;
}
std::string
pack_key_binding (const std::vector<std::pair<std::string, std::string> > &unpacked)
{
std::string packed;
for (std::vector<std::pair<std::string, std::string> >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) {
if (! packed.empty ()) {
packed += ";";
}
packed += tl::to_word_or_quoted_string (p->first);
packed += ":";
packed += tl::to_word_or_quoted_string (p->second);
}
return packed;
}
std::vector<std::pair<std::string, bool> >
unpack_menu_items_hidden (const std::string &packed)
{
tl::Extractor ex (packed.c_str ());
std::vector<std::pair<std::string, bool> > hidden;
while (! ex.at_end ()) {
ex.test(";");
hidden.push_back (std::make_pair (std::string (), false));
ex.read_word_or_quoted (hidden.back ().first);
ex.test(":");
ex.read (hidden.back ().second);
}
return hidden;
}
std::string
pack_menu_items_hidden (const std::vector<std::pair<std::string, bool> > &unpacked)
{
std::string packed;
for (std::vector<std::pair<std::string, bool> >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) {
if (! packed.empty ()) {
packed += ";";
}
packed += tl::to_word_or_quoted_string (p->first);
packed += ":";
packed += tl::to_string (p->second);
}
return packed;
}
// ---------------------------------------------------------------
// Helper function to parse a title with potential shortcut and
// icon specification
static void
parse_menu_title (const std::string &s, std::string &title, std::string &shortcut, std::string &icon_res, std::string &tool_tip)
{
const char *p = s.c_str ();
while (*p) {
if (*p == '\\' && p[1]) {
++p;
title += *p++;
} else if (*p == '(' || *p == '<' || *p == '{') {
break;
} else {
title += *p++;
}
}
do {
if (*p == '(') {
++p;
while (*p && *p != ')') {
shortcut += *p++;
}
if (*p == ')') {
++p;
}
} else if (*p == '{') {
++p;
while (*p && *p != '}') {
tool_tip += *p++;
}
if (*p == '}') {
++p;
}
} else if (*p == '<') {
++p;
while (*p && *p != '>') {
icon_res += *p++;
}
if (*p == '>') {
++p;
}
}
while (*p && isspace (*p)) {
++p;
}
} while (*p);
}
// ---------------------------------------------------------------
// AbstractMenuItem implementation
2020-02-10 00:07:41 +01:00
AbstractMenuItem::AbstractMenuItem (Dispatcher *dispatcher)
2020-02-13 00:16:37 +01:00
: mp_action (new Action ()), mp_dispatcher (dispatcher), m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
2020-02-10 00:07:41 +01:00
AbstractMenuItem::AbstractMenuItem (const AbstractMenuItem &item)
2020-02-13 00:16:37 +01:00
: mp_action (new Action ()), mp_dispatcher (item.dispatcher ()), m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
void
2020-02-13 00:16:37 +01:00
AbstractMenuItem::setup_item (const std::string &pn, const std::string &s, Action *a)
{
m_basename.clear ();
tl::Extractor ex (s.c_str ());
m_name = pn;
if (! m_name.empty ()) {
m_name += ".";
}
if (! ex.at_end ()) {
ex.read (m_basename, ":");
m_name += m_basename;
while (ex.test (":")) {
std::string g;
ex.read (g, ":");
m_groups.insert (g);
}
}
set_action (a, false);
}
void
2020-02-13 00:16:37 +01:00
AbstractMenuItem::set_action (Action *a, bool copy_properties)
{
2020-02-13 00:16:37 +01:00
tl_assert (a != 0);
2020-02-19 01:06:39 +01:00
a->keep ();
2020-02-13 00:16:37 +01:00
if (copy_properties && mp_action->qaction () && a->qaction ()) {
a->qaction ()->setIcon (mp_action->qaction ()->icon ());
a->qaction ()->setToolTip (mp_action->qaction ()->toolTip ());
a->qaction ()->setShortcut (mp_action->qaction ()->shortcut ());
a->qaction ()->setIconText (mp_action->qaction ()->iconText ());
}
2020-02-13 00:16:37 +01:00
bool enabled = mp_action.get () ? mp_action->is_enabled () : true;
bool visible = mp_action.get () ? mp_action->is_visible () : true;
mp_action->set_dispatcher (0);
mp_action.reset (a);
mp_action->set_enabled (enabled);
mp_action->set_visible (visible);
mp_action->set_dispatcher (mp_dispatcher);
mp_action->set_object_name (m_basename);
2020-02-13 00:16:37 +01:00
if (mp_action->menu ()) {
mp_action->menu ()->setObjectName (tl::to_qstring (m_basename));
}
}
void
AbstractMenuItem::set_action_title (const std::string &s)
{
2020-02-13 00:16:37 +01:00
mp_action->set_title (s);
}
void
AbstractMenuItem::set_has_submenu ()
{
m_has_submenu = true;
}
void
AbstractMenuItem::set_remove_on_empty ()
{
m_remove_on_empty = true;
}
// ---------------------------------------------------------------
// Action implementation
2020-02-13 00:16:37 +01:00
static std::set<Action *> *sp_actionHandles = 0;
static void
configure_action_from_title (Action *ah, const std::string &s)
{
std::string title;
std::string shortcut;
std::string res;
std::string tool_tip;
parse_menu_title (s, title, shortcut, res, tool_tip);
ah->qaction ()->setText (tl::to_qstring (title));
if (! tool_tip.empty ()) {
ah->qaction ()->setToolTip (tl::to_qstring (tool_tip));
}
if (! res.empty ()) {
ah->qaction ()->setIcon (QIcon (tl::to_qstring (res)));
}
if (! shortcut.empty ()) {
ah->set_default_shortcut (shortcut);
}
}
/**
* @brief A specialization that provides a way to catch ambiguous key shortcuts
*/
class ActionObject
: public QAction
{
public:
ActionObject (QObject *parent)
: QAction (parent)
{
static size_t s_id = 0;
m_id = ++s_id;
}
size_t id () const
{
return m_id;
}
bool event(QEvent *e)
{
if (e->type() == QEvent::Shortcut) {
QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
if (se->isAmbiguous() && sp_actionHandles) {
QString msg = QObject::tr ("Keyboard shortcut is ambiguous: ");
msg += QString (se->key ().toString ());
msg += tl::to_qstring ("\n\n");
msg += QObject::tr ("Targets of the that shortcut are:");
msg += tl::to_qstring ("\n");
2020-02-13 00:16:37 +01:00
for (std::set<Action *>::const_iterator a = sp_actionHandles->begin (); a != sp_actionHandles->end (); ++a) {
if (! (*a)->qaction ()->shortcut ().isEmpty () && (*a)->qaction ()->shortcut ().matches (se->key ()) != QKeySequence::NoMatch) {
msg += QChar (0x2022) /*bullet*/;
msg += tl::to_qstring (" ");
2020-02-13 00:16:37 +01:00
msg += (*a)->qaction ()->text ();
msg += tl::to_qstring ("\n");
}
}
QMessageBox::warning (0, QObject::tr ("Warning"), msg);
return true;
}
}
return QAction::event(e);
}
private:
size_t m_id;
};
static size_t
id_from_action (QAction *action)
{
ActionObject *ao = dynamic_cast<ActionObject *> (action);
return ao ? ao->id () : 0;
}
2020-02-13 00:16:37 +01:00
Action::Action ()
: mp_menu (0),
2020-02-10 00:07:41 +01:00
mp_action (new ActionObject (0)),
mp_dispatcher (0),
m_owned (true),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
2020-02-13 00:16:37 +01:00
sp_actionHandles = new std::set<Action *> ();
}
sp_actionHandles->insert (this);
// catch the destroyed signal to tell if the QAction object is deleted.
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
2020-02-10 00:07:41 +01:00
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
2020-02-13 00:16:37 +01:00
Action::Action (QAction *action, bool owned)
: mp_menu (0),
mp_action (action),
2020-02-10 00:07:41 +01:00
mp_dispatcher (0),
m_owned (owned),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
2020-02-13 00:16:37 +01:00
sp_actionHandles = new std::set<Action *> ();
}
sp_actionHandles->insert (this);
// catch the destroyed signal to tell if the QAction object is deleted.
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
2020-02-10 00:07:41 +01:00
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
2020-02-13 00:16:37 +01:00
Action::Action (QMenu *menu, bool owned)
: mp_menu (menu),
mp_action (menu->menuAction ()),
2020-02-10 00:07:41 +01:00
mp_dispatcher (0),
m_owned (owned),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
2020-02-13 00:16:37 +01:00
sp_actionHandles = new std::set<Action *> ();
}
sp_actionHandles->insert (this);
// catch the destroyed signal to tell if the QAction object is deleted.
2020-02-08 22:15:09 +01:00
connect (mp_menu, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
2020-02-10 00:07:41 +01:00
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
2020-02-13 00:16:37 +01:00
Action::Action (const std::string &title)
: mp_menu (0),
mp_action (new QAction (0)),
mp_dispatcher (0),
m_owned (true),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
sp_actionHandles = new std::set<Action *> ();
}
sp_actionHandles->insert (this);
configure_action_from_title (this, title);
// catch the destroyed signal to tell if the QAction object is deleted.
connect (mp_action, SIGNAL (destroyed (QObject *)), this, SLOT (destroyed (QObject *)));
connect (mp_action, SIGNAL (triggered ()), this, SLOT (qaction_triggered ()));
}
Action::~Action ()
{
if (sp_actionHandles) {
sp_actionHandles->erase (this);
if (sp_actionHandles->empty ()) {
delete sp_actionHandles;
sp_actionHandles = 0;
}
}
if (mp_menu) {
if (m_owned) {
delete mp_menu;
m_owned = false;
}
mp_menu = 0;
mp_action = 0;
} else if (mp_action) {
if (m_owned) {
delete mp_action;
m_owned = false;
}
mp_action = 0;
}
}
2020-02-10 00:07:41 +01:00
void
2020-02-13 00:16:37 +01:00
Action::set_dispatcher (Dispatcher *dispatcher)
2020-02-10 00:07:41 +01:00
{
if (mp_dispatcher != dispatcher) {
if (mp_action && m_owned) {
mp_action->setParent (dispatcher ? dispatcher->menu_parent_widget () : 0);
}
mp_dispatcher = dispatcher;
}
}
void
2020-02-13 00:16:37 +01:00
Action::qaction_triggered ()
2020-02-10 00:07:41 +01:00
{
2020-02-13 00:16:37 +01:00
BEGIN_PROTECTED
triggered ();
END_PROTECTED
2020-02-10 00:07:41 +01:00
}
void
2020-02-13 00:16:37 +01:00
Action::trigger ()
{
2020-02-13 00:16:37 +01:00
if (qaction ()) {
qaction ()->trigger ();
}
}
void
2020-02-13 00:16:37 +01:00
Action::triggered ()
{
2020-02-13 00:16:37 +01:00
// .. no action yet, the reimplementation must provide some ..
}
QAction *
2020-02-13 00:16:37 +01:00
Action::qaction () const
{
return mp_action;
}
QMenu *
2020-02-13 00:16:37 +01:00
Action::menu () const
{
return mp_menu;
}
void
2020-02-13 00:16:37 +01:00
Action::destroyed (QObject *obj)
{
2020-02-08 22:15:09 +01:00
if (obj == mp_action) {
mp_action = 0;
}
if (obj == mp_menu) {
mp_menu = 0;
mp_action = 0;
}
m_owned = false;
}
void
2020-02-13 00:16:37 +01:00
Action::set_visible (bool v)
{
if (m_visible != v) {
m_visible = v;
if (mp_action) {
mp_action->setVisible (is_effective_visible ());
mp_action->setShortcut (get_key_sequence ());
}
}
}
void
2020-02-13 00:16:37 +01:00
Action::set_hidden (bool h)
{
if (m_hidden != h) {
m_hidden = h;
if (mp_action) {
mp_action->setVisible (is_effective_visible ());
mp_action->setShortcut (get_key_sequence ());
}
}
}
bool
2020-02-13 00:16:37 +01:00
Action::is_visible () const
{
return m_visible;
}
bool
2020-02-13 00:16:37 +01:00
Action::is_hidden () const
{
return m_hidden;
}
bool
2020-02-13 00:16:37 +01:00
Action::is_effective_visible () const
{
return m_visible && !m_hidden;
}
void
2020-02-13 00:16:37 +01:00
Action::set_default_shortcut (const std::string &sc)
{
if (m_default_shortcut != sc) {
m_default_shortcut = sc;
m_default_key_sequence = QKeySequence (tl::to_qstring (sc));
if (mp_action) {
mp_action->setShortcut (get_key_sequence ());
}
}
}
void
2020-02-13 00:16:37 +01:00
Action::set_shortcut (const std::string &sc)
{
if (m_shortcut != sc) {
m_shortcut = sc;
m_no_key_sequence = (sc == Action::no_shortcut ());
m_key_sequence = m_no_key_sequence ? QKeySequence () : QKeySequence (tl::to_qstring (m_shortcut));
if (mp_action) {
mp_action->setShortcut (get_key_sequence ());
}
}
}
std::string
2020-02-13 00:16:37 +01:00
Action::get_default_shortcut () const
{
return tl::to_string (m_default_key_sequence.toString ());
}
std::string
2020-02-13 00:16:37 +01:00
Action::get_shortcut () const
{
return m_no_key_sequence ? Action::no_shortcut () : tl::to_string (m_key_sequence.toString ());
}
QKeySequence
2020-02-13 00:16:37 +01:00
Action::get_key_sequence () const
{
if (m_hidden) {
// A hidden menu item does not have a key sequence too.
return QKeySequence ();
} else if (m_no_key_sequence) {
return QKeySequence ();
} else if (m_key_sequence.isEmpty ()) {
return m_default_key_sequence;
} else {
return m_key_sequence;
}
}
QKeySequence
2020-02-13 00:16:37 +01:00
Action::get_key_sequence_for (const std::string &sc) const
{
if (m_hidden) {
// A hidden menu item does not have a key sequence too.
return QKeySequence ();
} else if (sc.empty ()) {
return m_default_key_sequence;
} else if (sc == Action::no_shortcut ()) {
return QKeySequence ();
} else {
return QKeySequence::fromString (tl::to_qstring (sc));
}
}
const std::string &
Action::no_shortcut ()
{
static const std::string no_shortcut ("none");
return no_shortcut;
}
void
Action::set_title (const std::string &t)
{
if (qaction ()) {
qaction ()->setText (tl::to_qstring (t));
}
}
std::string
Action::get_title () const
{
if (qaction ()) {
return tl::to_string (qaction ()->text ());
} else {
return std::string ();
}
}
std::string
Action::get_effective_shortcut () const
{
2020-02-13 00:16:37 +01:00
return tl::to_string (get_key_sequence ().toString ());
}
std::string
Action::get_effective_shortcut_for (const std::string &sc) const
{
2020-02-13 00:16:37 +01:00
return tl::to_string (get_key_sequence_for (sc).toString ());
}
void
Action::add_to_exclusive_group (lay::AbstractMenu *menu, const std::string &group_name)
{
2020-02-13 00:16:37 +01:00
if (qaction ()) {
menu->make_exclusive_group (group_name)->addAction (qaction ());
}
}
bool
Action::is_checkable () const
{
return qaction () && qaction ()->isCheckable ();
}
bool
Action::is_checked () const
{
return qaction () && qaction ()->isChecked ();
}
bool
Action::is_enabled () const
{
return qaction () && qaction ()->isEnabled ();
}
bool
Action::is_separator () const
{
return qaction () && qaction ()->isSeparator ();
}
void
Action::set_enabled (bool b)
{
if (qaction ()) {
qaction ()->setEnabled (b);
}
}
void
Action::set_checked (bool c)
{
if (qaction ()) {
qaction ()->setChecked (c);
}
}
void
Action::set_checkable (bool c)
{
if (qaction ()) {
qaction ()->setCheckable (c);
}
}
void
Action::set_separator (bool s)
{
if (qaction ()) {
qaction ()->setSeparator (s);
}
}
void
Action::set_icon (const std::string &filename)
{
if (qaction ()) {
if (filename.empty ()) {
qaction ()->setIcon (QIcon ());
} else {
qaction ()->setIcon (QIcon (tl::to_qstring (filename)));
}
}
}
std::string
Action::get_tool_tip () const
{
if (qaction ()) {
return tl::to_string (qaction ()->toolTip ());
} else {
return std::string ();
}
}
void
Action::set_tool_tip (const std::string &text)
{
if (qaction ()) {
if (text.empty ()) {
qaction ()->setToolTip (QString ());
} else {
qaction ()->setToolTip (tl::to_qstring (text));
}
}
}
std::string
Action::get_icon_text () const
{
if (qaction ()) {
return tl::to_string (qaction ()->iconText ());
} else {
return std::string ();
}
}
void
Action::set_icon_text (const std::string &icon_text)
{
if (qaction ()) {
if (icon_text.empty ()) {
qaction ()->setIconText (QString ());
} else {
qaction ()->setIconText (tl::to_qstring (icon_text));
}
}
}
void
Action::set_object_name (const std::string &name)
{
if (qaction ()) {
qaction ()->setObjectName (tl::to_qstring (name));
}
}
// ---------------------------------------------------------------
// ConfigureAction implementation
2020-02-13 00:16:37 +01:00
ConfigureAction::ConfigureAction ()
: Action (), m_type (ConfigureAction::setter_type)
{
// .. nothing yet ..
}
2020-02-13 00:16:37 +01:00
ConfigureAction::ConfigureAction (const std::string &cname, const std::string &cvalue)
: Action (), m_cname (cname), m_cvalue (cvalue), m_type (ConfigureAction::setter_type)
{
if (cvalue == "?") {
m_type = boolean_type;
set_checkable (true);
}
}
2020-02-13 00:16:37 +01:00
ConfigureAction::ConfigureAction (const std::string &title, const std::string &cname, const std::string &cvalue)
: Action (title), m_cname (cname), m_cvalue (cvalue), m_type (ConfigureAction::setter_type)
{
if (cvalue == "?") {
// A "?" notation indicates a boolean toogle entry
m_type = boolean_type;
set_checkable (true);
} else if (! cvalue.empty () && cvalue[0] == '?') {
// A "?value" notation indicates a choice
m_type = choice_type;
m_cvalue.erase (m_cvalue.begin (), m_cvalue.begin () + 1);
set_checkable (true);
}
}
ConfigureAction::~ConfigureAction ()
{
2020-02-13 00:16:37 +01:00
// .. nothing yet ..
}
void
ConfigureAction::triggered ()
{
2020-02-13 00:16:37 +01:00
if (dispatcher ()) {
if (m_type == boolean_type) {
m_cvalue = tl::to_string (is_checked ());
}
dispatcher ()->config_set (m_cname, m_cvalue);
}
}
void
ConfigureAction::configure (const std::string &value)
{
if (m_type == boolean_type) {
bool f = false;
tl::from_string (value, f);
set_checkable (true);
set_checked (f);
} else if (m_type == choice_type) {
set_checkable (true);
set_checked (m_cvalue == value);
}
}
// ---------------------------------------------------------------
// AbstractMenu implementation
2020-02-07 00:41:37 +01:00
AbstractMenu::AbstractMenu (Dispatcher *dispatcher)
2020-02-13 00:16:37 +01:00
: mp_dispatcher (dispatcher), m_root (dispatcher), m_config_actions_valid (false)
{
// .. nothing yet ..
}
AbstractMenu::~AbstractMenu ()
{
// .. nothing yet ..
}
QActionGroup *
AbstractMenu::make_exclusive_group (const std::string &name)
{
std::map<std::string, QActionGroup *>::const_iterator a = m_action_groups.find (name);
if (a == m_action_groups.end ()) {
QActionGroup *ag = new QActionGroup (this);
ag->setExclusive (true);
a = m_action_groups.insert (std::make_pair (name, ag)).first;
}
return a->second;
}
void
AbstractMenu::build_detached (const std::string &name, QFrame *mbar)
{
// Clean up the menu bar before rebuilding
if (mbar->layout ()) {
delete mbar->layout ();
}
QObjectList children = mbar->children ();
for (QObjectList::const_iterator c = children.begin (); c != children.end (); ++c) {
if (dynamic_cast<QToolButton *> (*c)) {
delete *c;
}
}
QHBoxLayout *menu_layout = new QHBoxLayout (mbar);
menu_layout->setMargin (0);
mbar->setLayout (menu_layout);
AbstractMenuItem *item = find_item_exact ("@@" + name);
tl_assert (item != 0);
for (std::list<AbstractMenuItem>::iterator c = item->children.begin (); c != item->children.end (); ++c) {
if (c->has_submenu ()) {
QToolButton *menu_button = new QToolButton (mbar);
menu_layout->addWidget (menu_button);
menu_button->setAutoRaise (true);
menu_button->setPopupMode (QToolButton::MenuButtonPopup);
2020-02-13 00:16:37 +01:00
menu_button->setText (tl::to_qstring (c->action ()->get_title ()));
if (c->menu () == 0) {
2020-02-08 22:15:09 +01:00
QMenu *menu = new QMenu (mp_dispatcher->menu_parent_widget ());
menu_button->setMenu (menu);
2020-02-13 00:16:37 +01:00
c->set_action (new Action (menu), true);
} else {
menu_button->setMenu (c->menu ());
}
build (c->menu (), c->children);
} else {
2020-02-13 00:16:37 +01:00
QAction *action = c->action ()->qaction ();
QToolButton *menu_button = new QToolButton (mbar);
menu_layout->addWidget (menu_button);
menu_button->setAutoRaise (true);
menu_button->setDefaultAction (action);
}
}
menu_layout->addStretch (1);
}
2020-11-13 02:04:53 +01:00
static QAction *insert_action_after (QWidget *widget, QAction *after, QAction *action)
{
QList<QAction *> actions = widget->actions ();
QAction *before = 0;
if (after == 0) {
if (! actions.isEmpty ()) {
before = actions.front ();
}
} else {
int index = actions.indexOf (after);
if (index >= 0 && index + 1 < actions.size ()) {
before = actions [index + 1];
}
}
widget->insertAction (before, action);
return action;
}
void
AbstractMenu::build (QMenuBar *mbar, QToolBar *tbar)
{
m_helper_menu_items.clear ();
2020-02-04 23:54:29 +01:00
if (tbar) {
tbar->clear ();
}
std::set<std::pair<size_t, QAction *> > present_actions;
2020-02-04 23:54:29 +01:00
if (mbar) {
QList<QAction *> a = mbar->actions ();
for (QList<QAction *>::const_iterator i = a.begin (); i != a.end (); ++i) {
present_actions.insert (std::make_pair (id_from_action (*i), *i));
}
}
2020-11-13 02:04:53 +01:00
QAction *prev_action = 0;
for (std::list<AbstractMenuItem>::iterator c = m_root.children.begin (); c != m_root.children.end (); ++c) {
if (c->has_submenu ()) {
if (c->name () == "@toolbar") {
2020-02-04 23:54:29 +01:00
if (tbar) {
build (tbar, c->children);
}
} else if (c->name ().find ("@@") == 0) {
// nothing: let build_detached build the menu
} else if (c->name ().find ("@") == 0) {
if (c->menu () == 0) {
2020-02-13 00:16:37 +01:00
QMenu *menu = new QMenu (tl::to_qstring (c->action ()->get_title ()), mp_dispatcher->menu_parent_widget ());
// HINT: it is necessary to add the menu action to a widget below the main window.
// Otherwise, the keyboard shortcuts do not work for menu items inside such a
// popup menu. It seems not to have a negative effect to add the menu to the
// main widget.
2020-02-07 00:41:37 +01:00
if (mp_dispatcher->menu_parent_widget ()) {
mp_dispatcher->menu_parent_widget ()->addAction (menu->menuAction ());
2020-02-04 23:54:29 +01:00
}
2020-02-13 00:16:37 +01:00
c->set_action (new Action (menu), true);
}
// prepare a detached menu which can be used as context menues
build (c->menu (), c->children);
2020-02-04 23:54:29 +01:00
} else if (mbar) {
if (c->menu () == 0) {
2020-02-08 22:15:09 +01:00
QMenu *menu = new QMenu (mp_dispatcher->menu_parent_widget ());
2020-02-13 00:16:37 +01:00
menu->setTitle (tl::to_qstring (c->action ()->get_title ()));
c->set_action (new Action (menu), true);
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (mbar, prev_action, menu->menuAction ());
} else {
// Move the action to the end if present in the menu already
std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.find (std::make_pair (id_from_action (c->menu ()->menuAction ()), c->menu ()->menuAction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
mbar->removeAction (a->second);
2020-11-13 02:04:53 +01:00
insert_action_after (mbar, prev_action, a->second);
}
2020-11-13 02:04:53 +01:00
prev_action = a->second;
present_actions.erase (*a);
} else {
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (mbar, prev_action, c->menu ()->menuAction ());
}
}
build (c->menu (), c->children);
}
2020-02-04 23:54:29 +01:00
} else if (mbar) {
2020-11-13 02:04:53 +01:00
// Move the action to the end if present in the menu already
2020-02-13 00:16:37 +01:00
std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.find (std::make_pair (id_from_action (c->action ()->qaction ()), c->action ()->qaction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
mbar->removeAction (a->second);
2020-11-13 02:04:53 +01:00
insert_action_after (mbar, prev_action, a->second);
}
2020-11-13 02:04:53 +01:00
prev_action = a->second;
present_actions.erase (*a);
} else {
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (mbar, prev_action, c->action ().qaction ());
}
2020-11-13 02:04:53 +01:00
}
}
// Remove all actions that have vanished
2020-02-04 23:54:29 +01:00
if (mbar) {
for (std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.begin (); a != present_actions.end (); ++a) {
mbar->removeAction (a->second);
}
}
}
void
AbstractMenu::build (QMenu *m, std::list<AbstractMenuItem> &items)
{
std::set<std::pair<size_t, QAction *> > present_actions;
QList<QAction *> a = m->actions ();
for (QList<QAction *>::const_iterator i = a.begin (); i != a.end (); ++i) {
present_actions.insert (std::make_pair (id_from_action (*i), *i));
}
2020-11-13 02:04:53 +01:00
QAction *prev_action = 0;
for (std::list<AbstractMenuItem>::iterator c = items.begin (); c != items.end (); ++c) {
if (c->has_submenu ()) {
if (! c->menu ()) {
// HINT: the action acts as a container for the title. Unfortunately, we cannot create a
// menu with a given action. The action is provided by addMenu instead.
2020-02-08 22:15:09 +01:00
QMenu *menu = new QMenu (mp_dispatcher->menu_parent_widget ());
2020-02-13 00:16:37 +01:00
menu->setTitle (tl::to_qstring (c->action ()->get_title ()));
c->set_action (new Action (menu), true);
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (m, prev_action, menu->menuAction ());
} else {
// Move the action to the end if present in the menu already
std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.find (std::make_pair (id_from_action (c->menu ()->menuAction ()), c->menu ()->menuAction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
m->removeAction (a->second);
2020-11-13 02:04:53 +01:00
insert_action_after (m, prev_action, a->second);
}
2020-11-13 02:04:53 +01:00
prev_action = a->second;
present_actions.erase (*a);
} else {
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (m, prev_action, c->menu ()->menuAction ());
}
}
build (c->menu (), c->children);
} else {
// Move the action to the end if present in the menu already
2020-02-13 00:16:37 +01:00
std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.find (std::make_pair (id_from_action (c->action ()->qaction ()), c->action ()->qaction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
m->removeAction (a->second);
2020-11-13 02:04:53 +01:00
insert_action_after (m, prev_action, a->second);
}
2020-11-13 02:04:53 +01:00
prev_action = a->second;
present_actions.erase (*a);
} else {
2020-11-13 02:04:53 +01:00
prev_action = insert_action_after (m, prev_action, c->action ().qaction ());
}
}
}
// Remove all actions that have vanished
for (std::set<std::pair<size_t, QAction *> >::iterator a = present_actions.begin (); a != present_actions.end (); ++a) {
m->removeAction (a->second);
}
}
void
AbstractMenu::build (QToolBar *t, std::list<AbstractMenuItem> &items)
{
for (std::list<AbstractMenuItem>::iterator c = items.begin (); c != items.end (); ++c) {
if (! c->children.empty ()) {
// To support tool buttons with menu we have to attach a helper menu
// item to the QAction object.
// TODO: this hurts if we use this QAction otherwise. In this case, this
// QAction would get a menu too. However, hopefully this usage is constrained
// to special toolbar buttons only.
// In order to be able to manage the QMenu ourselves, we must not give it a parent.
QMenu *menu = new QMenu (0);
m_helper_menu_items.push_back (menu); // will be owned by the stable vector
2020-02-13 00:16:37 +01:00
c->action ()->qaction ()->setMenu (menu);
t->addAction (c->action ()->qaction ());
build (menu, c->children);
} else {
2020-02-13 00:16:37 +01:00
t->addAction (c->action ()->qaction ());
}
}
}
QMenu *
AbstractMenu::detached_menu (const std::string &name)
{
AbstractMenuItem *item = find_item_exact ("@" + name);
tl_assert (item != 0);
return item->menu ();
}
QMenu *
AbstractMenu::menu (const std::string &path)
{
AbstractMenuItem *item = find_item_exact (path);
if (item) {
return item->menu ();
} else {
return 0;
}
}
bool
AbstractMenu::is_valid (const std::string &path) const
{
const AbstractMenuItem *item = find_item_exact (path);
return item != 0;
}
bool
AbstractMenu::is_menu (const std::string &path) const
{
const AbstractMenuItem *item = find_item_exact (path);
return item != 0 && item->has_submenu ();
}
bool
AbstractMenu::is_separator (const std::string &path) const
{
const AbstractMenuItem *item = find_item_exact (path);
2020-02-13 00:16:37 +01:00
return item != 0 && item->action ()->is_separator ();
}
2020-02-13 00:16:37 +01:00
const Action *AbstractMenu::action(const std::string &path) const
{
2020-02-13 00:16:37 +01:00
return (const_cast<AbstractMenu *> (this))->action (path);
}
Action *AbstractMenu::action(const std::string &path)
{
AbstractMenuItem *item = find_item_exact (path);
2020-02-19 01:06:39 +01:00
return item ? item->action () : 0;
}
std::vector<std::string>
AbstractMenu::items (const std::string &path) const
{
std::vector<std::string> res;
const AbstractMenuItem *item = find_item_exact (path);
if (item) {
res.reserve (item->children.size ());
for (std::list <AbstractMenuItem>::const_iterator c = item->children.begin (); c != item->children.end (); ++c) {
res.push_back (c->name ());
}
}
return res;
}
void
2020-02-13 00:16:37 +01:00
AbstractMenu::insert_item (const std::string &p, const std::string &name, Action *action)
{
2020-02-07 00:41:37 +01:00
tl::Extractor extr (p.c_str ());
while (! extr.at_end ()) {
2020-02-07 00:41:37 +01:00
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
path_type path = find_item (extr);
if (! path.empty ()) {
2020-02-07 00:41:37 +01:00
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::iterator iter = path.back ().second;
2020-02-07 00:41:37 +01:00
// insert the new item
2020-02-10 00:07:41 +01:00
parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
2020-02-07 00:41:37 +01:00
--iter;
2020-02-07 00:41:37 +01:00
iter->setup_item (parent->name (), name, action);
// 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;
}
2020-02-07 00:41:37 +01:00
}
}
2020-02-13 00:16:37 +01:00
emit_changed ();
}
void
AbstractMenu::insert_separator (const std::string &p, const std::string &name)
{
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
2020-02-07 00:41:37 +01:00
tl::Extractor extr (p.c_str ());
path_type path = find_item (extr);
if (! path.empty ()) {
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::iterator iter = path.back ().second;
2020-02-10 00:07:41 +01:00
parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
--iter;
2020-02-13 00:16:37 +01:00
Action *action = new Action ();
action->set_separator (true);
iter->setup_item (parent->name (), name, action);
}
2020-02-13 00:16:37 +01:00
emit_changed ();
}
void
2020-02-13 00:16:37 +01:00
AbstractMenu::insert_menu (const std::string &p, const std::string &name, Action *action)
{
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
2020-02-07 00:41:37 +01:00
tl::Extractor extr (p.c_str ());
path_type path = find_item (extr);
if (! path.empty ()) {
AbstractMenuItem *parent = path.back ().first;
std::list<AbstractMenuItem>::iterator iter = path.back ().second;
2020-02-10 00:07:41 +01:00
parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
--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;
}
}
2020-02-13 00:16:37 +01:00
emit_changed ();
}
void
AbstractMenu::insert_menu (const std::string &path, const std::string &name, const std::string &title)
{
2020-02-13 00:16:37 +01:00
insert_menu (path, name, new Action (title));
}
void
AbstractMenu::clear_menu (const std::string &p)
{
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
2020-02-07 00:41:37 +01:00
tl::Extractor extr (p.c_str ());
path_type path = find_item (extr);
if (! path.empty () && ! path.back ().second->children.empty ()) {
path.back ().second->children.clear ();
2020-02-13 00:16:37 +01:00
emit_changed ();
}
}
void
AbstractMenu::delete_item (const std::string &p)
{
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator > > path_type;
2020-02-07 00:41:37 +01:00
tl::Extractor extr (p.c_str ());
path_type path = find_item (extr);
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;
}
p->first->children.erase (p->second);
}
}
2020-02-13 00:16:37 +01:00
emit_changed ();
}
2020-02-13 00:16:37 +01:00
static void do_delete_items (AbstractMenuItem &parent, Action *action)
{
for (std::list<AbstractMenuItem>::iterator l = parent.children.begin (); l != parent.children.end (); ) {
std::list<AbstractMenuItem>::iterator ll = l;
++ll;
if (l->action () == action) {
parent.children.erase (l);
} else {
do_delete_items (*l, action);
if (l->remove_on_empty () && l->children.empty ()) {
parent.children.erase (l);
}
}
l = ll;
}
}
void
2020-02-13 00:16:37 +01:00
AbstractMenu::delete_items (Action *action)
{
2020-02-13 00:16:37 +01:00
if (action) {
do_delete_items (m_root, action);
emit_changed ();
}
}
const AbstractMenuItem *
AbstractMenu::find_item_exact (const std::string &path) const
{
return (const_cast<AbstractMenu *> (this))->find_item_exact (path);
}
AbstractMenuItem *
AbstractMenu::find_item_exact (const std::string &path)
{
tl::Extractor extr (path.c_str ());
AbstractMenuItem *item = &m_root;
while (! extr.at_end ()) {
if (extr.test ("#")) {
unsigned int n = 0;
extr.try_read (n);
std::list<AbstractMenuItem>::iterator c = item->children.begin ();
++n;
while (--n > 0 && c != item->children.end ()) {
++c;
}
if (n > 0) {
return 0;
}
item = &*c;
} else {
std::string n;
extr.read (n, ".");
std::string name (item->name ());
if (! name.empty ()) {
name += ".";
}
name += n;
AbstractMenuItem *p = item;
item = 0;
for (std::list<AbstractMenuItem>::iterator c = p->children.begin (); c != p->children.end () && item == 0; ++c) {
if (c->name () == name) {
item = &*c;
}
}
if (! item) {
return 0;
}
}
extr.test (".");
}
return item;
}
std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> >
2020-02-07 00:41:37 +01:00
AbstractMenu::find_item (tl::Extractor &extr)
{
typedef std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> > path_type;
path_type path;
AbstractMenuItem *parent = &m_root;
std::list<AbstractMenuItem>::iterator iter = m_root.children.end ();
while (parent && ! extr.at_end ()) {
2020-02-07 00:41:37 +01:00
if (extr.test (";")) {
break;
} else if (extr.test ("#")) {
unsigned int n = 0;
extr.try_read (n);
iter = parent->children.begin ();
++n;
while (--n > 0 && iter != parent->children.end ()) {
++iter;
}
if (n > 0) {
return path_type ();
}
} else {
std::string n;
2020-02-07 19:05:45 +01:00
extr.read (n, ".;+>(");
2020-02-07 01:41:03 +01:00
if (n.empty ()) {
// skip (avoids infinite loops on wrong paths)
while (! extr.at_end () && *extr != ';') {
++extr;
}
2020-02-07 01:41:03 +01:00
} else if (n == "begin") {
iter = parent->children.begin ();
} else if (n == "end") {
iter = parent->children.end ();
} else {
std::string nn;
if (extr.test (">")) {
2020-02-07 19:05:45 +01:00
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 ("(")) {
2020-02-07 19:05:45 +01:00
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 && nn.empty ()) {
++c;
}
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) {
2020-02-10 00:07:41 +01:00
parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
--iter;
2020-02-13 00:16:37 +01:00
iter->setup_item (parent->name (), n, new Action ());
iter->set_has_submenu ();
iter->set_remove_on_empty ();
iter->set_action_title (ndesc.empty () ? n : ndesc);
}
}
if (! parent) {
return path_type ();
}
}
}
path.push_back (std::make_pair (parent, iter));
extr.test (".");
if (iter == parent->children.end ()) {
parent = 0;
} else {
parent = iter.operator-> ();
}
}
return path;
}
std::vector<std::string>
AbstractMenu::group (const std::string &name) const
{
std::vector<std::string> grp;
collect_group (grp, name, m_root);
return grp;
}
2020-02-13 00:16:37 +01:00
std::vector<lay::Action *>
AbstractMenu::group_actions (const std::string &name)
{
std::vector<std::string> grp = group (name);
2020-02-13 00:16:37 +01:00
std::vector<lay::Action *> actions;
actions.reserve (grp.size ());
for (std::vector<std::string>::const_iterator g = grp.begin (); g != grp.end (); ++g) {
actions.push_back (action (*g));
}
return actions;
}
void
AbstractMenu::collect_group (std::vector<std::string> &grp, const std::string &name, const AbstractMenuItem &item) const
{
for (std::list<AbstractMenuItem>::const_iterator c = item.children.begin (); c != item.children.end (); ++c) {
if (c->groups ().find (name) != c->groups ().end ()) {
grp.push_back (c->name ());
}
collect_group (grp, name, *c);
}
}
2020-02-13 00:16:37 +01:00
void
AbstractMenu::emit_changed ()
{
m_config_actions_valid = false;
emit changed ();
}
std::vector<ConfigureAction *> AbstractMenu::configure_actions (const std::string &name)
{
if (! m_config_actions_valid) {
std::vector<lay::ConfigureAction *> ca;
collect_configure_actions (ca, m_root);
m_config_action_by_name.clear ();
for (std::vector<lay::ConfigureAction *>::const_iterator c = ca.begin (); c != ca.end (); ++c) {
m_config_action_by_name [(*c)->get_cname ()].push_back (*c);
}
m_config_actions_valid = true;
}
std::map<std::string, std::vector<lay::ConfigureAction *> >::const_iterator c = m_config_action_by_name.find (name);
return c == m_config_action_by_name.end () ? std::vector<lay::ConfigureAction *> () : c->second;
}
void
AbstractMenu::collect_configure_actions (std::vector<lay::ConfigureAction *> &ca, AbstractMenuItem &item)
{
for (std::list<AbstractMenuItem>::iterator c = item.children.begin (); c != item.children.end (); ++c) {
ConfigureAction *cca = dynamic_cast<ConfigureAction *> (c->action ());
if (cca) {
ca.push_back (cca);
}
collect_configure_actions (ca, *c);
}
}
}