klayout/src/laybasic/laybasic/layAbstractMenu.cc

1740 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
AbstractMenuItem::AbstractMenuItem ()
: m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
AbstractMenuItem::AbstractMenuItem (const AbstractMenuItem &)
: m_has_submenu (false), m_remove_on_empty (false)
{
// ... nothing yet ..
}
void
AbstractMenuItem::setup_item (const std::string &pn, const std::string &s, const 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
AbstractMenuItem::set_action (const Action &a, bool copy_properties)
{
Action acopy = a;
if (copy_properties && m_action.qaction () && acopy.qaction ()) {
acopy.qaction ()->setIcon (m_action.qaction ()->icon ());
acopy.qaction ()->setToolTip (m_action.qaction ()->toolTip ());
acopy.qaction ()->setShortcut (m_action.qaction ()->shortcut ());
acopy.qaction ()->setIconText (m_action.qaction ()->iconText ());
}
bool enabled = m_action.is_enabled ();
bool visible = m_action.is_visible ();
m_action = acopy;
m_action.set_enabled (enabled);
m_action.set_visible (visible);
m_action.set_object_name (m_basename);
if (m_action.menu ()) {
m_action.menu ()->setObjectName (tl::to_qstring (m_basename));
}
}
void
AbstractMenuItem::set_action_title (const std::string &s)
{
m_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
static std::set<ActionHandle *> *sp_actionHandles = 0;
/**
* @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");
for (std::set<ActionHandle *>::const_iterator a = sp_actionHandles->begin (); a != sp_actionHandles->end (); ++a) {
if (! (*a)->ptr ()->shortcut ().isEmpty () && (*a)->ptr ()->shortcut ().matches (se->key ()) != QKeySequence::NoMatch) {
msg += QChar (0x2022) /*bullet*/;
msg += tl::to_qstring (" ");
msg += (*a)->ptr ()->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;
}
ActionHandle::ActionHandle (QWidget *parent)
: mp_menu (0),
mp_action (new ActionObject (parent)),
m_ref_count (0),
m_owned (true),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
sp_actionHandles = new std::set<ActionHandle *> ();
}
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 *)));
}
ActionHandle::ActionHandle (QAction *action, bool owned)
: mp_menu (0),
mp_action (action),
m_ref_count (0),
m_owned (owned),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
sp_actionHandles = new std::set<ActionHandle *> ();
}
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 *)));
}
ActionHandle::ActionHandle (QMenu *menu, bool owned)
: mp_menu (menu),
mp_action (menu->menuAction ()),
m_ref_count (0),
m_owned (owned),
m_visible (true),
m_hidden (false),
m_no_key_sequence (false)
{
if (! sp_actionHandles) {
sp_actionHandles = new std::set<ActionHandle *> ();
}
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 *)));
}
ActionHandle::~ActionHandle ()
{
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;
}
}
void
ActionHandle::add_ref ()
{
++m_ref_count;
}
void
ActionHandle::remove_ref ()
{
if (--m_ref_count == 0) {
delete this;
}
}
QAction *
ActionHandle::ptr () const
{
return mp_action;
}
QMenu *
ActionHandle::menu () const
{
return mp_menu;
}
void
ActionHandle::destroyed (QObject * /*obj*/)
{
mp_action = 0;
m_owned = false;
}
void
ActionHandle::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
ActionHandle::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
ActionHandle::is_visible () const
{
return m_visible;
}
bool
ActionHandle::is_hidden () const
{
return m_hidden;
}
bool
ActionHandle::is_effective_visible () const
{
return m_visible && !m_hidden;
}
void
ActionHandle::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
ActionHandle::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
ActionHandle::get_default_shortcut () const
{
return tl::to_string (m_default_key_sequence.toString ());
}
std::string
ActionHandle::get_shortcut () const
{
return m_no_key_sequence ? Action::no_shortcut () : tl::to_string (m_key_sequence.toString ());
}
QKeySequence
ActionHandle::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
ActionHandle::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));
}
}
// ---------------------------------------------------------------
// Action implementation
Action::Action ()
: mp_handle (0)
{
2020-02-07 00:41:37 +01:00
if (lay::Dispatcher::instance ()) {
mp_handle = new ActionHandle (lay::Dispatcher::instance ()->menu_parent_widget ());
gtf::action_connect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
mp_handle->add_ref ();
}
}
const std::string &
Action::no_shortcut ()
{
static const std::string no_shortcut ("none");
return no_shortcut;
}
Action::Action (const std::string &title)
{
2020-02-04 23:54:29 +01:00
mp_handle = AbstractMenu::create_action (title, 0);
gtf::action_connect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
mp_handle->add_ref ();
}
Action::Action (ActionHandle *handle)
{
mp_handle = handle;
gtf::action_connect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
mp_handle->add_ref ();
}
Action
Action::create_free_action (QWidget *parent)
{
return Action (new ActionHandle (parent));
}
Action::Action (const Action &action)
: QObject ()
{
mp_handle = action.mp_handle;
if (mp_handle) {
gtf::action_connect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
mp_handle->add_ref ();
}
}
Action &
Action::operator= (const Action &action)
{
if (this != &action) {
if (mp_handle) {
if (mp_handle->ptr ()) {
gtf::action_disconnect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
}
mp_handle->remove_ref ();
mp_handle = 0;
}
mp_handle = action.mp_handle;
if (mp_handle) {
gtf::action_connect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
mp_handle->add_ref ();
}
}
return *this;
}
Action::~Action ()
{
if (mp_handle) {
if (mp_handle->ptr ()) {
gtf::action_disconnect (mp_handle->ptr (), SIGNAL (triggered ()), this, SLOT (triggered_slot ()));
}
mp_handle->remove_ref ();
mp_handle = 0;
}
}
void
Action::trigger ()
{
if (qaction ()) {
qaction ()->trigger ();
}
}
void
Action::triggered ()
{
// .. no action yet, the reimplementation must provide some ..
}
void
Action::triggered_slot ()
{
BEGIN_PROTECTED
triggered ();
END_PROTECTED
}
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 ();
}
}
void
Action::set_shortcut (const std::string &s)
{
if (mp_handle) {
mp_handle->set_shortcut (s);
}
}
void
Action::set_default_shortcut (const std::string &s)
{
if (mp_handle) {
mp_handle->set_default_shortcut (s);
}
}
std::string
Action::get_effective_shortcut () const
{
if (mp_handle) {
return tl::to_string (mp_handle->get_key_sequence ().toString ());
} else {
return std::string ();
}
}
std::string
Action::get_effective_shortcut_for (const std::string &sc) const
{
if (mp_handle) {
return tl::to_string (mp_handle->get_key_sequence_for (sc).toString ());
} else {
return std::string ();
}
}
std::string
Action::get_shortcut () const
{
if (mp_handle) {
return mp_handle->get_shortcut ();
} else {
return std::string ();
}
}
std::string
Action::get_default_shortcut () const
{
if (mp_handle) {
return mp_handle->get_default_shortcut ();
} else {
return std::string ();
}
}
QAction *
Action::qaction () const
{
return mp_handle ? mp_handle->ptr () : 0;
}
QMenu *
Action::menu () const
{
return mp_handle ? mp_handle->menu () : 0;
}
void
Action::add_to_exclusive_group (lay::AbstractMenu *menu, const std::string &group_name)
{
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_visible () const
{
return mp_handle && mp_handle->is_visible ();
}
bool
Action::is_hidden () const
{
return mp_handle && mp_handle->is_hidden ();
}
bool
Action::is_effective_visible () const
{
return mp_handle && mp_handle->is_effective_visible ();
}
bool
Action::is_separator () const
{
return qaction () && qaction ()->isSeparator ();
}
void
Action::set_enabled (bool b)
{
if (qaction ()) {
qaction ()->setEnabled (b);
}
}
void
Action::set_visible (bool v)
{
if (mp_handle) {
mp_handle->set_visible (v);
}
}
void
Action::set_hidden (bool h)
{
if (mp_handle) {
mp_handle->set_hidden (h);
}
}
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-07 00:41:37 +01:00
ConfigureAction::ConfigureAction (lay::Dispatcher *dispatcher)
: Action (), m_dispatcher (dispatcher), m_type (ConfigureAction::setter_type)
{
// .. nothing yet ..
}
2020-02-07 00:41:37 +01:00
ConfigureAction::ConfigureAction (lay::Dispatcher *dispatcher, const std::string &cname, const std::string &cvalue)
: Action (), m_dispatcher (dispatcher), m_cname (cname), m_cvalue (cvalue), m_type (ConfigureAction::setter_type)
{
if (cvalue == "?") {
m_type = boolean_type;
set_checkable (true);
}
reg ();
}
2020-02-07 00:41:37 +01:00
ConfigureAction::ConfigureAction (lay::Dispatcher *dispatcher, const std::string &title, const std::string &cname, const std::string &cvalue)
: Action (title), m_dispatcher (dispatcher), 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);
}
reg ();
}
ConfigureAction::~ConfigureAction ()
{
unreg ();
}
void
ConfigureAction::triggered ()
{
if (m_type == boolean_type) {
m_cvalue = tl::to_string (is_checked ());
}
2020-02-07 00:41:37 +01:00
m_dispatcher->config_set (m_cname, m_cvalue);
}
void
ConfigureAction::reg ()
{
2020-02-07 00:41:37 +01:00
m_dispatcher->register_config_action (m_cname, this);
}
void
ConfigureAction::unreg ()
{
2020-02-07 00:41:37 +01:00
m_dispatcher->unregister_config_action (m_cname, this);
}
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
ActionHandle *
2020-02-07 00:41:37 +01:00
AbstractMenu::create_action (const std::string &s, lay::Dispatcher *dispatcher)
{
std::string title;
std::string shortcut;
std::string res;
std::string tool_tip;
parse_menu_title (s, title, shortcut, res, tool_tip);
2020-02-07 00:41:37 +01:00
ActionHandle *ah = new ActionHandle (dispatcher->menu_parent_widget ());
ah->ptr ()->setText (tl::to_qstring (title));
if (! tool_tip.empty ()) {
ah->ptr ()->setToolTip (tl::to_qstring (tool_tip));
}
if (! res.empty ()) {
ah->ptr ()->setIcon (QIcon (tl::to_qstring (res)));
}
if (! shortcut.empty ()) {
ah->set_default_shortcut (shortcut);
}
return ah;
}
2020-02-07 00:41:37 +01:00
AbstractMenu::AbstractMenu (Dispatcher *dispatcher)
: mp_dispatcher (dispatcher)
{
// .. 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);
menu_button->setText (tl::to_qstring (c->action ().get_title ()));
if (c->menu () == 0) {
QMenu *menu = new QMenu ();
menu_button->setMenu (menu);
c->set_action (Action (new ActionHandle (menu)), true);
} else {
menu_button->setMenu (c->menu ());
}
build (c->menu (), c->children);
} else {
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);
}
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));
}
}
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) {
QMenu *menu = new QMenu (tl::to_qstring (c->action ().get_title ()));
// 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
}
c->set_action (Action (new ActionHandle (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) {
QMenu *menu = new QMenu ();
menu->setTitle (tl::to_qstring (c->action ().get_title ()));
mbar->addMenu (menu);
c->set_action (Action (new ActionHandle (menu)), true);
} 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);
mbar->addMenu (c->menu ());
}
present_actions.erase (*a);
} else {
mbar->addMenu (c->menu ());
}
}
build (c->menu (), c->children);
}
2020-02-04 23:54:29 +01:00
} else if (mbar) {
// 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->action ().qaction ()), c->action ().qaction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
mbar->removeAction (a->second);
mbar->addAction (c->action ().qaction ());
}
present_actions.erase (*a);
} else {
mbar->addAction (c->action ().qaction ());
}
}
}
// 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));
}
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.
QMenu *menu = new QMenu ();
menu->setTitle (tl::to_qstring (c->action ().get_title ()));
m->addMenu (menu);
c->set_action (Action (new ActionHandle (menu)), true);
} 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);
m->addMenu (c->menu ());
}
present_actions.erase (*a);
} else {
m->addMenu (c->menu ());
}
}
build (c->menu (), c->children);
} 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->action ().qaction ()), c->action ().qaction ()));
if (a != present_actions.end ()) {
if (s_can_move_menu) {
m->removeAction (a->second);
m->addAction (c->action ().qaction ());
}
present_actions.erase (*a);
} else {
m->addAction (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
c->action ().qaction ()->setMenu (menu);
t->addAction (c->action ().qaction ());
build (menu, c->children);
} else {
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);
return item != 0 && item->action ().is_separator ();
}
Action
AbstractMenu::action (const std::string &path) const
{
const AbstractMenuItem *item = find_item_exact (path);
if (! item) {
throw tl::Exception (tl::to_string (QObject::tr ("Not a valid menu item path: ")) + path);
}
return item->action ();
}
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
AbstractMenu::insert_item (const std::string &p, const std::string &name, const 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
parent->children.insert (iter, AbstractMenuItem ());
--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
}
}
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;
parent->children.insert (iter, AbstractMenuItem ());
--iter;
2020-02-07 00:41:37 +01:00
Action action (new ActionHandle (mp_dispatcher->menu_parent_widget ()));
action.set_separator (true);
iter->setup_item (parent->name (), name, action);
}
emit changed ();
}
void
AbstractMenu::insert_menu (const std::string &p, const std::string &name, const 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;
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 ();
}
void
AbstractMenu::insert_menu (const std::string &path, const std::string &name, const std::string &title)
{
2020-02-07 00:41:37 +01:00
insert_menu (path, name, create_action (title, mp_dispatcher));
}
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 ();
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);
}
}
emit changed ();
}
static void do_delete_items (AbstractMenuItem &parent, const 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
AbstractMenu::delete_items (const Action &action)
{
do_delete_items (m_root, action);
emit changed ();
}
const AbstractMenuItem *
AbstractMenu::find_item_exact (const std::string &path) const
{
// Hint: this const_cast saves us having to implement const and non-const versions ..
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;
extr.read (n, ".+>(");
if (n == "begin") {
iter = parent->children.begin ();
} else if (n == "end") {
iter = parent->children.end ();
} else {
std::string nn;
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 && 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) {
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 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;
}
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);
}
}
}