}
/// The keyboard shortcut (usually underlined in the entry)
- QString shortcut() const
+ /// If \p first is true, return only the first character
+ /// if a multi-character string has been defined.
+ QString shortcut(bool first = false) const
{
int const index = label_.lastIndexOf('|');
- return index == -1 ? QString() : label_.mid(index + 1);
+ if (index == -1)
+ return QString();
+ QString accelerators = label_.mid(index + 1);
+ if (accelerators.size() == 1)
+ return accelerators;
+ if (first)
+ return accelerators.left(1);
+ return accelerators;
}
/// The complete label, with label and shortcut separated by a '|'
QString fulllabel() const { return label_; }
/// Checks the associated FuncRequest status before adding the
/// menu item.
void addWithStatusCheck(MenuItem const &);
- // Check whether the menu shortcuts are unique
- void checkShortcuts() const;
+ /// Check whether the shortcut of \p mi are unique and valid, and report if not
+ void checkShortcutUnique(MenuItem const & mi) const;
+ /// Return true if a \p sc is a unique shortcut
+ bool checkShortcut(QString const sc) const;
+ /// Try to find a unique shortcut from a string of alternatives
+ QString getBestShortcut(MenuItem const & mi) const;
///
void expandLastfiles();
void expandDocuments();
}
-void MenuDefinition::checkShortcuts() const
+QString MenuDefinition::getBestShortcut(MenuItem const & mi) const
{
- // This is a quadratic algorithm, but we do not care because
- // menus are short enough
- for (const_iterator it1 = begin(); it1 != end(); ++it1) {
- QString shortcut = it1->shortcut();
- if (shortcut.isEmpty())
- continue;
- if (!it1->label().contains(shortcut))
+ // This might be a string of accelerators, a single accelerator
+ // or empty
+ QString accelerators = mi.shortcut();
+ QString const label = mi.label();
+ if (accelerators.size() == 0)
+ return QString();
+ if (accelerators.size() == 1) {
+ // check and report clashes
+ checkShortcutUnique(mi);
+ return accelerators;
+ }
+ for (int i = 0; i < accelerators.length(); i++)
+ {
+ // check each character in the string
+ // and use the first that does not conflict
+ QString const sc = accelerators.at(i);
+ if (!label.contains(sc)) {
+ // invalid shortcut. Report.
LYXERR0("Menu warning: menu entry \""
- << it1->label()
+ << label
<< "\" does not contain shortcut `"
- << shortcut << "'.");
- for (const_iterator it2 = begin(); it2 != it1 ; ++it2) {
- if (!it2->shortcut().compare(shortcut, Qt::CaseInsensitive)) {
- LYXERR0("Menu warning: menu entries "
- << '"' << it1->fulllabel()
- << "\" and \"" << it2->fulllabel()
- << "\" share the same shortcut.");
- }
+ << sc << "'.");
+ continue;
+ }
+ if (checkShortcut(sc))
+ return sc;
+ }
+ // At this point, we found no non-conflicting one
+ // issue a warning and omit the accelerator
+ LYXERR0("Menu warning: All accelerators of menu entry "
+ << '"' << mi.fulllabel()
+ << "\" are already taken. Omitting shortcut.");
+ return QString();
+}
+
+
+void MenuDefinition::checkShortcutUnique(MenuItem const & mi) const
+{
+ // We know this might only be a single character or nothing
+ QString const shortcut = mi.shortcut();
+ QString const label = mi.label();
+ if (shortcut.isEmpty())
+ return;
+ if (!label.contains(shortcut))
+ // invalid shortcut
+ LYXERR0("Menu warning: menu entry \""
+ << label
+ << "\" does not contain shortcut `"
+ << shortcut << "'.");
+ for (const_iterator it = begin(); it != end() ; ++it) {
+ if (mi.fulllabel() == it->fulllabel())
+ // do not compare with itself
+ continue;
+ if (!it->shortcut().compare(shortcut, Qt::CaseInsensitive)) {
+ // conflict
+ LYXERR0("Menu warning: menu entries "
+ << '"' << it->fulllabel()
+ << "\" and \"" << mi.fulllabel()
+ << "\" share the same shortcut.");
+ }
+ }
+}
+
+
+bool MenuDefinition::checkShortcut(QString const shortcut) const
+{
+ if (shortcut.isEmpty())
+ return true;
+ for (const_iterator it = begin(); it != end(); ++it) {
+ if (it->shortcut(true).compare(shortcut, Qt::CaseInsensitive) == 0) {
+ // conflict
+ return false;
}
}
+ // no conflict
+ return true;
}
/// Get a MenuDefinition item label from the menu backend
-static QString label(MenuItem const & mi)
+static QString label(MenuItem const & mi, MenuDefinition const & menu)
{
QString label = mi.label();
label.replace("&", "&&");
- QString shortcut = mi.shortcut();
+ QString shortcut = menu.getBestShortcut(mi);
if (!shortcut.isEmpty()) {
int pos = label.indexOf(shortcut);
if (pos != -1)
qMenu->addSeparator();
break;
case MenuItem::Submenu: {
- QMenu * subMenu = qMenu->addMenu(label(m));
+ QMenu * subMenu = qMenu->addMenu(label(m, menu));
populate(subMenu, m.submenu());
subMenu->setEnabled(!subMenu->isEmpty());
break;
// FIXME: A previous comment assured that MenuItem::Command was the
// only possible case in practice, but this is wrong. It would be
// good to document which cases are actually treated here.
- qMenu->addAction(new Action(m.func(), QIcon(), label(m),
+ qMenu->addAction(new Action(m.func(), QIcon(), label(m, menu),
m.tooltip(), qMenu));
break;
}
// we do not want the menu to end with a separator
if (!tomenu.empty() && tomenu.items_.back().kind() == MenuItem::Separator)
tomenu.items_.pop_back();
-
- // Check whether the shortcuts are unique
- tomenu.checkShortcuts();
}
}
Menu * menuptr = new Menu(view, m.submenuname(), true);
- menuptr->setTitle(label(m));
+ menuptr->setTitle(label(m, menu));
#if defined(Q_OS_MAC)
// On Mac OS with Qt/Cocoa, the menu is not displayed if there is no action