diff --git a/include/ui/multikeyedit.h b/include/ui/multikeyedit.h new file mode 100644 index 00000000..c1736280 --- /dev/null +++ b/include/ui/multikeyedit.h @@ -0,0 +1,50 @@ +#ifndef MULTIKEYEDIT_H +#define MULTIKEYEDIT_H + +#include +#include + +class QLineEdit; + + +// A collection of QKeySequenceEdit's laid out horizontally. +class MultiKeyEdit : public QWidget +{ + Q_OBJECT + +public: + MultiKeyEdit(QWidget *parent = nullptr, int fieldCount = 2); + + bool eventFilter(QObject *watched, QEvent *event) override; + + int fieldCount() const; + void setFieldCount(int count); + QList keySequences() const; + bool removeOne(const QKeySequence &keySequence); + bool contains(const QKeySequence &keySequence) const; + void setContextMenuPolicy(Qt::ContextMenuPolicy policy); + +public slots: + void clear(); + void setKeySequences(const QList &keySequences); + void addKeySequence(const QKeySequence &keySequence); + +signals: + void keySequenceChanged(const QKeySequence &keySequence); + void editingFinished(); + void customContextMenuRequested(const QPoint &pos); + +private: + QVector keySequenceEdit_vec; + QList keySequence_list; // Used to track changes + + void addNewKeySequenceEdit(); + void alignKeySequencesLeft(); + void setFocusToLastNonEmptyKeySequenceEdit(); + +private slots: + void onEditingFinished(); + void showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos); +}; + +#endif // MULTIKEYEDIT_H diff --git a/porymap.pro b/porymap.pro index 593b1112..af5a6205 100644 --- a/porymap.pro +++ b/porymap.pro @@ -73,6 +73,7 @@ SOURCES += src/core/block.cpp \ src/ui/mapruler.cpp \ src/ui/shortcut.cpp \ src/ui/shortcutseditor.cpp \ + src/ui/multikeyedit.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -144,6 +145,7 @@ HEADERS += include/core/block.h \ include/ui/mapruler.h \ include/ui/shortcut.h \ include/ui/shortcutseditor.h \ + include/ui/multikeyedit.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ diff --git a/src/ui/multikeyedit.cpp b/src/ui/multikeyedit.cpp new file mode 100644 index 00000000..5f66328e --- /dev/null +++ b/src/ui/multikeyedit.cpp @@ -0,0 +1,175 @@ +#include "multikeyedit.h" + +#include +#include +#include +#include +#include + + +MultiKeyEdit::MultiKeyEdit(QWidget *parent, int fieldCount) : + QWidget(parent), + keySequenceEdit_vec(QVector()), + keySequence_list(QList()) +{ + setLayout(new QHBoxLayout(this)); + layout()->setContentsMargins(0, 0, 0, 0); + setFieldCount(fieldCount); +} + +bool MultiKeyEdit::eventFilter(QObject *watched, QEvent *event) { + if (event->type() == QEvent::KeyPress) { + auto *watched_kse = qobject_cast(watched); + if (!watched_kse) + return false; + + auto *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + watched_kse->clearFocus(); + return true; + } else { + watched_kse->clear(); + } + } + + if (event->type() == QEvent::ContextMenu) { + auto *watched_lineEdit = qobject_cast(watched); + if (!watched_lineEdit) + return false; + + auto *contextMenuEvent = static_cast(event); + if (contextMenuPolicy() == Qt::DefaultContextMenu) { + showDefaultContextMenu(watched_lineEdit, contextMenuEvent->pos()); + return true; + } + } + + return false; +} + +int MultiKeyEdit::fieldCount() const { + return keySequenceEdit_vec.count(); +} + +void MultiKeyEdit::setFieldCount(int count) { + if (count < 1) + count = 1; + + while (keySequenceEdit_vec.count() < count) + addNewKeySequenceEdit(); + + while (keySequenceEdit_vec.count() > count) + delete keySequenceEdit_vec.takeLast(); + + alignKeySequencesLeft(); +} + +QList MultiKeyEdit::keySequences() const { + QList current_keySequences; + for (auto *kse : keySequenceEdit_vec) + if (!kse->keySequence().isEmpty()) + current_keySequences.append(kse->keySequence()); + return current_keySequences; +} + +bool MultiKeyEdit::removeOne(const QKeySequence &keySequence) { + for (auto *keySequenceEdit : keySequenceEdit_vec) { + if (keySequenceEdit->keySequence() == keySequence) { + keySequence_list.removeOne(keySequence); + keySequenceEdit->clear(); + alignKeySequencesLeft(); + return true; + } + } + return false; +} + +bool MultiKeyEdit::contains(const QKeySequence &keySequence) const { + for (auto current_keySequence : keySequences()) + if (current_keySequence == keySequence) + return true; + return false; +} + +void MultiKeyEdit::setContextMenuPolicy(Qt::ContextMenuPolicy policy) { + QWidget::setContextMenuPolicy(policy); + auto lineEdit_children = findChildren(); + for (auto *lineEdit : lineEdit_children) + lineEdit->setContextMenuPolicy(policy); +} + +void MultiKeyEdit::clear() { + for (auto *keySequenceEdit : keySequenceEdit_vec) + keySequenceEdit->clear(); + keySequence_list.clear(); +} + +void MultiKeyEdit::setKeySequences(const QList &keySequences) { + clear(); + keySequence_list = keySequences; + int minCount = qMin(keySequenceEdit_vec.count(), keySequence_list.count()); + for (int i = 0; i < minCount; ++i) + keySequenceEdit_vec[i]->setKeySequence(keySequence_list[i]); +} + +void MultiKeyEdit::addKeySequence(const QKeySequence &keySequence) { + keySequenceEdit_vec.last()->setKeySequence(keySequence); + alignKeySequencesLeft(); +} + +void MultiKeyEdit::addNewKeySequenceEdit() { + auto *keySequenceEdit = new QKeySequenceEdit(this); + keySequenceEdit->installEventFilter(this); + connect(keySequenceEdit, &QKeySequenceEdit::editingFinished, + this, &MultiKeyEdit::onEditingFinished); + connect(keySequenceEdit, &QKeySequenceEdit::keySequenceChanged, + this, &MultiKeyEdit::keySequenceChanged); + + auto *lineEdit = keySequenceEdit->findChild(); + lineEdit->installEventFilter(this); + connect(lineEdit, &QLineEdit::customContextMenuRequested, + this, &MultiKeyEdit::customContextMenuRequested); + + layout()->addWidget(keySequenceEdit); + keySequenceEdit_vec.append(keySequenceEdit); +} + +// Shift all key sequences left if there are any empty QKeySequenceEdit's. +void MultiKeyEdit::alignKeySequencesLeft() { + blockSignals(true); + setKeySequences(keySequences()); + blockSignals(false); +} + +void MultiKeyEdit::setFocusToLastNonEmptyKeySequenceEdit() { + for (auto it = keySequenceEdit_vec.rbegin(); it != keySequenceEdit_vec.rend(); ++it) { + if (!(*it)->keySequence().isEmpty()) { + (*it)->setFocus(); + return; + } + } +} + +void MultiKeyEdit::onEditingFinished() { + auto *keySequenceEdit = qobject_cast(sender()); + if (keySequenceEdit && keySequence_list.contains(keySequenceEdit->keySequence())) + removeOne(keySequenceEdit->keySequence()); + alignKeySequencesLeft(); + setFocusToLastNonEmptyKeySequenceEdit(); + + emit editingFinished(); +} + +/* QKeySequenceEdit doesn't send or receive context menu events, but it owns QLineEdit that does. + * This QLineEdit hijacks those events and so we need to filter/connect to it directly, rather than + * the QKeySequenceEdit. I wouldn't be surprised if Qt fixed this in the future, in which case any + * context menu related code in this class might need to change. */ +void MultiKeyEdit::showDefaultContextMenu(QLineEdit *lineEdit, const QPoint &pos) { + QMenu menu(this); + QAction clearAction("Clear Shortcut", &menu); + connect(&clearAction, &QAction::triggered, lineEdit, [&lineEdit]() { + lineEdit->clear(); + }); + menu.addAction(&clearAction); + menu.exec(lineEdit->mapToGlobal(pos)); +}