porymap/src/ui/shortcutseditor.cpp
garak 37fcfba829 use static (And const where applicable) regular expression objects
the speed increase here is noticable since the work of creating a regex object is only done once per session
2023-01-17 13:15:07 -05:00

188 lines
6.9 KiB
C++

#include "shortcutseditor.h"
#include "ui_shortcutseditor.h"
#include "config.h"
#include "multikeyedit.h"
#include "log.h"
#include <QGroupBox>
#include <QFormLayout>
#include <QAbstractButton>
#include <QtEvents>
#include <QMessageBox>
#include <QRegularExpression>
#include <QLabel>
ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ShortcutsEditor),
main_container(nullptr)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
main_container = ui->scrollAreaWidgetContents_Shortcuts;
auto *main_layout = new QVBoxLayout(main_container);
main_layout->setSpacing(12);
connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, &ShortcutsEditor::dialogButtonClicked);
}
ShortcutsEditor::ShortcutsEditor(const QObjectList &shortcutableObjects, QWidget *parent) :
ShortcutsEditor(parent)
{
setShortcutableObjects(shortcutableObjects);
}
ShortcutsEditor::~ShortcutsEditor()
{
delete ui;
}
void ShortcutsEditor::setShortcutableObjects(const QObjectList &shortcutableObjects) {
parseObjectList(shortcutableObjects);
populateMainContainer();
}
void ShortcutsEditor::saveShortcuts() {
QMultiMap<const QObject *, QKeySequence> objects_keySequences;
for (auto it = multiKeyEdits_objects.cbegin(); it != multiKeyEdits_objects.cend(); ++it) {
if (it.key()->keySequences().isEmpty())
objects_keySequences.insert(it.value(), QKeySequence());
for (auto keySequence : it.key()->keySequences())
objects_keySequences.insert(it.value(), keySequence);
}
shortcutsConfig.setUserShortcuts(objects_keySequences);
emit shortcutsSaved();
}
// Restores default shortcuts but doesn't save until Apply or OK is clicked.
void ShortcutsEditor::resetShortcuts() {
for (auto it = multiKeyEdits_objects.begin(); it != multiKeyEdits_objects.end(); ++it) {
it.key()->blockSignals(true);
const auto defaults = shortcutsConfig.defaultShortcuts(it.value());
it.key()->setKeySequences(defaults);
it.key()->blockSignals(false);
}
}
void ShortcutsEditor::parseObjectList(const QObjectList &objectList) {
for (auto *object : objectList) {
const auto label = getLabel(object);
if (!label.isEmpty() && !object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
labels_objects.insert(label, object);
}
}
QString ShortcutsEditor::getLabel(const QObject *object) const {
if (stringPropertyIsNotEmpty(object, "text"))
return object->property("text").toString().remove('&');
else if (stringPropertyIsNotEmpty(object, "whatsThis"))
return object->property("whatsThis").toString().remove('&');
else
return QString();
}
bool ShortcutsEditor::stringPropertyIsNotEmpty(const QObject *object, const char *name) const {
return object->property(name).isValid() && !object->property(name).toString().isEmpty();
}
void ShortcutsEditor::populateMainContainer() {
for (auto object : labels_objects) {
const auto shortcutContext = getShortcutContext(object);
if (!contexts_layouts.contains(shortcutContext))
addNewContextGroup(shortcutContext);
addNewMultiKeyEdit(object, shortcutContext);
}
}
// The context for which the object's shortcut is active (Displayed in group box title).
// Uses the parent window's objectName and adds spaces between words.
QString ShortcutsEditor::getShortcutContext(const QObject *object) const {
auto objectParentWidget = static_cast<QWidget *>(object->parent());
auto context = objectParentWidget->window()->objectName();
static const QRegularExpression re("[A-Z]");
int i = context.indexOf(re, 1);
while (i != -1) {
if (context.at(i - 1) != ' ')
context.insert(i++, ' ');
i = context.indexOf(re, i + 1);
}
return context;
}
// Seperate shortcuts into context groups for duplicate checking.
void ShortcutsEditor::addNewContextGroup(const QString &shortcutContext) {
auto *groupBox = new QGroupBox(shortcutContext, main_container);
main_container->layout()->addWidget(groupBox);
auto *formLayout = new QFormLayout(groupBox);
contexts_layouts.insert(shortcutContext, formLayout);
}
void ShortcutsEditor::addNewMultiKeyEdit(const QObject *object, const QString &shortcutContext) {
auto *container = contexts_layouts.value(shortcutContext)->parentWidget();
auto *multiKeyEdit = new MultiKeyEdit(container);
multiKeyEdit->setKeySequences(shortcutsConfig.userShortcuts(object));
connect(multiKeyEdit, &MultiKeyEdit::keySequenceChanged,
this, &ShortcutsEditor::checkForDuplicates);
contexts_layouts.value(shortcutContext)->addRow(labels_objects.key(object), multiKeyEdit);
multiKeyEdits_objects.insert(multiKeyEdit, object);
}
void ShortcutsEditor::checkForDuplicates(const QKeySequence &keySequence) {
if (keySequence.isEmpty())
return;
auto *sender_multiKeyEdit = qobject_cast<MultiKeyEdit *>(sender());
if (!sender_multiKeyEdit)
return;
for (auto *sibling_multiKeyEdit : siblings(sender_multiKeyEdit))
if (sibling_multiKeyEdit->contains(keySequence))
promptUserOnDuplicateFound(sender_multiKeyEdit, sibling_multiKeyEdit);
}
QList<MultiKeyEdit *> ShortcutsEditor::siblings(MultiKeyEdit *multiKeyEdit) const {
auto list = multiKeyEdit->parent()->findChildren<MultiKeyEdit *>(QString(), Qt::FindDirectChildrenOnly);
list.removeOne(multiKeyEdit);
return list;
}
void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyEdit *sibling) {
const auto duplicateKeySequence = sender->keySequences().last();
const auto siblingLabel = getLabel(multiKeyEdits_objects.value(sibling));
const auto message = QString(
"Shortcut '%1' is already used by '%2', would you like to replace it?")
.arg(duplicateKeySequence.toString()).arg(siblingLabel);
const auto result = QMessageBox::question(
this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (result == QMessageBox::Yes)
removeKeySequence(duplicateKeySequence, sibling);
else
removeKeySequence(duplicateKeySequence, sender);
activateWindow();
}
void ShortcutsEditor::removeKeySequence(const QKeySequence &keySequence, MultiKeyEdit *multiKeyEdit) {
multiKeyEdit->blockSignals(true);
multiKeyEdit->removeOne(keySequence);
multiKeyEdit->blockSignals(false);
}
void ShortcutsEditor::dialogButtonClicked(QAbstractButton *button) {
auto buttonRole = ui->buttonBox->buttonRole(button);
if (buttonRole == QDialogButtonBox::AcceptRole) {
saveShortcuts();
close();
} else if (buttonRole == QDialogButtonBox::ApplyRole) {
saveShortcuts();
} else if (buttonRole == QDialogButtonBox::RejectRole) {
close();
} else if (buttonRole == QDialogButtonBox::ResetRole) {
resetShortcuts();
}
}