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>
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;
55
using GroupMap = std::vector<std::pair<QLatin1String, QString>>;
59
const QLatin1String& item;
60
explicit GroupMap_find(const QLatin1String& item)
63
bool operator()(const std::pair<QLatin1String, QString>& elem) const
65
return elem.first == item;
71
/* TRANSLATOR Gui::Dialog::DlgCustomKeyboardImp */
74
* Constructs a DlgCustomKeyboardImp which is a child of 'parent', with the
75
* name 'name' and widget flags set to 'f'
77
* The dialog will by default be modeless, unless you set 'modal' to
78
* true to construct a modal dialog.
80
DlgCustomKeyboardImp::DlgCustomKeyboardImp(QWidget* parent)
81
: CustomizeActionPage(parent)
82
, ui(new Ui_DlgCustomKeyboard)
88
// Force create actions for all commands with shortcut to register with ShortcutManager
89
for (auto cmd : Application::Instance->commandManager().getAllCommands()) {
90
if (cmd->getShortcut().size()) {
95
ShortcutManager::instance(),
96
&ShortcutManager::shortcutChanged,
98
[](const char* cmdName) {
99
if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) {
104
conn = initCommandWidgets(ui->commandTreeWidget,
108
ui->assignedTreeWidget,
112
ui->accelLineEditShortcut);
114
ui->shortcutTimeout->onRestore();
115
QTimer* timer = new QTimer(this);
116
QObject::connect(ui->shortcutTimeout, qOverload<int>(&QSpinBox::valueChanged), timer, [=](int) {
119
QObject::connect(timer, &QTimer::timeout, [this]() {
120
ui->shortcutTimeout->onSave();
124
/** Destroys the object and frees any allocated resources */
125
DlgCustomKeyboardImp::~DlgCustomKeyboardImp() = default;
127
void DlgCustomKeyboardImp::setupConnections()
130
connect(ui->categoryBox, qOverload<int>(&QComboBox::activated),
131
this, &DlgCustomKeyboardImp::onCategoryBoxActivated);
132
connect(ui->commandTreeWidget, &QTreeWidget::currentItemChanged,
133
this, &DlgCustomKeyboardImp::onCommandTreeWidgetCurrentItemChanged);
134
connect(ui->buttonAssign, &QPushButton::clicked,
135
this, &DlgCustomKeyboardImp::onButtonAssignClicked);
136
connect(ui->buttonClear, &QPushButton::clicked,
137
this, &DlgCustomKeyboardImp::onButtonClearClicked);
138
connect(ui->buttonReset, &QPushButton::clicked,
139
this, &DlgCustomKeyboardImp::onButtonResetClicked);
140
connect(ui->buttonResetAll, &QPushButton::clicked,
141
this, &DlgCustomKeyboardImp::onButtonResetAllClicked);
142
connect(ui->editShortcut, &AccelLineEdit::textChanged,
143
this, &DlgCustomKeyboardImp::onEditShortcutTextChanged);
147
void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit* edit,
149
QTreeWidget* commandTreeWidget,
150
QTreeWidgetItem* separatorItem)
152
edit->setPlaceholderText(tr("Type to search..."));
153
auto completer = new CommandCompleter(edit, edit);
155
QObject::connect(completer, &CommandCompleter::commandActivated, [=](const QByteArray& name) {
156
CommandManager& cCmdMgr = Application::Instance->commandManager();
157
Command* cmd = cCmdMgr.getCommandByName(name.constData());
162
QString group = QString::fromLatin1(cmd->getGroupName());
163
int index = combo->findData(group);
167
if (index != combo->currentIndex()) {
168
QSignalBlocker blocker(combo);
169
combo->setCurrentIndex(index);
170
populateCommandList(commandTreeWidget, separatorItem, combo);
172
for (int i = 0; i < commandTreeWidget->topLevelItemCount(); ++i) {
173
QTreeWidgetItem* item = commandTreeWidget->topLevelItem(i);
174
if (item->data(1, Qt::UserRole).toByteArray() == name) {
175
commandTreeWidget->setCurrentItem(item);
182
void DlgCustomKeyboardImp::populateCommandList(QTreeWidget* commandTreeWidget,
183
QTreeWidgetItem* separatorItem,
187
if (auto item = commandTreeWidget->currentItem()) {
188
current = item->data(1, Qt::UserRole).toByteArray();
192
commandTreeWidget->takeTopLevelItem(commandTreeWidget->indexOfTopLevelItem(separatorItem));
194
commandTreeWidget->clear();
196
commandTreeWidget->addTopLevelItem(separatorItem);
199
CommandManager& cCmdMgr = Application::Instance->commandManager();
200
auto group = combo->itemData(combo->currentIndex(), Qt::UserRole).toByteArray();
202
group == "All" ? cCmdMgr.getAllCommands() : cCmdMgr.getGroupCommands(group.constData());
203
QTreeWidgetItem* currentItem = nullptr;
204
for (const Command* cmd : cmds) {
205
QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget);
206
item->setText(1, Action::commandMenuText(cmd));
207
item->setToolTip(1, Action::commandToolTip(cmd));
208
item->setData(1, Qt::UserRole, QByteArray(cmd->getName()));
209
item->setSizeHint(0, QSize(32, 32));
210
if (auto pixmap = cmd->getPixmap()) {
211
item->setIcon(0, BitmapFactory().iconFromTheme(pixmap));
213
item->setText(2, cmd->getShortcut());
214
if (auto accel = cmd->getAccel()) {
215
item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString());
218
if (current == cmd->getName()) {
223
commandTreeWidget->setCurrentItem(currentItem);
225
commandTreeWidget->resizeColumnToContents(2);
226
commandTreeWidget->resizeColumnToContents(3);
229
boost::signals2::connection DlgCustomKeyboardImp::initCommandList(QTreeWidget* commandTreeWidget,
230
QTreeWidgetItem* separatorItem,
234
labels << tr("Icon") << tr("Command") << tr("Shortcut") << tr("Default");
235
commandTreeWidget->setHeaderLabels(labels);
236
commandTreeWidget->setIconSize(QSize(32, 32));
237
commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
238
commandTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
239
commandTreeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
241
populateCommandGroups(combo);
243
// Using a timer to respond to command change for performance, and also
244
// because macro command may be added before proper initialization (null
246
QTimer* timer = new QTimer(combo);
247
timer->setSingleShot(true);
249
QObject::connect(timer, &QTimer::timeout, [=]() {
250
populateCommandGroups(combo);
251
populateCommandList(commandTreeWidget, separatorItem, combo);
254
QObject::connect(ShortcutManager::instance(),
255
&ShortcutManager::shortcutChanged,
261
QObject::connect(combo, qOverload<int>(&QComboBox::activated), timer, [timer]() {
265
return Application::Instance->commandManager().signalChanged.connect([timer]() {
270
void DlgCustomKeyboardImp::initPriorityList(QTreeWidget* priorityList,
271
QAbstractButton* buttonUp,
272
QAbstractButton* buttonDown)
275
labels << tr("Name") << tr("Title");
276
priorityList->setHeaderLabels(labels);
277
priorityList->header()->hide();
278
priorityList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
279
priorityList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
281
auto updatePriorityList = [priorityList](bool up) {
282
auto item = priorityList->currentItem();
287
int index = priorityList->indexOfTopLevelItem(item);
291
if ((index == 0 && up) || (index == priorityList->topLevelItemCount() - 1 && !up)) {
295
std::vector<QByteArray> actions;
296
for (int i = 0; i < priorityList->topLevelItemCount(); ++i) {
297
auto item = priorityList->topLevelItem(i);
298
actions.push_back(item->data(0, Qt::UserRole).toByteArray());
301
auto it = actions.begin() + index;
302
auto itNext = up ? it - 1 : it + 1;
303
std::swap(*it, *itNext);
304
ShortcutManager::instance()->setPriorities(actions);
307
QObject::connect(buttonUp, &QAbstractButton::clicked, [=]() {
308
updatePriorityList(true);
310
QObject::connect(buttonDown, &QAbstractButton::clicked, [=]() {
311
updatePriorityList(false);
313
QObject::connect(priorityList, &QTreeWidget::currentItemChanged, [=](QTreeWidgetItem* item) {
314
buttonUp->setEnabled(item != nullptr);
315
buttonDown->setEnabled(item != nullptr);
319
boost::signals2::connection
320
DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget* commandTreeWidget,
321
QTreeWidgetItem* separatorItem,
322
QComboBox* comboGroups,
323
QLineEdit* editCommand,
324
QTreeWidget* priorityList,
325
QAbstractButton* buttonUp,
326
QAbstractButton* buttonDown,
327
Gui::AccelLineEdit* editShortcut,
328
Gui::AccelLineEdit* currentShortcut)
330
initCommandCompleter(editCommand, comboGroups, commandTreeWidget, separatorItem);
331
auto conn = initCommandList(commandTreeWidget, separatorItem, comboGroups);
333
if (priorityList && buttonUp && buttonDown) {
334
initPriorityList(priorityList, buttonUp, buttonDown);
336
auto timer = new QTimer(priorityList);
337
timer->setSingleShot(true);
338
if (currentShortcut) {
339
QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer]() {
343
QObject::connect(editShortcut, &QLineEdit::textChanged, timer, [timer]() {
346
QObject::connect(ShortcutManager::instance(),
347
&ShortcutManager::priorityChanged,
352
QObject::connect(timer, &QTimer::timeout, [=]() {
353
populatePriorityList(priorityList, editShortcut, currentShortcut);
360
void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget* priorityList,
361
Gui::AccelLineEdit* editor,
362
Gui::AccelLineEdit* curShortcut)
365
if (auto currentItem = priorityList->currentItem()) {
366
current = currentItem->data(0, Qt::UserRole).toByteArray();
369
priorityList->clear();
371
if (!editor->isNone() && editor->text().size()) {
374
else if (curShortcut && !curShortcut->isNone()) {
375
sc = curShortcut->text();
378
auto actionList = ShortcutManager::instance()->getActionsByShortcut(sc);
379
QTreeWidgetItem* currentItem = nullptr;
380
for (const auto& info : actionList) {
384
QTreeWidgetItem* item = new QTreeWidgetItem(priorityList);
385
item->setText(0, QString::fromUtf8(info.first));
386
item->setText(1, Action::cleanTitle(info.second->text()));
387
item->setToolTip(0, info.second->toolTip());
388
item->setIcon(0, info.second->icon());
389
item->setData(0, Qt::UserRole, info.first);
390
if (current == info.first) {
394
priorityList->resizeColumnToContents(0);
395
priorityList->resizeColumnToContents(1);
397
priorityList->setCurrentItem(currentItem);
401
void DlgCustomKeyboardImp::populateCommandGroups(QComboBox* combo)
403
CommandManager& cCmdMgr = Application::Instance->commandManager();
404
std::map<std::string, Command*> sCommands = cCmdMgr.getCommands();
407
groupMap.push_back(std::make_pair(QLatin1String("File"), QString()));
408
groupMap.push_back(std::make_pair(QLatin1String("Edit"), QString()));
409
groupMap.push_back(std::make_pair(QLatin1String("View"), QString()));
410
groupMap.push_back(std::make_pair(QLatin1String("Standard-View"), QString()));
411
groupMap.push_back(std::make_pair(QLatin1String("Tools"), QString()));
412
groupMap.push_back(std::make_pair(QLatin1String("Window"), QString()));
413
groupMap.push_back(std::make_pair(QLatin1String("Help"), QString()));
415
std::make_pair(QLatin1String("Macros"), qApp->translate("Gui::MacroCommand", "Macros")));
417
for (const auto& sCommand : sCommands) {
418
QLatin1String group(sCommand.second->getGroupName());
419
QString text = sCommand.second->translatedGroupName();
420
GroupMap::iterator jt;
421
jt = std::find_if(groupMap.begin(), groupMap.end(), GroupMap_find(group));
422
if (jt != groupMap.end()) {
423
if (jt->second.isEmpty()) {
428
groupMap.push_back(std::make_pair(group, text));
431
groupMap.push_back(std::make_pair(QLatin1String("All"), tr("All")));
433
for (const auto& it : groupMap) {
434
if (combo->findData(it.first) < 0) {
435
combo->addItem(it.second);
436
combo->setItemData(combo->count() - 1, QVariant(it.first), Qt::UserRole);
441
void DlgCustomKeyboardImp::showEvent(QShowEvent* e)
444
// If we did this already in the constructor we wouldn't get the vertical scrollbar if needed.
445
// The problem was noticed with Qt 4.1.4 but may arise with any later version.
447
ui->categoryBox->activated(ui->categoryBox->currentIndex());
452
/** Shows the description for the corresponding command */
453
void DlgCustomKeyboardImp::onCommandTreeWidgetCurrentItemChanged(QTreeWidgetItem* item)
459
QVariant data = item->data(1, Qt::UserRole);
460
QByteArray name = data.toByteArray(); // command name
462
CommandManager& cCmdMgr = Application::Instance->commandManager();
463
Command* cmd = cCmdMgr.getCommandByName(name.constData());
465
QKeySequence ks = ShortcutManager::instance()->getShortcut(cmd->getName(), cmd->getAccel());
466
QKeySequence ks2 = QString::fromLatin1(cmd->getAccel());
467
QKeySequence ks3 = ui->editShortcut->text();
469
ui->accelLineEditShortcut->setText(tr("none"));
472
ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText));
475
ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3));
476
ui->buttonReset->setEnabled((ks != ks2));
480
/** Shows all commands of this category */
481
void DlgCustomKeyboardImp::onCategoryBoxActivated(int)
483
ui->buttonAssign->setEnabled(false);
484
ui->buttonReset->setEnabled(false);
485
ui->accelLineEditShortcut->clear();
486
ui->editShortcut->clear();
489
void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText)
491
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
496
QVariant data = item->data(1, Qt::UserRole);
497
QByteArray name = data.toByteArray(); // command name
499
QString portableText;
500
if (!accelText.isEmpty()) {
501
QKeySequence shortcut = accelText;
502
portableText = shortcut.toString(QKeySequence::PortableText);
503
ui->accelLineEditShortcut->setText(accelText);
504
ui->editShortcut->clear();
507
ui->accelLineEditShortcut->clear();
508
ui->editShortcut->clear();
510
ShortcutManager::instance()->setShortcut(name, portableText.toLatin1());
512
ui->buttonAssign->setEnabled(false);
513
ui->buttonReset->setEnabled(true);
516
/** Assigns a new accelerator to the selected command. */
517
void DlgCustomKeyboardImp::onButtonAssignClicked()
519
setShortcutOfCurrentAction(ui->editShortcut->text());
522
/** Clears the accelerator of the selected command. */
523
void DlgCustomKeyboardImp::onButtonClearClicked()
525
setShortcutOfCurrentAction(QString());
528
/** Resets the accelerator of the selected command to the default. */
529
void DlgCustomKeyboardImp::onButtonResetClicked()
531
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
536
QVariant data = item->data(1, Qt::UserRole);
537
QByteArray name = data.toByteArray(); // command name
538
ShortcutManager::instance()->reset(name);
540
QString txt = ShortcutManager::instance()->getShortcut(name);
541
ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt));
542
ui->buttonReset->setEnabled(false);
545
/** Resets the accelerator of all commands to the default. */
546
void DlgCustomKeyboardImp::onButtonResetAllClicked()
548
ShortcutManager::instance()->resetAll();
549
ui->buttonReset->setEnabled(false);
552
/** Checks for an already occupied shortcut. */
553
void DlgCustomKeyboardImp::onEditShortcutTextChanged(const QString&)
555
QTreeWidgetItem* item = ui->commandTreeWidget->currentItem();
557
QVariant data = item->data(1, Qt::UserRole);
558
QByteArray name = data.toByteArray(); // command name
560
CommandManager& cCmdMgr = Application::Instance->commandManager();
561
Command* cmd = cCmdMgr.getCommandByName(name.constData());
563
if (!ui->editShortcut->isNone()) {
564
ui->buttonAssign->setEnabled(true);
567
if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty()) {
568
ui->buttonAssign->setEnabled(false); // both key sequences are empty
574
void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray&)
577
void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray&)
580
void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray&)
582
QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole);
583
QString group = data.toString();
584
if (group == QLatin1String("Macros")) {
585
ui->categoryBox->activated(ui->categoryBox->currentIndex());
589
void DlgCustomKeyboardImp::changeEvent(QEvent* e)
591
if (e->type() == QEvent::LanguageChange) {
592
ui->retranslateUi(this);
593
int count = ui->categoryBox->count();
595
CommandManager& cCmdMgr = Application::Instance->commandManager();
596
for (int i = 0; i < count; i++) {
597
QVariant data = ui->categoryBox->itemData(i, Qt::UserRole);
598
std::vector<Command*> aCmds = cCmdMgr.getGroupCommands(data.toByteArray());
599
if (!aCmds.empty()) {
600
QString text = aCmds[0]->translatedGroupName();
601
ui->categoryBox->setItemText(i, text);
604
ui->categoryBox->activated(ui->categoryBox->currentIndex());
606
else if (e->type() == QEvent::StyleChange) {
607
ui->categoryBox->activated(ui->categoryBox->currentIndex());
610
QWidget::changeEvent(e);
613
#include "moc_DlgKeyboardImp.cpp"