Gui: add ShortcutManager to unify shortcut handling

Support longest key sequence match with user defined delay (configurable
through 'Customize -> Keyboard -> Key sequence delay').

Support user defined priority to resolve shortcut conflict through
'Customize -> Keyboard')

Add 'All' category in 'Customize -> Keyboard' to list all command and
showing their shortcuts

Unify macro command shortcut setting (BaseApp/Preferences/Shortcut).
This commit is contained in:
Zheng, Lei
2022-02-28 15:32:49 +08:00
committed by wwmayer
parent 30fb0df729
commit 775452cbc0
17 changed files with 1782 additions and 763 deletions

View File

@@ -57,6 +57,7 @@
#include "WhatsThis.h"
#include "Widgets.h"
#include "Workbench.h"
#include "ShortcutManager.h"
using namespace Gui;
@@ -149,6 +150,11 @@ void Action::setEnabled(bool b)
_action->setEnabled(b);
}
bool Action::isEnabled() const
{
return _action->isEnabled();
}
void Action::setVisible(bool b)
{
_action->setVisible(b);
@@ -157,6 +163,7 @@ void Action::setVisible(bool b)
void Action::setShortcut(const QString & key)
{
_action->setShortcut(key);
setToolTip(_tooltip, _title);
}
QKeySequence Action::shortcut() const
@@ -187,6 +194,8 @@ QString Action::statusTip() const
void Action::setText(const QString & s)
{
_action->setText(s);
if (_title.isEmpty())
setToolTip(_tooltip);
}
QString Action::text() const
@@ -194,14 +203,112 @@ QString Action::text() const
return _action->text();
}
void Action::setToolTip(const QString & s)
void Action::setToolTip(const QString & s, const QString & title)
{
_action->setToolTip(s);
_tooltip = s;
_title = title;
_action->setToolTip(createToolTip(s,
title.isEmpty() ? _action->text() : title,
_action->font(),
_action->shortcut().toString(QKeySequence::NativeText),
this));
}
QString Action::createToolTip(QString _tooltip,
const QString & title,
const QFont &font,
const QString &sc,
Action *act)
{
QString text = title;
text.remove(QLatin1Char('&'));;
while(text.size() && text[text.size()-1].isPunct())
text.resize(text.size()-1);
if (text.isEmpty())
return _tooltip;
// The following code tries to make a more useful tooltip by inserting at
// the beginning of the tooltip the action title in bold followed by the
// shortcut.
//
// The long winding code is to deal with the fact that Qt will auto wrap
// a rich text tooltip but the width is too short. We can escape the auto
// wrappin using <p style='white-space:pre'>.
QString shortcut = sc;
if (shortcut.size() && _tooltip.endsWith(shortcut))
_tooltip.resize(_tooltip.size() - shortcut.size());
if (shortcut.size())
shortcut = QString::fromLatin1(" (%1)").arg(shortcut);
QString tooltip = QString::fromLatin1(
"<p style='white-space:pre; margin-bottom:0.5em;'><b>%1</b>%2</p>").arg(
text.toHtmlEscaped(), shortcut.toHtmlEscaped());
QString cmdName;
auto pcCmd = act ? act->_pcCmd : nullptr;
if (pcCmd && pcCmd->getName()) {
cmdName = QString::fromLatin1(pcCmd->getName());
if (auto groupcmd = dynamic_cast<GroupCommand*>(pcCmd)) {
int idx = act->property("defaultAction").toInt();
auto cmd = groupcmd->getCommand(idx);
if (cmd && cmd->getName())
cmdName = QStringLiteral("%1 (%2:%3)")
.arg(QString::fromLatin1(cmd->getName()))
.arg(cmdName)
.arg(idx);
}
cmdName = QStringLiteral("<p style='white-space:pre; margin-top:0.5em;'><i>%1</i></p>")
.arg(cmdName.toHtmlEscaped());
}
if (shortcut.size() && _tooltip.endsWith(shortcut))
_tooltip.resize(_tooltip.size() - shortcut.size());
if (_tooltip.isEmpty()
|| _tooltip == text
|| _tooltip == title)
{
return tooltip + cmdName;
}
if (Qt::mightBeRichText(_tooltip)) {
// already rich text, so let it be to avoid duplicated unwrapping
return tooltip + _tooltip + cmdName;
}
tooltip += QString::fromLatin1(
"<p style='white-space:pre; margin:0;'>");
// If the user supplied tooltip contains line break, we shall honour it.
if (_tooltip.indexOf(QLatin1Char('\n')) >= 0)
tooltip += _tooltip.toHtmlEscaped() + QString::fromLatin1("</p>") ;
else {
// If not, try to end the non wrapping paragraph at some pre defined
// width, so that the following text can wrap at that width.
float tipWidth = 400;
QFontMetrics fm(font);
int width = fm.width(_tooltip);
if (width <= tipWidth)
tooltip += _tooltip.toHtmlEscaped() + QString::fromLatin1("</p>") ;
else {
int index = tipWidth / width * _tooltip.size();
// Try to only break at white space
for(int i=0; i<50 && index<_tooltip.size(); ++i, ++index) {
if (_tooltip[index] == QLatin1Char(' '))
break;
}
tooltip += _tooltip.left(index).toHtmlEscaped()
+ QString::fromLatin1("</p>")
+ _tooltip.right(_tooltip.size()-index).trimmed().toHtmlEscaped();
}
}
return tooltip + cmdName;
}
QString Action::toolTip() const
{
return _action->toolTip();
return _tooltip;
}
void Action::setWhatsThis(const QString & s)
@@ -533,7 +640,7 @@ void WorkbenchGroup::addTo(QWidget *w)
auto setupBox = [&](QComboBox* box) {
box->setIconSize(QSize(16, 16));
box->setToolTip(_action->toolTip());
box->setToolTip(_tooltip);
box->setStatusTip(_action->statusTip());
box->setWhatsThis(_action->whatsThis());
box->addActions(_group->actions());
@@ -1244,4 +1351,206 @@ void WindowAction::addTo ( QWidget * w )
}
}
// --------------------------------------------------------------------
struct CmdInfo {
Command *cmd = nullptr;
QString text;
QString tooltip;
QIcon icon;
bool iconChecked = false;
};
static std::vector<CmdInfo> _Commands;
static int _CommandRevision;
class CommandModel : public QAbstractItemModel
{
public:
public:
CommandModel(QObject* parent)
: QAbstractItemModel(parent)
{
update();
}
void update()
{
auto &manager = Application::Instance->commandManager();
if (_CommandRevision == manager.getRevision())
return;
beginResetModel();
_CommandRevision = manager.getRevision();
_Commands.clear();
for (auto &v : manager.getCommands()) {
_Commands.emplace_back();
auto &info = _Commands.back();
info.cmd = v.second;
}
endResetModel();
}
virtual QModelIndex parent(const QModelIndex &) const
{
return QModelIndex();
}
virtual QVariant data(const QModelIndex & index, int role) const
{
if (index.row() < 0 || index.row() >= (int)_Commands.size())
return QVariant();
auto &info = _Commands[index.row()];
switch(role) {
case Qt::DisplayRole:
case Qt::EditRole:
if (info.text.isEmpty()) {
#if QT_VERSION>=QT_VERSION_CHECK(5,2,0)
info.text = QString::fromLatin1("%2 (%1)").arg(
QString::fromLatin1(info.cmd->getName()),
qApp->translate(info.cmd->className(), info.cmd->getMenuText()));
#else
info.text = qApp->translate(info.cmd->className(), info.cmd->getMenuText());
#endif
info.text.replace(QLatin1Char('&'), QString());
if (info.text.isEmpty())
info.text = QString::fromLatin1(info.cmd->getName());
}
return info.text;
case Qt::DecorationRole:
if (!info.iconChecked) {
info.iconChecked = true;
if(info.cmd->getPixmap())
info.icon = BitmapFactory().iconFromTheme(info.cmd->getPixmap());
}
return info.icon;
case Qt::ToolTipRole:
if (info.tooltip.isEmpty()) {
info.tooltip = QString::fromLatin1("%1: %2").arg(
QString::fromLatin1(info.cmd->getName()),
qApp->translate(info.cmd->className(), info.cmd->getMenuText()));
QString tooltip = qApp->translate(info.cmd->className(), info.cmd->getToolTipText());
if (tooltip.size())
info.tooltip += QString::fromLatin1("\n\n") + tooltip;
}
return info.tooltip;
case Qt::UserRole:
return QByteArray(info.cmd->getName());
default:
return QVariant();
}
}
virtual QModelIndex index(int row, int, const QModelIndex &) const
{
return this->createIndex(row, 0);
}
virtual int rowCount(const QModelIndex &) const
{
return (int)(_Commands.size());
}
virtual int columnCount(const QModelIndex &) const
{
return 1;
}
};
// --------------------------------------------------------------------
CommandCompleter::CommandCompleter(QLineEdit *lineedit, QObject *parent)
: QCompleter(parent)
{
this->setModel(new CommandModel(this));
#if QT_VERSION>=QT_VERSION_CHECK(5,2,0)
this->setFilterMode(Qt::MatchContains);
#endif
this->setCaseSensitivity(Qt::CaseInsensitive);
this->setCompletionMode(QCompleter::PopupCompletion);
this->setWidget(lineedit);
connect(lineedit, SIGNAL(textEdited(QString)), this, SLOT(onTextChanged(QString)));
connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onCommandActivated(QModelIndex)));
connect(this, SIGNAL(highlighted(QString)), lineedit, SLOT(setText(QString)));
}
bool CommandCompleter::eventFilter(QObject *o, QEvent *ev)
{
if (ev->type() == QEvent::KeyPress
&& (o == this->widget() || o == this->popup()))
{
QKeyEvent * ke = static_cast<QKeyEvent*>(ev);
switch(ke->key()) {
case Qt::Key_Escape: {
auto edit = qobject_cast<QLineEdit*>(this->widget());
if (edit && edit->text().size()) {
edit->setText(QString());
popup()->hide();
return true;
} else if (popup()->isVisible()) {
popup()->hide();
return true;
}
break;
}
case Qt::Key_Tab: {
if (this->popup()->isVisible()) {
QKeyEvent kevent(ke->type(),Qt::Key_Down,0);
qApp->sendEvent(this->popup(), &kevent);
return true;
}
break;
}
case Qt::Key_Backtab: {
if (this->popup()->isVisible()) {
QKeyEvent kevent(ke->type(),Qt::Key_Up,0);
qApp->sendEvent(this->popup(), &kevent);
return true;
}
break;
}
case Qt::Key_Enter:
case Qt::Key_Return:
if (o == this->widget()) {
auto index = currentIndex();
if (index.isValid())
onCommandActivated(index);
else
complete();
ev->setAccepted(true);
return true;
}
default:
break;
}
}
return QCompleter::eventFilter(o, ev);
}
void CommandCompleter::onCommandActivated(const QModelIndex &index)
{
QByteArray name = completionModel()->data(index, Qt::UserRole).toByteArray();
Q_EMIT commandActivated(name);
}
void CommandCompleter::onTextChanged(const QString &txt)
{
if (txt.size() < 3 || !widget())
return;
static_cast<CommandModel*>(this->model())->update();
this->setCompletionPrefix(txt);
QRect rect = widget()->rect();
if (rect.width() < 300)
rect.setWidth(300);
this->complete(rect);
}
#include "moc_Action.cpp"

View File

@@ -28,6 +28,7 @@
#include <QAction>
#include <QComboBox>
#include <QKeySequence>
#include <QCompleter>
namespace Gui
{
@@ -57,6 +58,7 @@ public:
void setCheckable(bool);
void setChecked (bool, bool no_signal=false);
bool isChecked() const;
bool isEnabled() const;
void setShortcut (const QString &);
QKeySequence shortcut() const;
@@ -66,7 +68,7 @@ public:
QString statusTip() const;
void setText (const QString &);
QString text() const;
void setToolTip (const QString &);
void setToolTip (const QString &, const QString &title = QString());
QString toolTip() const;
void setWhatsThis (const QString &);
QString whatsThis() const;
@@ -75,6 +77,16 @@ public:
return _action;
}
static QString createToolTip(QString tooltip,
const QString &title,
const QFont &font,
const QString &shortcut,
Action *action = nullptr);
Command *command() const {
return _pcCmd;
}
public Q_SLOTS:
virtual void onActivated ();
virtual void onToggled (bool);
@@ -82,6 +94,8 @@ public Q_SLOTS:
protected:
QAction* _action;
Command *_pcCmd;
QString _tooltip;
QString _title;
};
// --------------------------------------------------------------------
@@ -357,6 +371,26 @@ private:
QMenu* _menu;
};
/**
* Command name completer.
*/
class GuiExport CommandCompleter : public QCompleter
{
Q_OBJECT
public:
CommandCompleter(QLineEdit *edit, QObject *parent = nullptr);
Q_SIGNALS:
void commandActivated(const QByteArray &name);
protected Q_SLOTS:
void onTextChanged(const QString &);
void onCommandActivated(const QModelIndex &);
protected:
bool eventFilter(QObject *, QEvent *ev);
};
} // namespace Gui
#endif // GUI_ACTION_H

View File

@@ -384,6 +384,7 @@ SET(Command_CPP_SRCS
CommandStructure.cpp
CommandLink.cpp
CommandPyImp.cpp
ShortcutManager.cpp
)
SET(Command_SRCS
${Command_CPP_SRCS}
@@ -391,6 +392,7 @@ SET(Command_SRCS
ActionFunction.h
Command.h
CommandT.h
ShortcutManager.h
)
SOURCE_GROUP("Command" FILES ${Command_SRCS})

View File

@@ -64,6 +64,7 @@
#include "WhatsThis.h"
#include "WorkbenchManager.h"
#include "Workbench.h"
#include "ShortcutManager.h"
FC_LOG_LEVEL_INIT("Command", true, true)
@@ -161,6 +162,11 @@ CommandBase::~CommandBase()
//Note: The Action object becomes a children of MainWindow which gets destroyed _before_ the
//command manager hence before any command object. So the action pointer is a dangling pointer
//at this state.
// Command can be destroyed before the the MainWindow, for example, dynamic
// command created (and later deleted) by user for a pie menu.
if (getMainWindow())
delete _pcAction;
}
Action* CommandBase::getAction() const
@@ -226,6 +232,19 @@ Command::~Command()
{
}
void Command::setShortcut(const QString &shortcut)
{
if (_pcAction)
_pcAction->setShortcut(shortcut);
}
QString Command::getShortcut() const
{
if (_pcAction)
return _pcAction->shortcut().toString();
return ShortcutManager::instance()->getShortcut(getName());
}
bool Command::isViewOfType(Base::Type t) const
{
Gui::Document *d = getGuiApplication()->activeDocument();
@@ -244,6 +263,12 @@ void Command::addTo(QWidget *pcWidget)
{
if (!_pcAction) {
_pcAction = createAction();
#ifdef FC_DEBUG
// Accelerator conflict can now be dynamically resolved in ShortcutManager
//
// printConflictingAccelerators();
#endif
setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel()));
testActive();
}
@@ -260,6 +285,12 @@ void Command::addToGroup(ActionGroup* group)
{
if (!_pcAction) {
_pcAction = createAction();
#ifdef FC_DEBUG
// Accelerator conflict can now be dynamically resolved in ShortcutManager
//
// printConflictingAccelerators();
#endif
setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel()));
testActive();
}
group->addAction(_pcAction->findChild<QAction*>());
@@ -869,37 +900,14 @@ const char * Command::endCmdHelp()
return "</body></html>\n\n";
}
void Command::recreateTooltip(const char* context, Action* action)
void Command::applyCommandData(const char* context, Action* action)
{
QString tooltip;
tooltip.append(QString::fromLatin1("<h3>"));
tooltip.append(QCoreApplication::translate(
action->setText(QCoreApplication::translate(
context, getMenuText()));
tooltip.append(QString::fromLatin1("</h3>"));
QRegularExpression re(QString::fromLatin1("([^&])&([^&])"));
tooltip.replace(re, QString::fromLatin1("\\1\\2"));
tooltip.replace(QString::fromLatin1("&&"), QString::fromLatin1("&"));
tooltip.append(QCoreApplication::translate(
action->setToolTip(QCoreApplication::translate(
context, getToolTipText()));
tooltip.append(QString::fromLatin1("<br><i>("));
tooltip.append(QCoreApplication::translate(
action->setWhatsThis(QCoreApplication::translate(
context, getWhatsThis()));
tooltip.append(QString::fromLatin1(")</i> "));
action->setToolTip(tooltip);
QString accel = action->shortcut().toString(QKeySequence::NativeText);
if (!accel.isEmpty()) {
// show shortcut inside tooltip
QString ttip = QString::fromLatin1("%1 (%2)")
.arg(action->toolTip(), accel);
action->setToolTip(ttip);
// show shortcut inside status tip
QString stip = QString::fromLatin1("(%1)\t%2")
.arg(accel, action->statusTip());
action->setStatusTip(stip);
}
if (sStatusTip)
action->setStatusTip(QCoreApplication::translate(
context, getStatusTip()));
@@ -908,14 +916,6 @@ void Command::recreateTooltip(const char* context, Action* action)
context, getToolTipText()));
}
void Command::applyCommandData(const char* context, Action* action)
{
action->setText(QCoreApplication::translate(
context, getMenuText()));
recreateTooltip(context, action);
action->setWhatsThis(QCoreApplication::translate(
context, getWhatsThis()));
}
const char* Command::keySequenceToAccel(int sk) const
{
@@ -987,10 +987,6 @@ Action * Command::createAction()
{
Action *pcAction;
pcAction = new Action(this,getMainWindow());
#ifdef FC_DEBUG
printConflictingAccelerators();
#endif
pcAction->setShortcut(QString::fromLatin1(sAccel));
applyCommandData(this->className(), pcAction);
if (sPixmap)
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap));
@@ -1030,8 +1026,15 @@ Command *GroupCommand::addCommand(const char *name) {
return cmd;
}
Command *GroupCommand::getCommand(int idx) const
{
if (idx >= 0 && idx < (int)cmds.size())
return cmds[idx].first;
return nullptr;
}
Action * GroupCommand::createAction() {
auto pcAction = new ActionGroup(this, getMainWindow());
auto* pcAction = new ActionGroup(this, getMainWindow());
pcAction->setMenuRole(QAction::NoRole);
pcAction->setDropDownMenu(true);
pcAction->setExclusive(false);
@@ -1077,19 +1080,28 @@ void GroupCommand::languageChange() {
void GroupCommand::setup(Action *pcAction) {
pcAction->setText(QCoreApplication::translate(className(), getMenuText()));
// The tooltip for the group is the tooltip of the active tool (that is, the tool that will
// be activated when the main portion of the button is clicked).
int idx = pcAction->property("defaultAction").toInt();
if(idx>=0 && idx<(int)cmds.size() && cmds[idx].first) {
auto cmd = cmds[idx].first;
pcAction->setIcon(BitmapFactory().iconFromTheme(cmd->getPixmap()));
pcAction->setChecked(cmd->getAction()->isChecked(),true);
pcAction->setText(QCoreApplication::translate(className(), getMenuText()));
if (auto childAction = cmd->getAction())
pcAction->setIcon(childAction->icon());
else
pcAction->setIcon(BitmapFactory().iconFromTheme(cmd->getPixmap()));
const char *context = dynamic_cast<PythonCommand*>(cmd) ? cmd->getName() : cmd->className();
cmd->recreateTooltip(context, cmd->getAction());
pcAction->setToolTip(cmd->getAction()->toolTip());
pcAction->setStatusTip(cmd->getAction()->statusTip());
const char *tooltip = cmd->getToolTipText();
const char *statustip = cmd->getStatusTip();
if (!statustip || '\0' == *statustip)
statustip = tooltip;
pcAction->setToolTip(QCoreApplication::translate(context,tooltip),
QCoreApplication::translate(cmd->className(), cmd->getMenuText()));
pcAction->setStatusTip(QCoreApplication::translate(context,statustip));
} else {
applyCommandData(this->className(), pcAction);
if (sPixmap)
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap));
}
}
@@ -1157,23 +1169,6 @@ Action * MacroCommand::createAction()
pcAction->setWhatsThis(QString::fromUtf8(sWhatsThis));
if (sPixmap)
pcAction->setIcon(Gui::BitmapFactory().pixmap(sPixmap));
#ifdef FC_DEBUG
printConflictingAccelerators();
#endif
pcAction->setShortcut(QString::fromLatin1(sAccel));
QString accel = pcAction->shortcut().toString(QKeySequence::NativeText);
if (!accel.isEmpty()) {
// show shortcut inside tooltip
QString ttip = QString::fromLatin1("%1 (%2)")
.arg(pcAction->toolTip(), accel);
pcAction->setToolTip(ttip);
// show shortcut inside status tip
QString stip = QString::fromLatin1("(%1)\t%2")
.arg(accel, pcAction->statusTip());
pcAction->setStatusTip(stip);
}
return pcAction;
}
@@ -1362,10 +1357,6 @@ Action * PythonCommand::createAction()
Action *pcAction;
pcAction = new Action(this, qtAction, getMainWindow());
#ifdef FC_DEBUG
printConflictingAccelerators();
#endif
pcAction->setShortcut(QString::fromLatin1(getAccel()));
applyCommandData(this->getName(), pcAction);
if (strcmp(getResource("Pixmap"),"") != 0)
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getResource("Pixmap")));
@@ -1759,15 +1750,25 @@ CommandManager::~CommandManager()
void CommandManager::addCommand(Command* pCom)
{
_sCommands[pCom->getName()] = pCom;// pCom->Init();
auto &cmd = _sCommands[pCom->getName()];
if (cmd) {
if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG))
FC_ERR("duplicate command " << pCom->getName());
return;
}
++_revision;
cmd = pCom;
signalChanged();
}
void CommandManager::removeCommand(Command* pCom)
{
std::map <std::string,Command*>::iterator It = _sCommands.find(pCom->getName());
if (It != _sCommands.end()) {
++_revision;
delete It->second;
_sCommands.erase(It);
signalChanged();
}
}
@@ -1796,12 +1797,13 @@ std::string CommandManager::newMacroName() const
return name;
}
void CommandManager::clearCommands()
{
for ( std::map<std::string,Command*>::iterator it = _sCommands.begin(); it != _sCommands.end(); ++it )
delete it->second;
_sCommands.clear();
++_revision;
signalChanged();
}
bool CommandManager::addTo(const char* Name, QWidget *pcWidget)
@@ -1886,6 +1888,7 @@ void CommandManager::addCommandMode(const char* sContext, const char* sName)
void CommandManager::updateCommands(const char* sContext, int mode)
{
std::map<std::string, std::list<std::string> >::iterator it = _sCommandModes.find(sContext);
int rev = _revision;
if (it != _sCommandModes.end()) {
for (const auto & jt : it->second) {
Command* cmd = getCommandByName(jt.c_str());
@@ -1894,6 +1897,8 @@ void CommandManager::updateCommands(const char* sContext, int mode)
}
}
}
if (rev != _revision)
signalChanged();
}
const Command* Gui::CommandManager::checkAcceleratorForConflicts(const char* accel, const Command* ignore) const

View File

@@ -25,6 +25,10 @@
#define GUI_COMMAND_H
#include <list>
#include <map>
#include <string>
#include <vector>
#include <boost_signals2.hpp>
#include <Base/Type.h>
#include <Gui/Application.h>
@@ -568,6 +572,11 @@ public:
//@}
/// Override shortcut of this command
virtual void setShortcut (const QString &);
/// Obtain the current shortcut of this command
virtual QString getShortcut() const;
/** @name arbitrary helper methods */
//@{
void adjustCameraPosition();
@@ -641,6 +650,7 @@ public:
*/
Command *addCommand(const char *cmdName);
Command *getCommand(int idx) const;
protected:
void activated(int iMsg) override;
Gui::Action * createAction() override;
@@ -871,6 +881,12 @@ public:
void addCommandMode(const char* sContext, const char* sName);
void updateCommands(const char* sContext, int mode);
/// Return a revision number to check for addition or removal of any command
int getRevision() { return _revision; }
/// Signal on any addition or removal of command
boost::signals2::signal<void ()> signalChanged;
/**
* Returns a pointer to a conflicting command, or nullptr if there is no conflict.
* In the case of multiple conflicts, only the first is returned.
@@ -891,6 +907,8 @@ private:
void clearCommands();
std::map<std::string, Command*> _sCommands;
std::map<std::string, std::list<std::string> > _sCommandModes;
int _revision = 0;
};
} // namespace Gui

View File

@@ -38,6 +38,7 @@
// inclusion of the generated files (generated out of CommandPy.xml)
#include "CommandPy.h"
#include "CommandPy.cpp"
#include "ShortcutManager.h"
// returns a string which represents the object e.g. when printed in python
@@ -188,33 +189,8 @@ PyObject* CommandPy::setShortcut(PyObject *args)
Command* cmd = this->getCommandPtr();
if (cmd) {
Action* action = cmd->getAction();
if (action) {
QKeySequence shortcut = QString::fromLatin1(pShortcut);
QString nativeText = shortcut.toString(QKeySequence::NativeText);
action->setShortcut(nativeText);
bool success = action->shortcut() == nativeText;
/**
* avoid cluttering parameters unnecessarily by saving only
* when new shortcut is not the default shortcut
* remove spaces to handle cases such as shortcut = "C,L" or "C, L"
*/
QString default_shortcut = QString::fromLatin1(cmd->getAccel());
QString spc = QString::fromLatin1(" ");
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
const char* pName = cmd->getName();
if (success && default_shortcut.remove(spc).toUpper() != nativeText.remove(spc).toUpper()) {
hGrp->SetASCII(pName, pShortcut);
}
else {
hGrp->RemoveASCII(pName);
}
return Py::new_reference_to(Py::Boolean(success));
}
else {
return Py::new_reference_to(Py::Boolean(false));
}
ShortcutManager::instance()->setShortcut(cmd->getName(), pShortcut);
return Py::new_reference_to(Py::Boolean(true));
}
else {
PyErr_Format(Base::PyExc_FC_GeneralError, "No such command");
@@ -230,24 +206,8 @@ PyObject* CommandPy::resetShortcut(PyObject *args)
Command* cmd = this->getCommandPtr();
if (cmd) {
Action* action = cmd->getAction();
if (action){
QString default_shortcut = QString::fromLatin1(cmd->getAccel());
action->setShortcut(default_shortcut);
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
hGrp->RemoveASCII(cmd->getName());
/** test to see if we successfully reset the shortcut by loading it back and comparing */
QString spc = QString::fromLatin1(" ");
QString new_shortcut = action->shortcut().toString();
if (default_shortcut.remove(spc).toUpper() == new_shortcut.remove(spc).toUpper()){
return Py::new_reference_to(Py::Boolean(true));
} else {
return Py::new_reference_to(Py::Boolean(false));
}
} else {
return Py::new_reference_to(Py::Boolean(false));
}
ShortcutManager::instance()->reset(cmd->getName());
return Py::new_reference_to(Py::Boolean(true));
} else {
PyErr_Format(Base::PyExc_FC_GeneralError, "No such command");
return nullptr;

View File

@@ -289,10 +289,13 @@ public:
const char* className() const override
{ return "StdCmdFreezeViews"; }
void setShortcut (const QString &) override;
QString getShortcut() const override;
protected:
void activated(int iMsg) override;
bool isActive() override;
Action * createAction() override;
Action * createAction(void) override;
void languageChange() override;
private:
@@ -356,6 +359,19 @@ Action * StdCmdFreezeViews::createAction()
return pcAction;
}
void StdCmdFreezeViews::setShortcut(const QString &shortcut)
{
if (freezeView)
freezeView->setShortcut(shortcut);
}
QString StdCmdFreezeViews::getShortcut() const
{
if (freezeView)
return freezeView->shortcut().toString();
return Command::getShortcut();
}
void StdCmdFreezeViews::activated(int iMsg)
{
auto pcAction = qobject_cast<ActionGroup*>(_pcAction);

View File

@@ -39,6 +39,7 @@
#include "Application.h"
#include "BitmapFactory.h"
#include "Command.h"
#include "ShortcutManager.h"
#include "ui_DlgChooseIcon.h"
@@ -87,6 +88,8 @@ DlgCustomActionsImp::DlgCustomActionsImp( QWidget* parent )
/** Destroys the object and frees any allocated resources */
DlgCustomActionsImp::~DlgCustomActionsImp()
{
if (bChanged)
MacroCommand::save();
}
bool DlgCustomActionsImp::event(QEvent* e)
@@ -126,17 +129,18 @@ bool DlgCustomActionsImp::event(QEvent* e)
void DlgCustomActionsImp::onAddMacroAction(const QByteArray&)
{
// does nothing
bChanged = true;
}
void DlgCustomActionsImp::onRemoveMacroAction(const QByteArray&)
void DlgCustomActionsImp::onRemoveMacroAction(const QByteArray &name)
{
// does nothing
bChanged = true;
ShortcutManager::instance()->reset(name.constData());
}
void DlgCustomActionsImp::onModifyMacroAction(const QByteArray&)
{
// does nothing
bChanged = true;
}
void DlgCustomActionsImp::showActions()
@@ -192,7 +196,8 @@ void DlgCustomActionsImp::on_actionListWidget_itemActivated(QTreeWidgetItem *ite
ui->actionMenu -> setText(QString::fromUtf8(pScript->getMenuText()));
ui->actionToolTip -> setText(QString::fromUtf8(pScript->getToolTipText()));
ui->actionStatus -> setText(QString::fromUtf8(pScript->getStatusTip()));
ui->actionAccel -> setText(QString::fromLatin1(pScript->getAccel()));
ui->actionAccel -> setText(ShortcutManager::instance()->getShortcut(
actionName.constData(), pScript->getAccel()));
ui->pixmapLabel->clear();
m_sPixmap.clear();
const char* name = pScript->getPixmap();
@@ -263,7 +268,8 @@ void DlgCustomActionsImp::on_buttonAddAction_clicked()
m_sPixmap.clear();
if (!ui->actionAccel->text().isEmpty()) {
macro->setAccel(ui->actionAccel->text().toLatin1());
ShortcutManager::instance()->setShortcut(
actionName.constData(), ui->actionAccel->text().toLatin1().constData());
}
ui->actionAccel->clear();
@@ -335,20 +341,8 @@ void DlgCustomActionsImp::on_buttonReplaceAction_clicked()
action->setStatusTip(QString::fromUtf8(macro->getStatusTip()));
if (macro->getPixmap())
action->setIcon(Gui::BitmapFactory().pixmap(macro->getPixmap()));
action->setShortcut(QString::fromLatin1(macro->getAccel()));
QString accel = action->shortcut().toString(QKeySequence::NativeText);
if (!accel.isEmpty()) {
// show shortcut inside tooltip
QString ttip = QString::fromLatin1("%1 (%2)")
.arg(action->toolTip(), accel);
action->setToolTip(ttip);
// show shortcut inside status tip
QString stip = QString::fromLatin1("(%1)\t%2")
.arg(accel, action->statusTip());
action->setStatusTip(stip);
}
action->setShortcut(ShortcutManager::instance()->getShortcut(
actionName.constData(), macro->getAccel()));
}
// emit signal to notify the container widget

View File

@@ -88,6 +88,7 @@ private:
private:
std::unique_ptr<Ui_DlgCustomActions> ui;
QString m_sPixmap; /**< Name of the specified pixmap */
bool bChanged = false;
};
class Ui_DlgChooseIcon;

View File

@@ -6,225 +6,321 @@
<rect>
<x>0</x>
<y>0</y>
<width>578</width>
<height>344</height>
<width>642</width>
<height>376</height>
</rect>
</property>
<property name="windowTitle">
<string>Keyboard</string>
</property>
<layout class="QGridLayout">
<property name="margin">
<number>9</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="TextLabelCategory">
<property name="text">
<string>&amp;Category:</string>
</property>
<property name="buddy">
<cstring>categoryBox</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="categoryBox"/>
</item>
<item>
<widget class="QLabel" name="TextLabelCommands">
<property name="text">
<string>C&amp;ommands:</string>
</property>
<property name="buddy">
<cstring>commandTreeWidget</cstring>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="commandTreeWidget">
<property name="minimumSize">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="textLabelShortcut">
<property name="text">
<string>Current shortcut:</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::AccelLineEdit" name="accelLineEditShortcut">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="textLabelNewShortcut">
<property name="text">
<string>Press &amp;new shortcut:</string>
</property>
<property name="buddy">
<cstring>editShortcut</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::AccelLineEdit" name="editShortcut"/>
</item>
<item>
<widget class="QLabel" name="textLabelAssigned">
<property name="text">
<string>Currently assigned to:</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="assignedTreeWidget">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item row="0" column="2">
<spacer>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="1">
<widget class="QComboBox" name="categoryBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="TextLabelCategory">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Category:</string>
</property>
<property name="buddy">
<cstring>categoryBox</cstring>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="editCommand"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTreeWidget" name="commandTreeWidget">
<property name="minimumSize">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="label">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Multi-key sequence delay: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="textLabelShortcut">
<property name="text">
<string>Current shortcut:</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="Gui::PrefSpinBox" name="shortcutTimeout">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Time in milliseconds to wait for the next key stroke of the current key sequence.
For example, pressing 'F' twice in less than the time delay setting here will be
be treated as shorctcut key sequence 'F, F'.</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>300</number>
</property>
<property name="prefPath" stdset="0">
<cstring>Shortcut/Settings</cstring>
</property>
<property name="prefEntry" stdset="0">
<cstring>ShortcutTimeout</cstring>
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QTreeWidget" name="assignedTreeWidget">
<property name="minimumSize">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item row="1" column="2" colspan="2">
<widget class="Gui::AccelLineEdit" name="accelLineEditShortcut">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="textLabelNewShortcut">
<property name="text">
<string>&amp;New shortcut:</string>
</property>
<property name="buddy">
<cstring>editShortcut</cstring>
</property>
</widget>
</item>
<item>
<widget class="Gui::AccelLineEdit" name="editShortcut"/>
</item>
</layout>
</item>
<item row="5" column="1" colspan="3">
<widget class="QLabel" name="textLabelAssigned">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="toolTip">
<string>This list shows commands having the same shortcut in the priority from hight
to low. If more than one command with the same shortcut are active at the
same time. The one with the highest prioirty will be triggered.</string>
</property>
<property name="text">
<string>Shorcut priority list:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="buttonAssign">
<property name="text">
<string>&amp;Assign</string>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonReset">
<property name="text">
<string>&amp;Reset</string>
</property>
<property name="shortcut">
<string>Alt+R</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonResetAll">
<property name="text">
<string>Re&amp;set All</string>
</property>
<property name="shortcut">
<string>Alt+S</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>41</width>
<height>150</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDown">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Down</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item row="0" column="3">
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QPushButton" name="buttonAssign">
<property name="text">
<string>&amp;Assign</string>
<widget class="QLabel" name="texLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="shortcut">
<string>Alt+A</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonClear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonReset">
<property name="text">
<string>&amp;Reset</string>
</property>
<property name="shortcut">
<string>Alt+R</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonResetAll">
<property name="text">
<string>Re&amp;set All</string>
</property>
<property name="shortcut">
<string>Alt+S</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>41</width>
<height>150</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0" colspan="4">
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="textLabelDescriptionHeader">
<property name="text">
<string>Description:</string>
</property>
@@ -232,6 +328,12 @@
</item>
<item>
<widget class="QLabel" name="textLabelDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
@@ -239,7 +341,7 @@
</item>
</layout>
</item>
</layout>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@@ -248,14 +350,25 @@
<extends>QLineEdit</extends>
<header>Gui/Widgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefSpinBox</class>
<extends>QSpinBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>editCommand</tabstop>
<tabstop>categoryBox</tabstop>
<tabstop>commandTreeWidget</tabstop>
<tabstop>accelLineEditShortcut</tabstop>
<tabstop>buttonAssign</tabstop>
<tabstop>buttonClear</tabstop>
<tabstop>buttonReset</tabstop>
<tabstop>buttonResetAll</tabstop>
<tabstop>shortcutTimeout</tabstop>
<tabstop>assignedTreeWidget</tabstop>
<tabstop>buttonUp</tabstop>
<tabstop>buttonDown</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@@ -22,13 +22,17 @@
#include "PreCompiled.h"
#include <boost/signals2/connection.hpp>
#ifndef _PreComp_
# include <QAction>
# include <QHeaderView>
# include <QMessageBox>
# include <QTimer>
#endif
#include <Base/Parameter.h>
#include <Base/Tools.h>
#include <Base/Console.h>
#include "DlgKeyboardImp.h"
#include "ui_DlgKeyboard.h"
@@ -37,6 +41,8 @@
#include "BitmapFactory.h"
#include "Command.h"
#include "Window.h"
#include "PrefWidgets.h"
#include "ShortcutManager.h"
using namespace Gui::Dialog;
@@ -71,6 +77,231 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent )
{
ui->setupUi(this);
conn = initCommandWidgets(ui->commandTreeWidget,
ui->categoryBox,
ui->editCommand,
ui->assignedTreeWidget,
ui->buttonUp,
ui->buttonDown,
ui->editShortcut,
ui->accelLineEditShortcut);
ui->shortcutTimeout->onRestore();
QTimer *timer = new QTimer(this);
QObject::connect(ui->shortcutTimeout, QOverload<int>::of(&QSpinBox::valueChanged),
timer, [=](int) {timer->start(100);});
QObject::connect(timer, &QTimer::timeout, [=](){ui->shortcutTimeout->onSave();});
}
/** Destroys the object and frees any allocated resources */
DlgCustomKeyboardImp::~DlgCustomKeyboardImp()
{
}
void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, QComboBox *combo, QTreeWidget *commandTreeWidget)
{
edit->setPlaceholderText(tr("Type to search..."));
auto completer = new CommandCompleter(edit, edit);
QObject::connect(completer, &CommandCompleter::commandActivated,
[combo, commandTreeWidget](const QByteArray &name) {
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command *cmd = cCmdMgr.getCommandByName(name.constData());
if (!cmd)
return;
QString group = QString::fromLatin1(cmd->getGroupName());
int index = combo->findData(group);
if (index < 0)
return;
if (index != combo->currentIndex()) {
combo->setCurrentIndex(index);
combo->activated(index);
}
for (int i=0 ; i<commandTreeWidget->topLevelItemCount(); ++i) {
QTreeWidgetItem *item = commandTreeWidget->topLevelItem(i);
if (item->data(1, Qt::UserRole).toByteArray() == name) {
commandTreeWidget->setCurrentItem(item);
return;
}
}
});
}
boost::signals2::connection
DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox *combo)
{
QStringList labels;
labels << tr("Icon") << tr("Command") << tr("Shortcut") << tr("Default");
commandTreeWidget->setHeaderLabels(labels);
commandTreeWidget->setIconSize(QSize(32, 32));
commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
commandTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
commandTreeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
QObject::connect(combo, QOverload<int>::of(&QComboBox::activated), [=](int index) {
QByteArray current;
if (auto item = commandTreeWidget->currentItem())
current = item->data(1, Qt::UserRole).toByteArray();
commandTreeWidget->clear();
CommandManager & cCmdMgr = Application::Instance->commandManager();
QString group = combo->itemData(index, Qt::UserRole).toString();
auto cmds = group == QStringLiteral("All") ? cCmdMgr.getAllCommands()
: cCmdMgr.getGroupCommands(group.toLatin1());
QTreeWidgetItem *currentItem = nullptr;
for (const Command *cmd : cmds) {
QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget);
if (dynamic_cast<const MacroCommand*>(cmd)) {
item->setText(1, QString::fromUtf8(cmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(cmd->getToolTipText()));
} else {
item->setText(1, qApp->translate(cmd->className(), cmd->getMenuText()));
item->setToolTip(1, qApp->translate(cmd->className(), cmd->getToolTipText()));
}
item->setData(1, Qt::UserRole, QByteArray(cmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (auto pixmap = cmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(pixmap));
item->setText(2, cmd->getShortcut());
if (auto accel = cmd->getAccel())
item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString());
if (current == cmd->getName())
currentItem = item;
}
if (currentItem)
commandTreeWidget->setCurrentItem(currentItem);
commandTreeWidget->resizeColumnToContents(2);
commandTreeWidget->resizeColumnToContents(3);
});
QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged,
combo, [combo]() { combo->activated(combo->currentIndex()); });
populateCommandGroups(combo);
return Application::Instance->commandManager().signalChanged.connect([combo](){
if (combo) {
populateCommandGroups(combo);
combo->activated(combo->currentIndex());
}
});
}
void DlgCustomKeyboardImp::initPriorityList(QTreeWidget *priorityList,
QAbstractButton *buttonUp,
QAbstractButton *buttonDown)
{
QStringList labels;
labels << tr("Name") << tr("Title");
priorityList->setHeaderLabels(labels);
priorityList->header()->hide();
priorityList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
priorityList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
auto updatePriorityList = [priorityList](bool up) {
auto item = priorityList->currentItem();
if (!item)
return;
int index = priorityList->indexOfTopLevelItem(item);
if (index < 0)
return;
if ((index == 0 && up)
|| (index == priorityList->topLevelItemCount()-1 && !up))
return;
std::vector<QByteArray> actions;
for (int i=0; i<priorityList->topLevelItemCount(); ++i) {
auto item = priorityList->topLevelItem(i);
actions.push_back(item->data(0, Qt::UserRole).toByteArray());
}
auto it = actions.begin() + index;
auto itNext = up ? it - 1 : it + 1;
std::swap(*it, *itNext);
ShortcutManager::instance()->setPriorities(actions);
};
QObject::connect(buttonUp, &QAbstractButton::clicked, [=](){updatePriorityList(true);});
QObject::connect(buttonDown, &QAbstractButton::clicked, [=](){updatePriorityList(false);});
QObject::connect(priorityList, &QTreeWidget::currentItemChanged,
[=](QTreeWidgetItem *item){
buttonUp->setEnabled(item!=nullptr);
buttonDown->setEnabled(item!=nullptr);
}
);
}
boost::signals2::connection
DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget,
QComboBox *comboGroups,
QLineEdit *editCommand,
QTreeWidget *priorityList,
QAbstractButton *buttonUp,
QAbstractButton *buttonDown,
AccelLineEdit *editShortcut,
AccelLineEdit *currentShortcut)
{
initCommandCompleter(editCommand, comboGroups, commandTreeWidget);
auto conn = initCommandList(commandTreeWidget, comboGroups);
if (priorityList && buttonUp && buttonDown) {
initPriorityList(priorityList, buttonUp, buttonDown);
auto timer = new QTimer(priorityList);
timer->setSingleShot(true);
if (currentShortcut)
QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer](){timer->start(200);});
QObject::connect(editShortcut, &QLineEdit::textChanged, timer, [timer](){timer->start(200);});
QObject::connect(ShortcutManager::instance(), &ShortcutManager::priorityChanged, timer, [timer](){timer->start(200);});
QObject::connect(timer, &QTimer::timeout, [=](){
populatePriorityList(priorityList, editShortcut, currentShortcut);
});
}
return conn;
}
void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget *priorityList,
AccelLineEdit *editor,
AccelLineEdit *curShortcut)
{
QByteArray current;
if (auto currentItem = priorityList->currentItem())
current = currentItem->data(0, Qt::UserRole).toByteArray();
priorityList->clear();
QString sc;
if (!editor->isNone() && editor->text().size())
sc = editor->text();
else if (curShortcut && !curShortcut->isNone())
sc = curShortcut->text();
auto actionList = ShortcutManager::instance()->getActionsByShortcut(sc);
QTreeWidgetItem *currentItem = nullptr;
for (size_t i=0; i<actionList.size(); ++i) {
const auto &info = actionList[i];
if (!info.second)
continue;
QTreeWidgetItem* item = new QTreeWidgetItem(priorityList);
item->setText(0, QString::fromUtf8(info.first));
item->setText(1, info.second->text());
item->setToolTip(0, info.second->toolTip());
item->setIcon(0, info.second->icon());
item->setData(0, Qt::UserRole, info.first);
if (current == info.first)
currentItem = item;
}
priorityList->resizeColumnToContents(0);
priorityList->resizeColumnToContents(1);
if (currentItem)
priorityList->setCurrentItem(currentItem);
}
void DlgCustomKeyboardImp::populateCommandGroups(QComboBox *combo)
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::map<std::string,Command*> sCommands = cCmdMgr.getCommands();
@@ -97,27 +328,14 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent )
groupMap.push_back(std::make_pair(group, text));
}
}
groupMap.push_back(std::make_pair(QLatin1String("All"), tr("All")));
int index = 0;
for (auto it = groupMap.begin(); it != groupMap.end(); ++it, ++index) {
ui->categoryBox->addItem(it->second);
ui->categoryBox->setItemData(index, QVariant(it->first), Qt::UserRole);
for (GroupMap::iterator it = groupMap.begin(); it != groupMap.end(); ++it) {
if (combo->findData(it->first) < 0) {
combo->addItem(it->second);
combo->setItemData(combo->count()-1, QVariant(it->first), Qt::UserRole);
}
}
QStringList labels;
labels << tr("Icon") << tr("Command");
ui->commandTreeWidget->setHeaderLabels(labels);
ui->commandTreeWidget->header()->hide();
ui->commandTreeWidget->setIconSize(QSize(32, 32));
ui->commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
ui->assignedTreeWidget->setHeaderLabels(labels);
ui->assignedTreeWidget->header()->hide();
}
/** Destroys the object and frees any allocated resources */
DlgCustomKeyboardImp::~DlgCustomKeyboardImp()
{
}
void DlgCustomKeyboardImp::showEvent(QShowEvent* e)
@@ -126,7 +344,7 @@ void DlgCustomKeyboardImp::showEvent(QShowEvent* e)
// If we did this already in the constructor we wouldn't get the vertical scrollbar if needed.
// The problem was noticed with Qt 4.1.4 but may arise with any later version.
if (firstShow) {
on_categoryBox_activated(ui->categoryBox->currentIndex());
ui->categoryBox->activated(ui->categoryBox->currentIndex());
firstShow = false;
}
}
@@ -143,68 +361,29 @@ void DlgCustomKeyboardImp::on_commandTreeWidget_currentItemChanged(QTreeWidgetIt
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* cmd = cCmdMgr.getCommandByName(name.constData());
if (cmd) {
if (cmd->getAction()) {
QKeySequence ks = cmd->getAction()->shortcut();
QKeySequence ks2 = QString::fromLatin1(cmd->getAccel());
QKeySequence ks3 = ui->editShortcut->text();
QKeySequence ks = ShortcutManager::instance()->getShortcut(
cmd->getName(), cmd->getAccel());
QKeySequence ks2 = QString::fromLatin1(cmd->getAccel());
QKeySequence ks3 = ui->editShortcut->text();
if (ks.isEmpty())
ui->accelLineEditShortcut->setText( tr("none") );
else
ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText));
if (ks.isEmpty())
ui->accelLineEditShortcut->setText( tr("none") );
else
ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText));
ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3));
ui->buttonReset->setEnabled((ks != ks2));
} else {
QKeySequence ks = QString::fromLatin1(cmd->getAccel());
if (ks.isEmpty())
ui->accelLineEditShortcut->setText( tr("none") );
else
ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText));
ui->buttonAssign->setEnabled(false);
ui->buttonReset->setEnabled(false);
}
ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3));
ui->buttonReset->setEnabled((ks != ks2));
}
ui->textLabelDescription->setText(item->toolTip(1));
}
/** Shows all commands of this category */
void DlgCustomKeyboardImp::on_categoryBox_activated(int index)
void DlgCustomKeyboardImp::on_categoryBox_activated(int)
{
QVariant data = ui->categoryBox->itemData(index, Qt::UserRole);
QString group = data.toString();
ui->commandTreeWidget->clear();
ui->buttonAssign->setEnabled(false);
ui->buttonReset->setEnabled(false);
ui->accelLineEditShortcut->clear();
ui->editShortcut->clear();
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::vector<Command*> aCmds = cCmdMgr.getGroupCommands( group.toLatin1() );
if (group == QLatin1String("Macros")) {
for (const auto & aCmd : aCmds) {
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, QString::fromUtf8(aCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(aCmd->getToolTipText()));
item->setData(1, Qt::UserRole, QByteArray(aCmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (aCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(aCmd->getPixmap()));
}
}
else {
for (const auto & aCmd : aCmds) {
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, qApp->translate(aCmd->className(), aCmd->getMenuText()));
item->setToolTip(1, qApp->translate(aCmd->className(), aCmd->getToolTipText()));
item->setData(1, Qt::UserRole, QByteArray(aCmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (aCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(aCmd->getPixmap()));
}
}
}
void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText)
@@ -216,44 +395,21 @@ void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText)
QVariant data = item->data(1, Qt::UserRole);
QByteArray name = data.toByteArray(); // command name
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* cmd = cCmdMgr.getCommandByName(name.constData());
if (cmd && cmd->getAction()) {
QString nativeText;
Action* action = cmd->getAction();
if (!accelText.isEmpty()) {
QKeySequence shortcut = accelText;
nativeText = shortcut.toString(QKeySequence::NativeText);
action->setShortcut(nativeText);
ui->accelLineEditShortcut->setText(accelText);
ui->editShortcut->clear();
}
else {
action->setShortcut(QString());
ui->accelLineEditShortcut->clear();
ui->editShortcut->clear();
}
// update the tool tip (and status tip)
cmd->recreateTooltip(cmd->className(), action);
// The shortcuts for macros are store in a different location,
// also override the command's shortcut directly
if (dynamic_cast<MacroCommand*>(cmd)) {
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Macro/Macros");
if (hGrp->HasGroup(cmd->getName())) {
hGrp = hGrp->GetGroup(cmd->getName());
hGrp->SetASCII("Accel", ui->accelLineEditShortcut->text().toUtf8());
cmd->setAccel(ui->accelLineEditShortcut->text().toUtf8());
}
}
else {
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
hGrp->SetASCII(name.constData(), ui->accelLineEditShortcut->text().toUtf8());
}
ui->buttonAssign->setEnabled(false);
ui->buttonReset->setEnabled(true);
QString nativeText;
if (!accelText.isEmpty()) {
QKeySequence shortcut = accelText;
nativeText = shortcut.toString(QKeySequence::NativeText);
ui->accelLineEditShortcut->setText(accelText);
ui->editShortcut->clear();
}
else {
ui->accelLineEditShortcut->clear();
ui->editShortcut->clear();
}
ShortcutManager::instance()->setShortcut(name, nativeText.toLatin1());
ui->buttonAssign->setEnabled(false);
ui->buttonReset->setEnabled(true);
}
/** Assigns a new accelerator to the selected command. */
@@ -277,205 +433,54 @@ void DlgCustomKeyboardImp::on_buttonReset_clicked()
QVariant data = item->data(1, Qt::UserRole);
QByteArray name = data.toByteArray(); // command name
ShortcutManager::instance()->reset(name);
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* cmd = cCmdMgr.getCommandByName(name.constData());
if (cmd && cmd->getAction()) {
cmd->getAction()->setShortcut(QString::fromLatin1(cmd->getAccel()));
QString txt = cmd->getAction()->shortcut().toString(QKeySequence::NativeText);
ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt));
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
hGrp->RemoveASCII(name.constData());
// update the tool tip (and status tip)
cmd->recreateTooltip(cmd->className(), cmd->getAction());
}
QString txt = ShortcutManager::instance()->getShortcut(name);
ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt));
ui->buttonReset->setEnabled( false );
}
/** Resets the accelerator of all commands to the default. */
void DlgCustomKeyboardImp::on_buttonResetAll_clicked()
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::vector<Command*> cmds = cCmdMgr.getAllCommands();
for (const auto & cmd : cmds) {
if (cmd->getAction()) {
cmd->getAction()->setShortcut(QKeySequence(QString::fromLatin1(cmd->getAccel()))
.toString(QKeySequence::NativeText));
// update the tool tip (and status tip)
cmd->recreateTooltip(cmd->className(), cmd->getAction());
}
}
WindowParameter::getDefaultParameter()->RemoveGrp("Shortcut");
ShortcutManager::instance()->resetAll();
ui->buttonReset->setEnabled(false);
}
/** Checks for an already occupied shortcut. */
void DlgCustomKeyboardImp::on_editShortcut_textChanged(const QString& sc)
void DlgCustomKeyboardImp::on_editShortcut_textChanged(const QString& )
{
ui->assignedTreeWidget->clear();
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
if (!item)
return;
QVariant data = item->data(1, Qt::UserRole);
QByteArray name = data.toByteArray(); // command name
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* cmd = cCmdMgr.getCommandByName(name.constData());
if (cmd && !cmd->getAction()) {
Base::Console().Warning("Command %s not in use yet\n", cmd->getName());
QMessageBox::warning(this,
tr("Command not in use yet"),
tr("The command '%1' is loaded but not in use yet, so it can't be assigned a new shortcut.\n"
"To enable assignment, please make '%2' the active workbench").arg(data.toString(),ui->categoryBox->currentText()),
QMessageBox::Ok);
ui->buttonAssign->setEnabled(false); // command not in use
return;
}
ui->buttonAssign->setEnabled(true);
QKeySequence ks(sc);
if (!ks.isEmpty() && !ui->editShortcut->isNone()) {
int countAmbiguous = 0;
QString ambiguousCommand;
QString ambiguousMenu;
std::vector<Command*> ambiguousCommands;
if (item) {
QVariant data = item->data(1, Qt::UserRole);
QByteArray name = data.toByteArray(); // command name
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::vector<Command*> cmds = cCmdMgr.getAllCommands();
for (const auto & cmd : cmds) {
if (cmd->getAction()) {
// A command may have several QAction's. So, check all of them if one of them matches (See bug #0002160)
QList<QAction*> acts = cmd->getAction()->findChildren<QAction*>();
for (const auto & act : acts) {
if (act->shortcut() == ks) {
++countAmbiguous;
ambiguousCommands.push_back(cmd);
ambiguousCommand = QString::fromLatin1(cmd->getName()); // store the last one
ambiguousMenu = qApp->translate(cmd->className(), cmd->getMenuText());
Command* cmd = cCmdMgr.getCommandByName(name.constData());
auto item = new QTreeWidgetItem(ui->assignedTreeWidget);
item->setText(1, qApp->translate(cmd->className(), cmd->getMenuText()));
item->setToolTip(1, qApp->translate(cmd->className(), cmd->getToolTipText()));
item->setData(1, Qt::UserRole, QByteArray(cmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (cmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(cmd->getPixmap()));
break;
}
}
}
}
if (countAmbiguous > 0)
ui->assignedTreeWidget->resizeColumnToContents(0);
if (countAmbiguous > 1) {
QMessageBox::warning(this, tr("Multiple defined keyboard shortcut"),
tr("The keyboard shortcut '%1' is defined more than once. This could result in unexpected behaviour.").arg(sc) );
ui->editShortcut->setFocus();
ui->buttonAssign->setEnabled(false);
}
else if (countAmbiguous == 1 && ambiguousCommand != QLatin1String(name)) {
QMessageBox box(this);
box.setIcon(QMessageBox::Warning);
box.setWindowTitle(tr("Already defined keyboard shortcut"));
box.setText(tr("The keyboard shortcut '%1' is already assigned to '%2'.").arg(sc, ambiguousMenu));
box.setInformativeText(tr("Do you want to override it?"));
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box.setDefaultButton(QMessageBox::No);
box.setEscapeButton(QMessageBox::No);
int ret = box.exec();
if (ret == QMessageBox::Yes) {
for (auto* cmd : ambiguousCommands) {
Action* action = cmd->getAction();
action->setShortcut(QString());
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
hGrp->RemoveASCII(cmd->getName());
}
}
else {
ui->editShortcut->setFocus();
ui->buttonAssign->setEnabled(false);
}
}
if (!ui->editShortcut->isNone())
ui->buttonAssign->setEnabled(true);
else {
if (cmd && cmd->getAction() && cmd->getAction()->shortcut() == ks)
ui->buttonAssign->setEnabled(false);
}
}
else {
if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty())
ui->buttonAssign->setEnabled(false); // both key sequences are empty
}
}
void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray& macro)
{
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
QString group = data.toString();
if (group == QLatin1String("Macros"))
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* pCmd = cCmdMgr.getCommandByName(macro);
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, QString::fromUtf8(pCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText()));
item->setData(1, Qt::UserRole, macro);
item->setSizeHint(0, QSize(32, 32));
if (pCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap()));
}
}
void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray& macro)
{
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
QString group = data.toString();
if (group == QLatin1String("Macros"))
{
for (int i=0; i<ui->commandTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i);
QByteArray command = item->data(1, Qt::UserRole).toByteArray();
if (command == macro) {
ui->commandTreeWidget->takeTopLevelItem(i);
delete item;
break;
}
if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty())
ui->buttonAssign->setEnabled(false); // both key sequences are empty
}
}
}
void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray& macro)
void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray&)
{
}
void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray&)
{
}
void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray&)
{
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
QString group = data.toString();
if (group == QLatin1String("Macros"))
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* pCmd = cCmdMgr.getCommandByName(macro);
for (int i=0; i<ui->commandTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i);
QByteArray command = item->data(1, Qt::UserRole).toByteArray();
if (command == macro) {
item->setText(1, QString::fromUtf8(pCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText()));
item->setData(1, Qt::UserRole, macro);
item->setSizeHint(0, QSize(32, 32));
if (pCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap()));
if (item->isSelected())
ui->textLabelDescription->setText(item->toolTip(1));
break;
}
}
}
ui->categoryBox->activated(ui->categoryBox->currentIndex());
}
void DlgCustomKeyboardImp::changeEvent(QEvent *e)
@@ -493,8 +498,10 @@ void DlgCustomKeyboardImp::changeEvent(QEvent *e)
ui->categoryBox->setItemText(i, text);
}
}
on_categoryBox_activated(ui->categoryBox->currentIndex());
ui->categoryBox->activated(ui->categoryBox->currentIndex());
}
else if (e->type() == QEvent::StyleChange)
ui->categoryBox->activated(ui->categoryBox->currentIndex());
QWidget::changeEvent(e);
}

View File

@@ -24,12 +24,22 @@
#ifndef GUI_DIALOG_DLGKEYBOARD_IMP_H
#define GUI_DIALOG_DLGKEYBOARD_IMP_H
#include "PropertyPage.h"
#include <boost/signals2/connection.hpp>
#include <memory>
#include <QPointer>
#include <QAction>
#include "PropertyPage.h"
class QTreeWidget;
class QTreeWidgetItem;
class QComboBox;
class QLineEdit;
class QAbstractButton;
namespace Gui {
class AccelLineEdit;
namespace Dialog {
class Ui_DlgCustomKeyboard;
@@ -48,9 +58,44 @@ public:
explicit DlgCustomKeyboardImp( QWidget* parent = nullptr );
~DlgCustomKeyboardImp() override;
/** Public helper function for handling command widgets
*
* @param commandTreeWidget: a tree widget listing commands
* @param comboGroups: a combo box widget for choosing categories of commands
* @param editCommand: a line edit for searching command with auto complete
* @param priroityList: a tree widget listing commands with the same shortcut in order of priority
* @param buttonUp: a button widget to increase command priority
* @param buttonDown: a button widget to decrease command priority
* @param editShortcut: an accelerator editor for setting user defined shortcut
* @param currentShortcut: optional accelerator editor showing the current shortcut of a command
*
* @return Return a boost signal connection for monitoring command changes.
* Most disconnect the signal before widgets gets destroyed.
*/
static boost::signals2::connection
initCommandWidgets(QTreeWidget *commandTreeWidget,
QComboBox *comboGroups,
QLineEdit *editCommand,
QTreeWidget *priorityList = nullptr,
QAbstractButton *buttonUp = nullptr,
QAbstractButton *buttonDown = nullptr,
AccelLineEdit *editShortcut = nullptr,
AccelLineEdit *currentShortcut = nullptr);
protected:
void showEvent(QShowEvent* e) override;
/** @name Internal helper function for handling command list widgets
*/
//@{
static void initCommandCompleter(QLineEdit *, QComboBox *combo, QTreeWidget *treeWidget);
static boost::signals2::connection initCommandList(QTreeWidget *, QComboBox *combo);
static void initPriorityList(QTreeWidget *, QAbstractButton *buttonUp, QAbstractButton *buttonDown);
static void populateCommandGroups(QComboBox *);
static void populatePriorityList(QTreeWidget *priorityList,
AccelLineEdit *editor,
AccelLineEdit *current);
//@}
protected Q_SLOTS:
void on_categoryBox_activated(int index);
void on_commandTreeWidget_currentItemChanged(QTreeWidgetItem*);
@@ -70,6 +115,7 @@ protected:
private:
std::unique_ptr<Ui_DlgCustomKeyboard> ui;
bool firstShow;
boost::signals2::scoped_connection conn;
};
} // namespace Dialog

View File

@@ -31,6 +31,7 @@
#endif
#include "DlgToolbarsImp.h"
#include "DlgKeyboardImp.h"
#include "ui_DlgToolbars.h"
#include "Application.h"
#include "BitmapFactory.h"
@@ -44,20 +45,6 @@
using namespace Gui::Dialog;
namespace Gui { namespace Dialog {
using GroupMap = std::vector< std::pair<QLatin1String, QString> >;
struct GroupMap_find {
const QLatin1String& item;
explicit GroupMap_find(const QLatin1String& item) : item(item) {}
bool operator () (const std::pair<QLatin1String, QString>& elem) const
{
return elem.first == item;
}
};
}
}
/* TRANSLATOR Gui::Dialog::DlgCustomToolbars */
/**
@@ -78,43 +65,15 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent)
ui->moveActionDownButton->setIcon(BitmapFactory().iconFromTheme("button_down"));
ui->moveActionUpButton->setIcon(BitmapFactory().iconFromTheme("button_up"));
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::map<std::string,Command*> sCommands = cCmdMgr.getCommands();
conn = DlgCustomKeyboardImp::initCommandWidgets(ui->commandTreeWidget,
ui->categoryBox,
ui->editCommand);
GroupMap groupMap;
groupMap.push_back(std::make_pair(QLatin1String("File"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Edit"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("View"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Standard-View"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Tools"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Window"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Help"), QString()));
groupMap.push_back(std::make_pair(QLatin1String("Macros"), qApp->translate("Gui::MacroCommand", "Macros")));
for (const auto & sCommand : sCommands) {
QLatin1String group(sCommand.second->getGroupName());
QString text = sCommand.second->translatedGroupName();
GroupMap::iterator jt;
jt = std::find_if(groupMap.begin(), groupMap.end(), GroupMap_find(group));
if (jt != groupMap.end()) {
if (jt->second.isEmpty())
jt->second = text;
}
else {
groupMap.push_back(std::make_pair(group, text));
}
}
int index = 0;
for (auto it = groupMap.begin(); it != groupMap.end(); ++it, ++index) {
ui->categoryBox->addItem(it->second);
ui->categoryBox->setItemData(index, QVariant(it->first), Qt::UserRole);
}
// fills the combo box with all available workbenches
QStringList workbenches = Application::Instance->workbenches();
workbenches.sort();
index = 1;
int index = 1;
ui->workbenchBox->addItem(QApplication::windowIcon(), tr("Global"));
ui->workbenchBox->setItemData(0, QVariant(QString::fromLatin1("Global")), Qt::UserRole);
for (const auto & workbench : workbenches) {
@@ -131,13 +90,7 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent)
}
QStringList labels;
labels << tr("Icon") << tr("Command");
ui->commandTreeWidget->setHeaderLabels(labels);
ui->commandTreeWidget->header()->hide();
ui->commandTreeWidget->setIconSize(QSize(32, 32));
ui->commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
labels.clear(); labels << tr("Command");
labels << tr("Command");
ui->toolbarTreeWidget->setHeaderLabels(labels);
ui->toolbarTreeWidget->header()->hide();
@@ -193,43 +146,13 @@ void DlgCustomToolbars::hideEvent(QHideEvent * event)
CustomizeActionPage::hideEvent(event);
}
void DlgCustomToolbars::on_categoryBox_activated(int index)
void DlgCustomToolbars::on_categoryBox_activated(int)
{
QVariant data = ui->categoryBox->itemData(index, Qt::UserRole);
QString group = data.toString();
ui->commandTreeWidget->clear();
CommandManager & cCmdMgr = Application::Instance->commandManager();
std::vector<Command*> aCmds = cCmdMgr.getGroupCommands(group.toLatin1());
// Create a separator item
auto sepitem = new QTreeWidgetItem(ui->commandTreeWidget);
QTreeWidgetItem* sepitem = new QTreeWidgetItem;
sepitem->setText(1, tr("<Separator>"));
sepitem->setData(1, Qt::UserRole, QByteArray("Separator"));
sepitem->setSizeHint(0, QSize(32, 32));
if (group == QLatin1String("Macros")) {
for (const auto & aCmd : aCmds) {
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, QString::fromUtf8(aCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(aCmd->getToolTipText()));
item->setData(1, Qt::UserRole, QByteArray(aCmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (aCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(aCmd->getPixmap()));
}
}
else {
for (const auto & aCmd : aCmds) {
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, qApp->translate(aCmd->className(), aCmd->getMenuText()));
item->setToolTip(1, qApp->translate(aCmd->className(), aCmd->getToolTipText()));
item->setData(1, Qt::UserRole, QByteArray(aCmd->getName()));
item->setSizeHint(0, QSize(32, 32));
if (aCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(aCmd->getPixmap()));
}
}
ui->commandTreeWidget->insertTopLevelItem(0, sepitem);
}
void DlgCustomToolbars::on_workbenchBox_activated(int index)
@@ -556,41 +479,12 @@ void DlgCustomToolbars::on_renameButton_clicked()
}
}
void DlgCustomToolbars::onAddMacroAction(const QByteArray& macro)
void DlgCustomToolbars::onAddMacroAction(const QByteArray&)
{
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
QString group = data.toString();
if (group == QLatin1String("Macros"))
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* pCmd = cCmdMgr.getCommandByName(macro);
auto item = new QTreeWidgetItem(ui->commandTreeWidget);
item->setText(1, QString::fromUtf8(pCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText()));
item->setData(1, Qt::UserRole, macro);
item->setSizeHint(0, QSize(32, 32));
if (pCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap()));
}
}
void DlgCustomToolbars::onRemoveMacroAction(const QByteArray& macro)
void DlgCustomToolbars::onRemoveMacroAction(const QByteArray&)
{
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
QString group = data.toString();
if (group == QLatin1String("Macros"))
{
for (int i=0; i<ui->commandTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i);
QByteArray command = item->data(1, Qt::UserRole).toByteArray();
if (command == macro) {
ui->commandTreeWidget->takeTopLevelItem(i);
delete item;
break;
}
}
}
}
void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro)
@@ -601,20 +495,6 @@ void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro)
{
CommandManager & cCmdMgr = Application::Instance->commandManager();
Command* pCmd = cCmdMgr.getCommandByName(macro);
// the left side
for (int i=0; i<ui->commandTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i);
QByteArray command = item->data(1, Qt::UserRole).toByteArray();
if (command == macro) {
item->setText(1, QString::fromUtf8(pCmd->getMenuText()));
item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText()));
item->setData(1, Qt::UserRole, macro);
item->setSizeHint(0, QSize(32, 32));
if (pCmd->getPixmap())
item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap()));
break;
}
}
// the right side
for (int i=0; i<ui->toolbarTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* toplevel = ui->toolbarTreeWidget->topLevelItem(i);
@@ -628,6 +508,7 @@ void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro)
}
}
}
on_categoryBox_activated(ui->categoryBox->currentIndex());
}
}
@@ -648,6 +529,8 @@ void DlgCustomToolbars::changeEvent(QEvent *e)
}
on_categoryBox_activated(ui->categoryBox->currentIndex());
}
else if (e->type() == QEvent::StyleChange)
ui->categoryBox->activated(ui->categoryBox->currentIndex());
QWidget::changeEvent(e);
}

View File

@@ -24,6 +24,7 @@
#ifndef GUI_DIALOG_DLGTOOLBARS_IMP_H
#define GUI_DIALOG_DLGTOOLBARS_IMP_H
#include <boost/signals2/connection.hpp>
#include "PropertyPage.h"
#include <memory>
@@ -82,6 +83,7 @@ protected:
std::unique_ptr<Ui_DlgCustomToolbars> ui;
private:
Type type;
boost::signals2::scoped_connection conn;
};
/** This class implements the creation of user defined toolbars.

455
src/Gui/ShortcutManager.cpp Normal file
View File

@@ -0,0 +1,455 @@
/****************************************************************************
* Copyright (c) 2022 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QShortcutEvent>
# include <QApplication>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <Base/Console.h>
#include <Base/Tools.h>
#include "ShortcutManager.h"
#include "Command.h"
#include "Window.h"
#include "Action.h"
using namespace Gui;
ShortcutManager::ShortcutManager()
{
hShortcuts = WindowParameter::getDefaultParameter()->GetGroup("Shortcut");
hShortcuts->Attach(this);
hPriorities = hShortcuts->GetGroup("Priorities");
hPriorities->Attach(this);
hSetting = hShortcuts->GetGroup("Settings");
hSetting->Attach(this);
timeout = hSetting->GetInt("ShortcutTimeout", 300);
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, [this](){onTimer();});
topPriority = 0;
for (const auto &v : hPriorities->GetIntMap()) {
priorities[v.first] = v.second;
if (topPriority < v.second)
topPriority = v.second;
}
if (topPriority == 0)
topPriority = 100;
QApplication::instance()->installEventFilter(this);
}
ShortcutManager::~ShortcutManager()
{
hShortcuts->Detach(this);
hSetting->Detach(this);
hPriorities->Detach(this);
}
static ShortcutManager *Instance;
ShortcutManager *ShortcutManager::instance()
{
if (!Instance)
Instance = new ShortcutManager;
return Instance;
}
void ShortcutManager::destroy()
{
delete Instance;
Instance = nullptr;
}
void ShortcutManager::OnChange(Base::Subject<const char*> &src, const char *reason)
{
if (hSetting == &src) {
if (boost::equals(reason, "ShortcutTimeout"))
timeout = hSetting->GetInt("ShortcutTimeout");
return;
}
if (busy)
return;
if (hPriorities == &src) {
int p = hPriorities->GetInt(reason, 0);
if (p == 0)
priorities.erase(reason);
else
priorities[reason] = p;
if (topPriority < p)
topPriority = p;
priorityChanged(reason, p);
return;
}
Base::StateLocker lock(busy);
auto cmd = Application::Instance->commandManager().getCommandByName(reason);
if (cmd) {
auto accel = cmd->getAccel();
if (!accel) accel = "";
QKeySequence oldShortcut = cmd->getShortcut();
QKeySequence newShortcut = getShortcut(reason, accel);
if (oldShortcut != newShortcut) {
cmd->setShortcut(newShortcut.toString());
shortcutChanged(reason, oldShortcut);
}
}
}
void ShortcutManager::reset(const char *cmd)
{
if (cmd && cmd[0]) {
QKeySequence oldShortcut = getShortcut(cmd);
hShortcuts->RemoveASCII(cmd);
if (oldShortcut != getShortcut(cmd))
shortcutChanged(cmd, oldShortcut);
int oldPriority = getPriority(cmd);
hPriorities->RemoveInt(cmd);
if (oldPriority != getPriority(cmd))
priorityChanged(cmd, oldPriority);
}
}
void ShortcutManager::resetAll()
{
{
Base::StateLocker lock(busy);
hShortcuts->Clear();
hPriorities->Clear();
for (auto cmd : Application::Instance->commandManager().getAllCommands()) {
if (cmd->getAction()) {
auto accel = cmd->getAccel();
if (!accel) accel = "";
cmd->setShortcut(getShortcut(nullptr, accel));
}
}
}
shortcutChanged("", QKeySequence());
priorityChanged("", 0);
}
QString ShortcutManager::getShortcut(const char *cmdName, const char *accel)
{
if (!accel) {
if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) {
accel = cmd->getAccel();
if (!accel)
accel = "";
}
}
QString shortcut;
if (cmdName)
shortcut = QString::fromLatin1(hShortcuts->GetASCII(cmdName, accel).c_str());
else
shortcut = QString::fromLatin1(accel);
return QKeySequence(shortcut).toString(QKeySequence::NativeText);
}
void ShortcutManager::setShortcut(const char *cmdName, const char *accel)
{
if (cmdName && cmdName[0]) {
setTopPriority(cmdName);
if (!accel)
accel = "";
if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) {
auto defaultAccel = cmd->getAccel();
if (!defaultAccel)
defaultAccel = "";
if (QKeySequence(QString::fromLatin1(accel)) == QKeySequence(QString::fromLatin1(defaultAccel))) {
hShortcuts->RemoveASCII(cmdName);
return;
}
}
hShortcuts->SetASCII(cmdName, accel);
}
}
bool ShortcutManager::checkShortcut(QObject *o, const QKeySequence &key)
{
auto focus = QApplication::focusWidget();
if (!focus)
return false;
auto action = qobject_cast<QAction*>(o);
if (!action)
return false;
const auto &index = actionMap.get<1>();
auto iter = index.lower_bound(ActionKey(key));
if (iter == index.end())
return false;
auto it = iter;
// skip to the first not exact matched key
for (; it != index.end() && key == it->key.shortcut; ++it);
// check for potential partial match, i.e. longer key sequences
bool flush = true;
for (; it != index.end(); ++it) {
if (key.matches(it->key.shortcut) == QKeySequence::NoMatch)
break;
if (it->action && it->action->isEnabled()) {
flush = false;
break;
}
}
int count = 0;
for (it = iter; it != index.end() && key == it->key.shortcut; ++it) {
if (it->action && it->action->isEnabled()) {
if (!flush) {
// temporary disable the action so that we can try potential
// match with further keystrokes.
it->action->setEnabled(false);
}
pendingActions.emplace_back(it->action, key.count(), getPriority(it->key.name));
++count;
}
}
if (!count) {
// action not found in the map, shouldn't happen!
pendingActions.emplace_back(action, key.count(), 0);
}
if (flush) {
// We'll flush now because there is no poential match with further
// keystrokes, so no need to wait for timer.
lastFocus = nullptr;
onTimer();
return true;
}
lastFocus = focus;
pendingSequence = key;
// Qt's shortcut state machine favors shortest match (which is ridiculous,
// unless I'm mistaken?). We'll do longest match. We've disabled all
// shortcuts that can match the current key sequence. Now reply the sequence
// and wait for the next keystroke.
for (int i=0; i<key.count(); ++i) {
int k = key[i];
Qt::KeyboardModifiers modifiers;
if (k & Qt::SHIFT)
modifiers |= Qt::ShiftModifier;
if (k & Qt::CTRL)
modifiers |= Qt::ControlModifier;
if (k & Qt::ALT)
modifiers |= Qt::AltModifier;
if (k & Qt::META)
modifiers |= Qt::MetaModifier;
k &= ~(Qt::SHIFT|Qt::CTRL|Qt::ALT|Qt::META);
QKeyEvent *kev = new QKeyEvent(QEvent::KeyPress, k, modifiers, 0, 0, 0);
QApplication::postEvent(focus, kev);
kev = new QKeyEvent(QEvent::KeyRelease, k, modifiers, 0, 0, 0);
QApplication::postEvent(focus, kev);
}
timer.start(timeout);
return true;
}
bool ShortcutManager::eventFilter(QObject *o, QEvent *ev)
{
switch(ev->type()) {
case QEvent::KeyPress:
lastFocus = nullptr;
break;
case QEvent::Shortcut:
if (timeout > 0) {
auto sev = static_cast<QShortcutEvent*>(ev);
if (checkShortcut(o, sev->key())) {
// shortcut event handled here, so filter out the event
return true;
} else {
// Not handled. Clear any existing pending actions.
timer.stop();
for (const auto &info : pendingActions) {
if (info.action)
info.action->setEnabled(true);
}
pendingActions.clear();
lastFocus = nullptr;
}
}
break;
case QEvent::ActionChanged:
if (auto action = qobject_cast<QAction*>(o)) {
auto &index = actionMap.get<0>();
auto it = index.find(reinterpret_cast<intptr_t>(action));
if (action->shortcut().isEmpty()) {
if (it != index.end()) {
QKeySequence oldShortcut = it->key.shortcut;
index.erase(it);
actionShortcutChanged(action, oldShortcut);
}
break;
}
QByteArray name;
if (auto fcAction = qobject_cast<Action*>(action->parent())) {
if (fcAction->command() && fcAction->command()->getName())
name = fcAction->command()->getName();
}
if (name.isEmpty()) {
name = action->objectName().size() ?
action->objectName().toUtf8() : action->text().toUtf8();
if (name.isEmpty())
name = "~";
else
name = QByteArray("~ ") + name;
}
if (it != index.end()) {
if (it->key.shortcut == action->shortcut() && it->key.name == name)
break;
QKeySequence oldShortcut = it->key.shortcut;
index.replace(it, {action, name});
actionShortcutChanged(action, oldShortcut);
} else {
index.insert({action, name});
actionShortcutChanged(action, QKeySequence());
}
}
break;
default:
break;
}
return false;
}
std::vector<std::pair<QByteArray, QAction*>> ShortcutManager::getActionsByShortcut(const QKeySequence &shortcut)
{
const auto &index = actionMap.get<1>();
std::vector<std::pair<QByteArray, QAction*>> res;
std::multimap<int, const ActionData*, std::greater<int>> map;
for (auto it = index.lower_bound(ActionKey(shortcut)); it != index.end(); ++it) {
if (it->key.shortcut != shortcut)
break;
if (it->key.name != "~" && it->action)
map.emplace(getPriority(it->key.name), &(*it));
}
for (const auto &v : map)
res.emplace_back(v.second->key.name, v.second->action);
return res;
}
void ShortcutManager::setPriorities(const std::vector<QByteArray> &actions)
{
if (actions.empty())
return;
// Keep the same top priority of the given action, and adjust the rest. Can
// go negative if necessary
int current = 0;
for (const auto &name : actions)
current = std::max(current, getPriority(name));
if (current == 0)
current = (int)actions.size();
setPriority(actions.front(), current);
++current;
for (const auto &name : actions) {
int p = getPriority(name);
if (p <= 0 || p >= current) {
if (--current == 0)
--current;
setPriority(name, current);
} else
current = p;
}
}
int ShortcutManager::getPriority(const char *cmdName)
{
if (!cmdName)
return 0;
auto it = priorities.find(cmdName);
if (it == priorities.end())
return 0;
return it->second;
}
void ShortcutManager::setPriority(const char *cmdName, int p)
{
if (p == 0)
hPriorities->RemoveInt(cmdName);
else
hPriorities->SetInt(cmdName, p);
}
void ShortcutManager::setTopPriority(const char *cmdName)
{
++topPriority;
hPriorities->SetInt(cmdName, topPriority);
}
void ShortcutManager::onTimer()
{
QAction *found = nullptr;
int priority = -INT_MAX;
int seq_length = 0;
for (const auto &info : pendingActions) {
if (info.action) {
info.action->setEnabled(true);
if (info.seq_length > seq_length
|| (info.seq_length == seq_length
&& info.priority > priority))
{
priority = info.priority;
seq_length = info.seq_length;
found = info.action;
}
}
}
if (found)
found->activate(QAction::Trigger);
pendingActions.clear();
if (lastFocus && lastFocus == QApplication::focusWidget()) {
// We are here because we have withheld some previous triggered action.
// We then disabled the action, and faked the same key strokes in order
// to wait for more for potential match of longer key sequence. We use
// a timer to end the wait and triggered the pending action.
//
// However, Qt's internal shorcutmap state machine is still armed with
// our fake key strokes. So we try to fake some more obscure symbol key
// stroke below, hoping to reset Qt's state machine.
const auto &index = actionMap.get<1>();
static const std::string symbols = "~!@#$%^&*()_+";
QString shortcut = pendingSequence.toString() + QStringLiteral(", Ctrl+");
for (int s : symbols) {
QKeySequence k(shortcut + QLatin1Char(s));
auto it = index.lower_bound(ActionKey(k));
if (it->key.shortcut != k) {
QKeyEvent *kev = new QKeyEvent(QEvent::KeyPress, s, Qt::ControlModifier, 0, 0, 0);
QApplication::postEvent(lastFocus, kev);
kev = new QKeyEvent(QEvent::KeyRelease, s, Qt::ControlModifier, 0, 0, 0);
QApplication::postEvent(lastFocus, kev);
break;
}
}
}
timer.stop();
}
#include "moc_ShortcutManager.cpp"

171
src/Gui/ShortcutManager.h Normal file
View File

@@ -0,0 +1,171 @@
/****************************************************************************
* Copyright (c) 2022 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#ifndef GUI_SHORTCUT_MANAGER_H
#define GUI_SHORTCUT_MANAGER_H
#include <unordered_map>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <QAction>
#include <QKeySequence>
#include <QPointer>
#include <QTimer>
#include <Base/Parameter.h>
namespace Gui {
class Command;
namespace bmi = boost::multi_index;
class GuiExport ShortcutManager : public QObject, public ParameterGrp::ObserverType
{
Q_OBJECT
public:
ShortcutManager();
~ShortcutManager();
static ShortcutManager *instance();
static void destroy();
void OnChange(Base::Subject<const char*> &, const char *reason) override;
/// Clear all user defined shortcut
void resetAll();
/// Clear the user defined shortcut of a given command
void reset(const char *cmd);
/// Set shortcut of a given command
void setShortcut(const char *cmd, const char *accel);
/** Get shortcut of a given command
* @param cmd: command name
* @param defaultAccel: default shortcut
*/
QString getShortcut(const char *cmd, const char *defaultAccel = nullptr);
/// Return actions having a given shortcut in order of decreasing priority
std::vector<std::pair<QByteArray, QAction*>> getActionsByShortcut(const QKeySequence &shortcut);
/// Set properties for a given list of actions in order of decreasing priority
void setPriorities(const std::vector<QByteArray> &actions);
/** Set the priority of a given command
* @param cmd: command name
* @param priority: priority of the command, bigger value means higher priority
*/
void setPriority(const char *cmd, int priority);
/// Get the priority of a given command
int getPriority(const char *cmd);
/** Set the top priority of a given command
* Make the given command the top priority of all commands.
*
* @param cmd: command name
*/
void setTopPriority(const char *cmd);
Q_SIGNALS:
void shortcutChanged(const char *name, const QKeySequence &oldShortcut);
void actionShortcutChanged(QAction *, const QKeySequence &oldShortcut);
void priorityChanged(const char *name, int priority);
protected:
bool eventFilter(QObject *, QEvent *ev) override;
bool checkShortcut(QObject *o, const QKeySequence &key);
void onTimer();
private:
ParameterGrp::handle hShortcuts;
ParameterGrp::handle hPriorities;
ParameterGrp::handle hSetting;
bool busy = false;
struct ActionKey {
QKeySequence shortcut;
QByteArray name;
ActionKey(const QKeySequence &shortcut, const char *name = "")
: shortcut(shortcut)
, name(name)
{}
bool operator<(const ActionKey &other) const
{
if (shortcut > other.shortcut)
return false;
if (shortcut < other.shortcut)
return true;
return name < other.name;
}
};
struct ActionData {
ActionKey key;
intptr_t pointer;
QPointer<QAction> action;
ActionData(QAction *action, const char *name = "")
: key(action->shortcut(), name)
, pointer(reinterpret_cast<int64_t>(action))
, action(action)
{}
};
bmi::multi_index_container<
ActionData,
bmi::indexed_by<
// hashed index on ActionData::Action pointer
bmi::hashed_unique<bmi::member<ActionData, intptr_t, &ActionData::pointer>>,
// ordered index on shortcut + name
bmi::ordered_non_unique<bmi::member<ActionData, ActionKey, &ActionData::key>>
>
> actionMap;
std::unordered_map<std::string, int> priorities;
int topPriority;
struct ActionInfo {
QPointer<QAction> action;
int seq_length;
int priority;
ActionInfo(QAction *action, int l, int p)
: action(action)
, seq_length(l)
, priority(p)
{}
};
std::vector<ActionInfo> pendingActions;
QKeySequence pendingSequence;
QPointer<QWidget> lastFocus;
QTimer timer;
int timeout;
};
}
#endif // GUI_SHORTCUT_MANAGER_H

View File

@@ -310,6 +310,8 @@ void Workbench::setupCustomToolbars(ToolBarItem* root, const Base::Reference<Par
void Workbench::setupCustomShortcuts() const
{
// Now managed by ShortcutManager
#if 0
// Assigns user defined accelerators
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter();
if (hGrp->HasGroup("Shortcut")) {
@@ -328,6 +330,7 @@ void Workbench::setupCustomShortcuts() const
}
}
}
#endif
}
void Workbench::setupContextMenu(const char* recipient,MenuItem* item) const