1
/***************************************************************************
2
* Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
25
#include <boost/signals2/connection.hpp>
28
# include <QHeaderView>
29
# include <QMessageBox>
33
#include <Base/Parameter.h>
34
#include <Base/Tools.h>
35
#include <Base/Console.h>
37
#include "DlgKeyboardImp.h"
38
#include "ui_DlgKeyboard.h"
40
#include "Application.h"
41
#include "BitmapFactory.h"
44
#include "PrefWidgets.h"
45
#include "ShortcutManager.h"
46
#include "CommandCompleter.h"
49
using namespace Gui::Dialog;
51
namespace Gui { namespace Dialog {
52
using GroupMap = std::vector< std::pair<QLatin1String, QString> >;
55
const QLatin1String& item;
56
explicit GroupMap_find(const QLatin1String& item) : item(item) {}
57
bool operator () (const std::pair<QLatin1String, QString>& elem) const
59
return elem.first == item;
65
/* TRANSLATOR Gui::Dialog::DlgCustomKeyboardImp */
68
* Constructs a DlgCustomKeyboardImp which is a child of 'parent', with the
69
* name 'name' and widget flags set to 'f'
71
* The dialog will by default be modeless, unless you set 'modal' to
72
* true to construct a modal dialog.
74
DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent )
75
: CustomizeActionPage(parent)
76
, ui(new Ui_DlgCustomKeyboard)
82
// Force create actions for all commands with shortcut to register with ShortcutManager
83
for (auto cmd : Application::Instance->commandManager().getAllCommands()) {
84
if (cmd->getShortcut().size())
87
QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, this,
88
[](const char *cmdName) {
89
if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName))
93
conn = initCommandWidgets(ui->commandTreeWidget,
97
ui->assignedTreeWidget,
101
ui->accelLineEditShortcut);
103
ui->shortcutTimeout->onRestore();
104
QTimer *timer = new QTimer(this);
105
QObject::connect(ui->shortcutTimeout, qOverload<int>(&QSpinBox::valueChanged), timer, [=](int) {
108
QObject::connect(timer, &QTimer::timeout, [=]() {
109
ui->shortcutTimeout->onSave();
113
/** Destroys the object and frees any allocated resources */
114
DlgCustomKeyboardImp::~DlgCustomKeyboardImp() = default;
116
void DlgCustomKeyboardImp::setupConnections()
118
connect(ui->categoryBox, qOverload<int>(&QComboBox::activated),
119
this, &DlgCustomKeyboardImp::onCategoryBoxActivated);
120
connect(ui->commandTreeWidget, &QTreeWidget::currentItemChanged,
121
this, &DlgCustomKeyboardImp::onCommandTreeWidgetCurrentItemChanged);
122
connect(ui->buttonAssign, &QPushButton::clicked,
123
this, &DlgCustomKeyboardImp::onButtonAssignClicked);
124
connect(ui->buttonClear, &QPushButton::clicked,
125
this, &DlgCustomKeyboardImp::onButtonClearClicked);
126
connect(ui->buttonReset, &QPushButton::clicked,
127
this, &DlgCustomKeyboardImp::onButtonResetClicked);
128
connect(ui->buttonResetAll, &QPushButton::clicked,
129
this, &DlgCustomKeyboardImp::onButtonResetAllClicked);
130
connect(ui->editShortcut, &AccelLineEdit::textChanged,
131
this, &DlgCustomKeyboardImp::onEditShortcutTextChanged);
134
void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit,
136
QTreeWidget *commandTreeWidget,
137
QTreeWidgetItem *separatorItem)
139
edit->setPlaceholderText(tr("Type to search..."));
140
auto completer = new CommandCompleter(edit, edit);
142
QObject::connect(completer, &CommandCompleter::commandActivated,
143
[=](const QByteArray &name) {
144
CommandManager & cCmdMgr = Application::Instance->commandManager();
145
Command *cmd = cCmdMgr.getCommandByName(name.constData());
149
QString group = QString::fromLatin1(cmd->getGroupName());
150
int index = combo->findData(group);
153
if (index != combo->currentIndex()) {
154
QSignalBlocker blocker(combo);
155
combo->setCurrentIndex(index);
156
populateCommandList(commandTreeWidget, separatorItem, combo);
158
for (int i=0 ; i<commandTreeWidget->topLevelItemCount(); ++i) {
159
QTreeWidgetItem *item = commandTreeWidget->topLevelItem(i);
160
if (item->data(1, Qt::UserRole).toByteArray() == name) {
161
commandTreeWidget->setCurrentItem(item);
168
void DlgCustomKeyboardImp::populateCommandList(QTreeWidget *commandTreeWidget,
169
QTreeWidgetItem *separatorItem,
173
if (auto item = commandTreeWidget->currentItem())
174
current = item->data(1, Qt::UserRole).toByteArray();
177
commandTreeWidget->takeTopLevelItem(commandTreeWidget->indexOfTopLevelItem(separatorItem));
179
commandTreeWidget->clear();
181
commandTreeWidget->addTopLevelItem(separatorItem);
184
CommandManager & cCmdMgr = Application::Instance->commandManager();
185
auto group = combo->itemData(combo->currentIndex(), Qt::UserRole).toByteArray();
186
auto cmds = group == "All" ? cCmdMgr.getAllCommands()
187
: cCmdMgr.getGroupCommands(group.constData());
188
QTreeWidgetItem *currentItem = nullptr;
189
for (const Command *cmd : cmds) {
190
QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget);
191
item->setText(1, Action::commandMenuText(cmd));
192
item->setToolTip(1, Action::commandToolTip(cmd));
193
item->setData(1, Qt::UserRole, QByteArray(cmd->getName()));
194
item->setSizeHint(0, QSize(32, 32));
195
if (auto pixmap = cmd->getPixmap())
196
item->setIcon(0, BitmapFactory().iconFromTheme(pixmap));
197
item->setText(2, cmd->getShortcut());
198
if (auto accel = cmd->getAccel())
199
item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString());
201
if (current == cmd->getName())
205
commandTreeWidget->setCurrentItem(currentItem);
206
commandTreeWidget->resizeColumnToContents(2);
207
commandTreeWidget->resizeColumnToContents(3);
210
boost::signals2::connection
211
DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget,
212
QTreeWidgetItem *separatorItem,
216
labels << tr("Icon") << tr("Command") << tr("Shortcut") << tr("Default");
217
commandTreeWidget->setHeaderLabels(labels);
218
commandTreeWidget->setIconSize(QSize(32, 32));
219
commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
220
commandTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
221
commandTreeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
223
populateCommandGroups(combo);
225
// Using a timer to respond to command change for performance, and also
226
// because macro command may be added before proper initialization (null
228
QTimer *timer = new QTimer(combo);
229
timer->setSingleShot(true);
231
QObject::connect(timer, &QTimer::timeout, [=](){
232
populateCommandGroups(combo);
233
populateCommandList(commandTreeWidget, separatorItem, combo);
236
QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, timer, [timer]() {
240
QObject::connect(combo, qOverload<int>(&QComboBox::activated), timer, [timer]() {
244
return Application::Instance->commandManager().signalChanged.connect([timer](){
249
void DlgCustomKeyboardImp::initPriorityList(QTreeWidget *priorityList,
250
QAbstractButton *buttonUp,
251
QAbstractButton *buttonDown)
254
labels << tr("Name") << tr("Title");
255
priorityList->setHeaderLabels(labels);
256
priorityList->header()->hide();
257
priorityList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
258
priorityList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
260
auto updatePriorityList = [priorityList](bool up) {
261
auto item = priorityList->currentItem();
265
int index = priorityList->indexOfTopLevelItem(item);
268
if ((index == 0 && up)
269
|| (index == priorityList->topLevelItemCount()-1 && !up))
272
std::vector<QByteArray> actions;
273
for (int i=0; i<priorityList->topLevelItemCount(); ++i) {
274
auto item = priorityList->topLevelItem(i);
275
actions.push_back(item->data(0, Qt::UserRole).toByteArray());
278
auto it = actions.begin() + index;
279
auto itNext = up ? it - 1 : it + 1;
280
std::swap(*it, *itNext);
281
ShortcutManager::instance()->setPriorities(actions);
284
QObject::connect(buttonUp, &QAbstractButton::clicked, [=](){updatePriorityList(true);});
285
QObject::connect(buttonDown, &QAbstractButton::clicked, [=](){updatePriorityList(false);});
286
QObject::connect(priorityList, &QTreeWidget::currentItemChanged,
287
[=](QTreeWidgetItem *item){
288
buttonUp->setEnabled(item!=nullptr);
289
buttonDown->setEnabled(item!=nullptr);
294
boost::signals2::connection
295
DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget,
296
QTreeWidgetItem *separatorItem,
297
QComboBox *comboGroups,
298
QLineEdit *editCommand,
299
QTreeWidget *priorityList,
300
QAbstractButton *buttonUp,
301
QAbstractButton *buttonDown,
302
Gui::AccelLineEdit *editShortcut,
303
Gui::AccelLineEdit *currentShortcut)
305
initCommandCompleter(editCommand, comboGroups, commandTreeWidget, separatorItem);
306
auto conn = initCommandList(commandTreeWidget, separatorItem, comboGroups);
308
if (priorityList && buttonUp && buttonDown) {
309
initPriorityList(priorityList, buttonUp, buttonDown);
311
auto timer = new QTimer(priorityList);
312
timer->setSingleShot(true);
313
if (currentShortcut) {
314
QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer]() {
318
QObject::connect(editShortcut, &QLineEdit::textChanged, timer, [timer]() {
321
QObject::connect(ShortcutManager::instance(), &ShortcutManager::priorityChanged, timer, [timer](){
324
QObject::connect(timer, &QTimer::timeout, [=]() {
325
populatePriorityList(priorityList, editShortcut, currentShortcut);
332
void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget *priorityList,
333
Gui::AccelLineEdit *editor,
334
Gui::AccelLineEdit *curShortcut)
337
if (auto currentItem = priorityList->currentItem())
338
current = currentItem->data(0, Qt::UserRole).toByteArray();
340
priorityList->clear();
342
if (!editor->isNone() && editor->text().size())
344
else if (curShortcut && !curShortcut->isNone())
345
sc = curShortcut->text();
347
auto actionList = ShortcutManager::instance()->getActionsByShortcut(sc);
348
QTreeWidgetItem *currentItem = nullptr;
349
for (const auto &info : actionList) {
352
QTreeWidgetItem* item = new QTreeWidgetItem(priorityList);
353
item->setText(0, QString::fromUtf8(info.first));
354
item->setText(1, Action::cleanTitle(info.second->text()));
355
item->setToolTip(0, info.second->toolTip());
356
item->setIcon(0, info.second->icon());
357
item->setData(0, Qt::UserRole, info.first);
358
if (current == info.first)
361
priorityList->resizeColumnToContents(0);
362
priorityList->resizeColumnToContents(1);
364
priorityList->setCurrentItem(currentItem);
367
void DlgCustomKeyboardImp::populateCommandGroups(QComboBox *combo)
369
CommandManager & cCmdMgr = Application::Instance->commandManager();
370
std::map<std::string,Command*> sCommands = cCmdMgr.getCommands();
373
groupMap.push_back(std::make_pair(QLatin1String("File"), QString()));
374
groupMap.push_back(std::make_pair(QLatin1String("Edit"), QString()));
375
groupMap.push_back(std::make_pair(QLatin1String("View"), QString()));
376
groupMap.push_back(std::make_pair(QLatin1String("Standard-View"), QString()));
377
groupMap.push_back(std::make_pair(QLatin1String("Tools"), QString()));
378
groupMap.push_back(std::make_pair(QLatin1String("Window"), QString()));
379
groupMap.push_back(std::make_pair(QLatin1String("Help"), QString()));
380
groupMap.push_back(std::make_pair(QLatin1String("Macros"), qApp->translate("Gui::MacroCommand", "Macros")));
382
for (const auto & sCommand : sCommands) {
383
QLatin1String group(sCommand.second->getGroupName());
384
QString text = sCommand.second->translatedGroupName();
385
GroupMap::iterator jt;
386
jt = std::find_if(groupMap.begin(), groupMap.end(), GroupMap_find(group));
387
if (jt != groupMap.end()) {
388
if (jt->second.isEmpty())
392
groupMap.push_back(std::make_pair(group, text));
395
groupMap.push_back(std::make_pair(QLatin1String("All"), tr("All")));
397
for (const auto & it : groupMap) {
398
if (combo->findData(it.first) < 0) {
399
combo->addItem(it.second);
400
combo->setItemData(combo->count()-1, QVariant(it.first), Qt::UserRole);
405
void DlgCustomKeyboardImp::showEvent(QShowEvent* e)
408
// If we did this already in the constructor we wouldn't get the vertical scrollbar if needed.
409
// The problem was noticed with Qt 4.1.4 but may arise with any later version.
411
ui->categoryBox->activated(ui->categoryBox->currentIndex());
416
/** Shows the description for the corresponding command */
417
void DlgCustomKeyboardImp::onCommandTreeWidgetCurrentItemChanged(QTreeWidgetItem* item)
422
QVariant data = item->data(1, Qt::UserRole);
423
QByteArray name = data.toByteArray(); // command name
425
CommandManager & cCmdMgr = Application::Instance->commandManager();
426
Command* cmd = cCmdMgr.getCommandByName(name.constData());
428
QKeySequence ks = ShortcutManager::instance()->getShortcut(
429
cmd->getName(), cmd->getAccel());
430
QKeySequence ks2 = QString::fromLatin1(cmd->getAccel());
431
QKeySequence ks3 = ui->editShortcut->text();
433
ui->accelLineEditShortcut->setText( tr("none") );
435
ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText));
437
ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3));
438
ui->buttonReset->setEnabled((ks != ks2));
442
/** Shows all commands of this category */
443
void DlgCustomKeyboardImp::onCategoryBoxActivated(int)
445
ui->buttonAssign->setEnabled(false);
446
ui->buttonReset->setEnabled(false);
447
ui->accelLineEditShortcut->clear();
448
ui->editShortcut->clear();
451
void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText)
453
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
457
QVariant data = item->data(1, Qt::UserRole);
458
QByteArray name = data.toByteArray(); // command name
461
if (!accelText.isEmpty()) {
462
QKeySequence shortcut = accelText;
463
nativeText = shortcut.toString(QKeySequence::NativeText);
464
ui->accelLineEditShortcut->setText(accelText);
465
ui->editShortcut->clear();
468
ui->accelLineEditShortcut->clear();
469
ui->editShortcut->clear();
471
ShortcutManager::instance()->setShortcut(name, nativeText.toLatin1());
473
ui->buttonAssign->setEnabled(false);
474
ui->buttonReset->setEnabled(true);
477
/** Assigns a new accelerator to the selected command. */
478
void DlgCustomKeyboardImp::onButtonAssignClicked()
480
setShortcutOfCurrentAction(ui->editShortcut->text());
483
/** Clears the accelerator of the selected command. */
484
void DlgCustomKeyboardImp::onButtonClearClicked()
486
setShortcutOfCurrentAction(QString());
489
/** Resets the accelerator of the selected command to the default. */
490
void DlgCustomKeyboardImp::onButtonResetClicked()
492
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
496
QVariant data = item->data(1, Qt::UserRole);
497
QByteArray name = data.toByteArray(); // command name
498
ShortcutManager::instance()->reset(name);
500
QString txt = ShortcutManager::instance()->getShortcut(name);
501
ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt));
502
ui->buttonReset->setEnabled( false );
505
/** Resets the accelerator of all commands to the default. */
506
void DlgCustomKeyboardImp::onButtonResetAllClicked()
508
ShortcutManager::instance()->resetAll();
509
ui->buttonReset->setEnabled(false);
512
/** Checks for an already occupied shortcut. */
513
void DlgCustomKeyboardImp::onEditShortcutTextChanged(const QString& )
515
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
517
QVariant data = item->data(1, Qt::UserRole);
518
QByteArray name = data.toByteArray(); // command name
520
CommandManager & cCmdMgr = Application::Instance->commandManager();
521
Command* cmd = cCmdMgr.getCommandByName(name.constData());
523
if (!ui->editShortcut->isNone())
524
ui->buttonAssign->setEnabled(true);
526
if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty())
527
ui->buttonAssign->setEnabled(false); // both key sequences are empty
532
void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray&)
536
void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray&)
540
void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray&)
542
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
543
QString group = data.toString();
544
if (group == QLatin1String("Macros")) {
545
ui->categoryBox->activated(ui->categoryBox->currentIndex());
549
void DlgCustomKeyboardImp::changeEvent(QEvent *e)
551
if (e->type() == QEvent::LanguageChange) {
552
ui->retranslateUi(this);
553
int count = ui->categoryBox->count();
555
CommandManager & cCmdMgr = Application::Instance->commandManager();
556
for (int i=0; i<count; i++) {
557
QVariant data = ui->categoryBox->itemData(i, Qt::UserRole);
558
std::vector<Command*> aCmds = cCmdMgr.getGroupCommands(data.toByteArray());
559
if (!aCmds.empty()) {
560
QString text = aCmds[0]->translatedGroupName();
561
ui->categoryBox->setItemText(i, text);
564
ui->categoryBox->activated(ui->categoryBox->currentIndex());
566
else if (e->type() == QEvent::StyleChange) {
567
ui->categoryBox->activated(ui->categoryBox->currentIndex());
570
QWidget::changeEvent(e);
573
#include "moc_DlgKeyboardImp.cpp"