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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>&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&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 &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>&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>&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>&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>&Reset</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonResetAll">
|
||||
<property name="text">
|
||||
<string>Re&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>&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>&Reset</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonResetAll">
|
||||
<property name="text">
|
||||
<string>Re&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/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
455
src/Gui/ShortcutManager.cpp
Normal 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
171
src/Gui/ShortcutManager.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user