From faeeb42b853426a211883d4885e01eb01f26eb7e Mon Sep 17 00:00:00 2001 From: Laszlo Papp Date: Sat, 9 Jul 2022 20:57:14 +0100 Subject: [PATCH] Add a shortcut editor example Many applications offer shortcuts for quick interaction with the application. It is also common in such applications to offer a shortcut editor in the preferences or separately in a dialog. However, even though this is a fairly common use case for applications with more than a couple of shortcuts, there is no good and comprehensive official Qt example how this could be achieved. This change is an attempt to bridge the gap. Change-Id: Ic01a404e6157bda1b0a75a0b792cbfe5d910d48f Reviewed-by: Paul Wicking --- doc/src/images/shortcuteditor-example.png | Bin 0 -> 40745 bytes examples/widgets/doc/src/shortcuteditor.qdoc | 230 +++++++++++++++ examples/widgets/widgets/CMakeLists.txt | 1 + .../widgets/shortcuteditor/CMakeLists.txt | 39 +++ .../widgets/shortcuteditor/actionmanager.cpp | 57 ++++ .../widgets/shortcuteditor/actionmanager.h | 33 +++ .../widgets/shortcuteditor/application.cpp | 15 + .../widgets/shortcuteditor/application.h | 27 ++ .../widgets/widgets/shortcuteditor/main.cpp | 13 + .../widgets/shortcuteditor/mainwindow.cpp | 46 +++ .../widgets/shortcuteditor/mainwindow.h | 22 ++ .../shortcuteditor/shortcuteditordelegate.cpp | 71 +++++ .../shortcuteditor/shortcuteditordelegate.h | 34 +++ .../shortcuteditor/shortcuteditormodel.cpp | 273 ++++++++++++++++++ .../shortcuteditor/shortcuteditormodel.h | 71 +++++ .../shortcuteditor/shortcuteditorwidget.cpp | 33 +++ .../shortcuteditor/shortcuteditorwidget.h | 32 ++ 17 files changed, 997 insertions(+) create mode 100644 doc/src/images/shortcuteditor-example.png create mode 100644 examples/widgets/doc/src/shortcuteditor.qdoc create mode 100644 examples/widgets/widgets/shortcuteditor/CMakeLists.txt create mode 100644 examples/widgets/widgets/shortcuteditor/actionmanager.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/actionmanager.h create mode 100644 examples/widgets/widgets/shortcuteditor/application.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/application.h create mode 100644 examples/widgets/widgets/shortcuteditor/main.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/mainwindow.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/mainwindow.h create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.h create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditormodel.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditormodel.h create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.cpp create mode 100644 examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.h diff --git a/doc/src/images/shortcuteditor-example.png b/doc/src/images/shortcuteditor-example.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe9a77cdd4ea6dad63536dacb43cee6d09ebdf2 GIT binary patch literal 40745 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%0A-hd$?@SM0<`7!K;D2 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 jg7t%`_`mR{ds5EQ6KhM7A!X1e +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "actionmanager.h" + +#include +#include +#include +#include + +static const char *kDefaultShortcutPropertyName = "defaultShortcuts"; +static const char *kIdPropertyName = "id"; +static const char *kAuthorName = "qt"; + +struct ActionIdentifier { + QString author; + QString context; + QString category; + QString name; +}; + +QList ActionManager::registeredActions() const +{ + return m_actions; +} + +void ActionManager::registerAction(QAction *action) +{ + action->setProperty(kDefaultShortcutPropertyName, QVariant::fromValue(action->shortcut())); + m_actions.append(action); +} + +void ActionManager::registerAction(QAction *action, const QString &context, const QString &category) +{ + action->setProperty(kIdPropertyName, QVariant::fromValue(ActionIdentifier{ + kAuthorName, context, category, action->text() + })); + registerAction(action); +} + +QAction *ActionManager::registerAction(const QString &name, const QString &shortcut, const QString &context, const QString &category) +{ + QAction *action = new QAction(name, qApp); + action->setShortcut(QKeySequence(shortcut)); + registerAction(action, context, category); + return action; +} + +QString ActionManager::contextForAction(QAction *action) +{ + return action->property(kIdPropertyName).value().context; +} + +QString ActionManager::categoryForAction(QAction *action) +{ + return action->property(kIdPropertyName).value().category; +} diff --git a/examples/widgets/widgets/shortcuteditor/actionmanager.h b/examples/widgets/widgets/shortcuteditor/actionmanager.h new file mode 100644 index 0000000000..da20cd8840 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/actionmanager.h @@ -0,0 +1,33 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef ACTIONMANAGER_H +#define ACTIONMANAGER_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QAction; +QT_END_NAMESPACE + +class ActionManager +{ +public: + ActionManager() = default; + ~ActionManager() = default; + + QList registeredActions() const; + + void registerAction(QAction *action); + void registerAction(QAction *action, const QString &context, const QString &category); + QAction *registerAction(const QString &name, const QString &shortcut, const QString &context, const QString &category); + + QString contextForAction(QAction *action); + QString categoryForAction(QAction *action); + +private: + QList m_actions; +}; + +#endif diff --git a/examples/widgets/widgets/shortcuteditor/application.cpp b/examples/widgets/widgets/shortcuteditor/application.cpp new file mode 100644 index 0000000000..4ac76682d0 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/application.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "application.h" + +Application::Application(int &argc, char **argv) + : QApplication(argc, argv) +{ + m_actionManager = std::make_unique(); +} + +ActionManager *Application::actionManager() const +{ + return m_actionManager.get(); +} diff --git a/examples/widgets/widgets/shortcuteditor/application.h b/examples/widgets/widgets/shortcuteditor/application.h new file mode 100644 index 0000000000..38808c3ad2 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/application.h @@ -0,0 +1,27 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include "actionmanager.h" + +#include + +#include + +class Application : public QApplication +{ + Q_OBJECT + +public: + Application(int &argc, char **argv); + ~Application() override = default; + + ActionManager *actionManager() const; + +private: + std::unique_ptr m_actionManager; +}; + +#endif diff --git a/examples/widgets/widgets/shortcuteditor/main.cpp b/examples/widgets/widgets/shortcuteditor/main.cpp new file mode 100644 index 0000000000..029f7a351a --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/main.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "application.h" +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + Application app(argc, argv); + MainWindow window; + window.show(); + return app.exec(); +} diff --git a/examples/widgets/widgets/shortcuteditor/mainwindow.cpp b/examples/widgets/widgets/shortcuteditor/mainwindow.cpp new file mode 100644 index 0000000000..587dbbc5b5 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/mainwindow.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include "actionmanager.h" +#include "application.h" +#include "shortcuteditorwidget.h" + +#include +#include +#include +#include + +MainWindow::MainWindow() +{ + QPushButton *topPushButton = new QPushButton("Left"); + QPushButton *bottomPushButton = new QPushButton("Right"); + for (auto nameShortcut : std::vector>{{"red", "r", "shift+r"}, {"green", "g", "shift+g"}, {"blue", "b", "shift+b"}}) { + Application *application = static_cast(QCoreApplication::instance()); + ActionManager *actionManager = application->actionManager(); + QAction *action = actionManager->registerAction(nameShortcut[0], nameShortcut[1], "top", "color"); + topPushButton->addAction(action); + connect(action, &QAction::triggered, this, [topPushButton, nameShortcut]() { + topPushButton->setText(nameShortcut[0]); + }); + + action = actionManager->registerAction(nameShortcut[0], nameShortcut[2], "bottom", "color"); + bottomPushButton->addAction(action); + connect(action, &QAction::triggered, this, [bottomPushButton, nameShortcut]() { + bottomPushButton->setText(nameShortcut[0]); + }); + } + + QVBoxLayout *vBoxLayout = new QVBoxLayout; + vBoxLayout->addWidget(topPushButton); + vBoxLayout->addWidget(bottomPushButton); + + QHBoxLayout *hBoxLayout = new QHBoxLayout; + hBoxLayout->addWidget(new ShortcutEditorWidget); + hBoxLayout->addLayout(vBoxLayout); + + QWidget *centralWidget = new QWidget; + centralWidget->setLayout(hBoxLayout); + setCentralWidget(centralWidget); +} diff --git a/examples/widgets/widgets/shortcuteditor/mainwindow.h b/examples/widgets/widgets/shortcuteditor/mainwindow.h new file mode 100644 index 0000000000..702b3f2d87 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/mainwindow.h @@ -0,0 +1,22 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +class QPushButton; +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + ~MainWindow() override = default; +}; + +#endif diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.cpp b/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.cpp new file mode 100644 index 0000000000..a8b32bc06a --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "shortcuteditordelegate.h" + +#include +#include + +//! [0] +ShortcutEditorDelegate::ShortcutEditorDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} +//! [0] + +//! [1] +QWidget *ShortcutEditorDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &/*option*/, + const QModelIndex &/*index*/) const +{ + QKeySequenceEdit *editor = new QKeySequenceEdit(parent); + connect(editor, &QKeySequenceEdit::editingFinished, this, &ShortcutEditorDelegate::commitAndCloseEditor); + return editor; +} +//! [1] + +//! [2] +void ShortcutEditorDelegate::commitAndCloseEditor() +{ + QKeySequenceEdit *editor = static_cast(sender()); + Q_EMIT commitData(editor); + Q_EMIT closeEditor(editor); +} +//! [2] + +//! [3] +void ShortcutEditorDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + if (!editor || !index.isValid()) + return; + + QString value = index.model()->data(index, Qt::EditRole).toString(); + + QKeySequenceEdit *keySequenceEdit = static_cast(editor); + keySequenceEdit->setKeySequence(value); +} +//! [3] + +//! [4] +void ShortcutEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + if (!editor || !model || !index.isValid()) + return; + + const QKeySequenceEdit *keySequenceEdit = static_cast(editor); + const QKeySequence keySequence = keySequenceEdit->keySequence(); + QString keySequenceString = keySequence.toString(QKeySequence::NativeText); + model->setData(index, keySequenceString); +} +//! [4] + +//! [5] +void ShortcutEditorDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &/*index*/) const +{ + editor->setGeometry(option.rect); +} +//! [5] diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.h b/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.h new file mode 100644 index 0000000000..2818438db3 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.h @@ -0,0 +1,34 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SHORTCUTEDITORDELEGATE_H +#define SHORTCUTEDITORDELEGATE_H + +#include + +//! [0] +class ShortcutEditorDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit ShortcutEditorDelegate(QObject *parent = nullptr); + ~ShortcutEditorDelegate() override = default; + +protected: + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + +private: + void commitAndCloseEditor(); +}; +//! [0] + +#endif diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.cpp b/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.cpp new file mode 100644 index 0000000000..4355f029e1 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.cpp @@ -0,0 +1,273 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "shortcuteditormodel.h" + +#include "actionmanager.h" +#include "application.h" + +#include +#include + +// List of actions for all categories +using CategoryActionsMap = QMap>; + +// List of categories for all contexts +using ActionsMap = QMap; + + +ShortcutEditorModel::ShortcutEditorModelItem::ShortcutEditorModelItem(const QList &data, ShortcutEditorModelItem *parent) + : m_itemData(data) + , m_parentItem(parent) +{ +} + +ShortcutEditorModel::ShortcutEditorModelItem::~ShortcutEditorModelItem() +{ + qDeleteAll(m_childItems); +} + +void ShortcutEditorModel::ShortcutEditorModelItem::appendChild(ShortcutEditorModelItem *item) +{ + m_childItems.push_back(item); +} + +ShortcutEditorModel::ShortcutEditorModelItem *ShortcutEditorModel::ShortcutEditorModelItem::child(int row) const +{ + if (row < 0 || row >= m_childItems.size()) + return nullptr; + + return m_childItems.at(row); +} + +int ShortcutEditorModel::ShortcutEditorModelItem::childCount() const +{ + return m_childItems.count(); +} + +int ShortcutEditorModel::ShortcutEditorModelItem::columnCount() const +{ + return m_itemData.count(); +} + +QVariant ShortcutEditorModel::ShortcutEditorModelItem::data(int column) const +{ + if (column < 0 || column >= m_itemData.size()) + return QVariant(); + + QVariant columnVariant = m_itemData.at(column); + if (column != static_cast(Column::Shortcut) || columnVariant.canConvert()) + return columnVariant; + + QAction *action = static_cast(columnVariant.value()); + if (!action) + return QVariant(); + + QKeySequence keySequence = action->shortcut(); + QString keySequenceString = keySequence.toString(QKeySequence::NativeText); + return keySequenceString; +} + +ShortcutEditorModel::ShortcutEditorModelItem *ShortcutEditorModel::ShortcutEditorModelItem::parentItem() const +{ + return m_parentItem; +} + +int ShortcutEditorModel::ShortcutEditorModelItem::row() const +{ + if (m_parentItem) + return m_parentItem->m_childItems.indexOf(const_cast(this)); + + return 0; +} + +QAction *ShortcutEditorModel::ShortcutEditorModelItem::action() const +{ + QVariant actionVariant = m_itemData.at(static_cast(Column::Shortcut)); + return static_cast(actionVariant.value()); +} + + +//! [0] +ShortcutEditorModel::ShortcutEditorModel(QObject *parent) + : QAbstractItemModel(parent) +{ + m_rootItem = new ShortcutEditorModelItem({tr("Name"), tr("Shortcut")}); +} +//! [0] + +//! [1] +ShortcutEditorModel::~ShortcutEditorModel() +{ + delete m_rootItem; +} +//! [1] + +//! [2] +void ShortcutEditorModel::setActions() +{ + beginResetModel(); + setupModelData(m_rootItem); + endResetModel(); +} +//! [2] + +//! [3] +QModelIndex ShortcutEditorModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + ShortcutEditorModelItem *parentItem; + if (!parent.isValid()) + parentItem = m_rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + ShortcutEditorModelItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + + return QModelIndex(); +} +//! [3] + +//! [4] +QModelIndex ShortcutEditorModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + ShortcutEditorModelItem *childItem = static_cast(index.internalPointer()); + ShortcutEditorModelItem *parentItem = childItem->parentItem(); + + if (parentItem == m_rootItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} +//! [4] + +//! [5] +int ShortcutEditorModel::rowCount(const QModelIndex &parent) const +{ + ShortcutEditorModelItem *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = m_rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->childCount(); +} +//! [5] + +//! [6] +int ShortcutEditorModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + + return m_rootItem->columnCount(); +} +//! [6] + +//! [7] +QVariant ShortcutEditorModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole && role != Qt::EditRole) + return QVariant(); + + ShortcutEditorModelItem *item = static_cast(index.internalPointer()); + return item->data(index.column()); +} +//! [7] + +//! [8] +Qt::ItemFlags ShortcutEditorModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags modelFlags = QAbstractItemModel::flags(index); + if (index.column() == static_cast(Column::Shortcut)) + modelFlags |= Qt::ItemIsEditable; + + return modelFlags; +} +//! [8] + +//! [9] +QVariant ShortcutEditorModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + return m_rootItem->data(section); + } + + return QVariant(); +} +//! [9] + +//! [10] +void ShortcutEditorModel::setupModelData(ShortcutEditorModelItem *parent) +{ + ActionsMap actionsMap; + Application *application = static_cast(QCoreApplication::instance()); + ActionManager *actionManager = application->actionManager(); + const QList registeredActions = actionManager->registeredActions(); + for (QAction *action : registeredActions) { + QString context = actionManager->contextForAction(action); + QString category = actionManager->categoryForAction(action); + actionsMap[context][category].append(action); + } + + QAction *nullAction = nullptr; + const QString contextIdPrefix = "root"; + // Go through each context, one context - many categories each iteration + for (const auto &contextLevel : actionsMap.keys()) { + ShortcutEditorModelItem *contextLevelItem = new ShortcutEditorModelItem({contextLevel, QVariant::fromValue(nullAction)}, parent); + parent->appendChild(contextLevelItem); + + // Go through each category, one category - many actions each iteration + for (const auto &categoryLevel : actionsMap[contextLevel].keys()) { + ShortcutEditorModelItem *categoryLevelItem = new ShortcutEditorModelItem({categoryLevel, QVariant::fromValue(nullAction)}, contextLevelItem); + contextLevelItem->appendChild(categoryLevelItem); + for (QAction *action : actionsMap[contextLevel][categoryLevel]) { + QString name = action->text(); + if (name.isEmpty() || !action) + continue; + + ShortcutEditorModelItem *actionLevelItem = new ShortcutEditorModelItem({name, QVariant::fromValue(reinterpret_cast(action))}, categoryLevelItem); + categoryLevelItem->appendChild(actionLevelItem); + } + } + } +} +//! [10] + +//! [11] +bool ShortcutEditorModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == Qt::EditRole && index.column() == static_cast(Column::Shortcut)) { + QString keySequenceString = value.toString(); + ShortcutEditorModelItem *item = static_cast(index.internalPointer()); + QAction *itemAction = item->action(); + if (itemAction + && keySequenceString == itemAction->shortcut().toString(QKeySequence::NativeText)) + return true; + + itemAction->setShortcut(keySequenceString); + Q_EMIT dataChanged(index, index); + + if (keySequenceString.isEmpty()) + return true; + } + + return QAbstractItemModel::setData(index, value, role); +} +//! [11] diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.h b/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.h new file mode 100644 index 0000000000..c687bb4129 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditormodel.h @@ -0,0 +1,71 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SHORTCUTEDITORMODEL_H +#define SHORTCUTEDITORMODEL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QAction; +QT_END_NAMESPACE + +enum class Column : uint8_t { + Name, + Shortcut +}; + +//! [0] +class ShortcutEditorModel : public QAbstractItemModel +{ + Q_OBJECT + + class ShortcutEditorModelItem + { + public: + explicit ShortcutEditorModelItem(const QList &data, + ShortcutEditorModelItem *parentItem = nullptr); + ~ShortcutEditorModelItem(); + + void appendChild(ShortcutEditorModelItem *child); + + ShortcutEditorModelItem *child(int row) const; + int childCount() const; + int columnCount() const; + QVariant data(int column) const; + int row() const; + ShortcutEditorModelItem *parentItem() const; + QAction *action() const; + + private: + QList m_childItems; + QList m_itemData; + ShortcutEditorModelItem *m_parentItem; + }; + +public: + explicit ShortcutEditorModel(QObject *parent = nullptr); + ~ShortcutEditorModel() override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &index = QModelIndex()) const override; + int columnCount(const QModelIndex &index = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + void setActions(); + +private: + void setupModelData(ShortcutEditorModelItem *parent); + + ShortcutEditorModelItem *m_rootItem; +}; +//! [0] + +#endif diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.cpp b/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.cpp new file mode 100644 index 0000000000..3e8a027f38 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "shortcuteditorwidget.h" + +#include "shortcuteditordelegate.h" +#include "shortcuteditormodel.h" + +#include +#include +#include + +//! [0] +ShortcutEditorWidget::ShortcutEditorWidget(QWidget *parent) + : QWidget(parent) +{ + m_model = new ShortcutEditorModel(this); + m_delegate = new ShortcutEditorDelegate(this); + m_view = new QTreeView(this); + m_view->setModel(m_model); + m_view->setItemDelegateForColumn(static_cast(Column::Shortcut), m_delegate); + m_view->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + m_view->setAllColumnsShowFocus(true); + m_view->header()->resizeSection(0, 250); + + QVBoxLayout *layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_view); + setLayout(layout); + + m_model->setActions(); +} +//! [0] diff --git a/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.h b/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.h new file mode 100644 index 0000000000..44735e65e2 --- /dev/null +++ b/examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.h @@ -0,0 +1,32 @@ +// Copyright (C) 2022 Laszlo Papp +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SHORTCUTEDITORWIDGET_H +#define SHORTCUTEDITORWIDGET_H + +#include + +class ShortcutEditorDelegate; +class ShortcutEditorModel; + +QT_BEGIN_NAMESPACE +class QTreeView; +QT_END_NAMESPACE + +//! [0] +class ShortcutEditorWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ShortcutEditorWidget(QWidget *parent = nullptr); + ~ShortcutEditorWidget() override = default; + +private: + ShortcutEditorDelegate *m_delegate; + ShortcutEditorModel *m_model; + QTreeView *m_view; +}; +//! [0] + +#endif