summaryrefslogtreecommitdiffstats
path: root/examples/widgets
diff options
context:
space:
mode:
authorLaszlo Papp <lpapp@kde.org>2022-07-09 20:57:14 +0100
committerLaszlo Papp <lpapp@kde.org>2022-10-25 14:38:46 +0100
commitfaeeb42b853426a211883d4885e01eb01f26eb7e (patch)
tree1559bf8b2f79f7f69b600d7339a5b3b36f1a732d /examples/widgets
parentf263211484f8d4f2bf706077cc911d1a76c5db36 (diff)
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 <paul.wicking@qt.io>
Diffstat (limited to 'examples/widgets')
-rw-r--r--examples/widgets/doc/src/shortcuteditor.qdoc230
-rw-r--r--examples/widgets/widgets/CMakeLists.txt1
-rw-r--r--examples/widgets/widgets/shortcuteditor/CMakeLists.txt39
-rw-r--r--examples/widgets/widgets/shortcuteditor/actionmanager.cpp57
-rw-r--r--examples/widgets/widgets/shortcuteditor/actionmanager.h33
-rw-r--r--examples/widgets/widgets/shortcuteditor/application.cpp15
-rw-r--r--examples/widgets/widgets/shortcuteditor/application.h27
-rw-r--r--examples/widgets/widgets/shortcuteditor/main.cpp13
-rw-r--r--examples/widgets/widgets/shortcuteditor/mainwindow.cpp46
-rw-r--r--examples/widgets/widgets/shortcuteditor/mainwindow.h22
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.cpp71
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditordelegate.h34
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditormodel.cpp273
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditormodel.h71
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.cpp33
-rw-r--r--examples/widgets/widgets/shortcuteditor/shortcuteditorwidget.h32
16 files changed, 997 insertions, 0 deletions
diff --git a/examples/widgets/doc/src/shortcuteditor.qdoc b/examples/widgets/doc/src/shortcuteditor.qdoc
new file mode 100644
index 0000000000..350609c962
--- /dev/null
+++ b/examples/widgets/doc/src/shortcuteditor.qdoc
@@ -0,0 +1,230 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example widgets/shortcuteditor
+ \title Shortcut Editor Example
+ \ingroup examples-widgets
+ \brief The Shortcut Editor example shows how to create a basic, read-write
+ hierarchical model to use with Qt's standard view and QKeySequenceEdit
+ classes. For a description of Model/View Programming, see the \l{Model/View
+ Programming} overview.
+
+ \image shortcuteditor-example.png
+
+ Qt's model/view architecture provides a standard way for views to
+ manipulate information in a data source, using an abstract model
+ of the data to simplify and standardize the way it is accessed.
+ The shortcut editor model represents the actions as a tree of items, and
+ allow views to access this data via an
+ \l{Model/View Programming#Models}{index-based} system. More generally,
+ models can be used to represent data in the form of a tree structure
+ by allowing each item to act as a parent to a table of child items.
+
+ \section1 Design and Concepts
+
+ The data structure that we use to represent the structure of the data takes
+ the form of a tree built from ShortcutEditorModelItem objects. Each
+ ShortcutEditorModelItem represents an item in a tree view, and contains
+ two columns of data.
+
+ \table
+ \row \li \inlineimage treemodel-structure.png
+ \li \b{Shortcut Editor Structure}
+
+ The data is stored internally in the model using ShortcutEditorModelItem
+ objects that are linked together in a pointer-based tree structure.
+ Generally, each ShortcutEditorModelItem has a parent item, and can have a
+ number of child items. However, the root item in the tree structure has no
+ parent item and it is never referenced outside the model.
+
+ Each ShortcutEditorModelItem contains information about its place in the
+ tree structure; it can return its parent item and its row number. Having
+ this information readily available makes implementing the model easier.
+
+ Since each item in a tree view usually contains several columns of data
+ (a name and a shortcut in this example), it is natural to store this
+ information in each item. For simplicity, we will use a list of QVariant
+ objects to store the data for each column in the item.
+ \endtable
+
+ The use of a pointer-based tree structure means that, when passing a
+ model index to a view, we can record the address of the corresponding
+ item in the index (see QAbstractItemModel::createIndex()) and retrieve
+ it later with QModelIndex::internalPointer(). This makes writing the
+ model easier and ensures that all model indexes that refer to the same
+ item have the same internal data pointer.
+
+ With the appropriate data structure in place, we can create a tree model
+ with a minimal amount of extra code to supply model indexes and data to
+ other components.
+
+ \section1 ShortcutEditorModelItem Class Definition
+
+ The ShortcutEditorModelItem class is defined as follows:
+
+ The class is a basic C++ class. It does not inherit from QObject or
+ provide signals and slots. It is used to hold a list of QVariants,
+ containing column data, and information about its position in the tree
+ structure. The functions provide the following features:
+
+ \list
+ \li The \c appendChildItem() is used to add data when the model is first
+ constructed and is not used during normal use.
+ \li The \c child() and \c childCount() functions allow the model to obtain
+ information about any child items.
+ \li Information about the number of columns associated with the item is
+ provided by \c columnCount(), and the data in each column can be
+ obtained with the data() function.
+ \li The \c row() and \c parent() functions are used to obtain the item's
+ row number and parent item.
+ \endlist
+
+ The parent item and column data are stored in the \c parentItem and
+ \c itemData private member variables. The \c childItems variable contains
+ a list of pointers to the item's own child items.
+
+ \section1 ShortcutEditorModel Class Definition
+
+ The \c ShortcutEditorModel class is defined as follows:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.h 0
+
+ This class is similar to most other subclasses of QAbstractItemModel that
+ provide read-write models. Only the form of the constructor and the
+ \c setupModelData() function are specific to this model. In addition, we
+ provide a destructor to clean up when the model is destroyed.
+
+ \section1 ShortcutEditorModel Class Implementation
+
+ The constructor takes an argument containing the data that the model will
+ share with views and delegates:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 0
+
+ It is up to the constructor to create a root item for the model. This
+ item only contains vertical header data for convenience. We also use it
+ to reference the internal data structure that contains the model data,
+ and it is used to represent an imaginary parent of top-level items in
+ the model.
+
+ The model's internal data structure is populated with items by the
+ \c setupModelData() function. We will examine this function separately
+ at the end of this document.
+
+ The destructor ensures that the root item and all of its descendants
+ are deleted when the model is destroyed:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 1
+
+ Since we cannot add data to the model after it is constructed and set
+ up, this simplifies the way that the internal tree of items is managed.
+
+ Models must implement an \c index() function to provide indexes for
+ views and delegates to use when accessing data. Indexes are created
+ for other components when they are referenced by their row and column
+ numbers, and their parent model index. If an invalid model
+ index is specified as the parent, it is up to the model to return an
+ index that corresponds to a top-level item in the model.
+
+ When supplied with a model index, we first check whether it is valid.
+ If it is not, we assume that a top-level item is being referred to;
+ otherwise, we obtain the data pointer from the model index with its
+ \l{QModelIndex::internalPointer()}{internalPointer()} function and use
+ it to reference a \c TreeItem object. Note that all the model indexes
+ that we construct will contain a pointer to an existing \c TreeItem,
+ so we can guarantee that any valid model indexes that we receive will
+ contain a valid data pointer.
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 2
+
+ Since the row and column arguments to this function refer to a
+ child item of the corresponding parent item, we obtain the item using
+ the \c TreeItem::child() function. The
+ \l{QAbstractItemModel::createIndex()}{createIndex()} function is used
+ to create a model index to be returned. We specify the row and column
+ numbers, and a pointer to the item itself. The model index can be used
+ later to obtain the item's data.
+
+ The way that the \c TreeItem objects are defined makes writing the
+ \c parent() function easy:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 3
+
+ We only need to ensure that we never return a model index corresponding
+ to the root item. To be consistent with the way that the \c index()
+ function is implemented, we return an invalid model index for the
+ parent of any top-level items in the model.
+
+ When creating a model index to return, we must specify the row and
+ column numbers of the parent item within its own parent. We can
+ easily discover the row number with the \c TreeItem::row() function,
+ but we follow a convention of specifying 0 as the column number of
+ the parent. The model index is created with
+ \l{QAbstractItemModel::createIndex()}{createIndex()} in the same way
+ as in the \c index() function.
+
+ The \c rowCount() function simply returns the number of child items
+ for the \c TreeItem that corresponds to a given model index, or the
+ number of top-level items if an invalid index is specified:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 4
+
+ Since each item manages its own column data, the \c columnCount()
+ function has to call the item's own \c columnCount() function to
+ determine how many columns are present for a given model index.
+ As with the \c rowCount() function, if an invalid model index is
+ specified, the number of columns returned is determined from the
+ root item:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 5
+
+ Data is obtained from the model via \c data(). Since the item manages
+ its own columns, we need to use the column number to retrieve the data
+ with the \c TreeItem::data() function:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 6
+
+ Note that we only support the \l{Qt::ItemDataRole}{DisplayRole}
+ in this implementation, and we also return invalid QVariant objects for
+ invalid model indexes.
+
+ We use the \c flags() function to ensure that views know that the
+ model is read-only:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 7
+
+ The \c headerData() function returns data that we conveniently stored
+ in the root item:
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 8
+
+ This information could have been supplied in a different way: either
+ specified in the constructor, or hard coded into the \c headerData()
+ function.
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 9
+
+ TODO
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 10
+
+ TODO
+
+ \snippet widgets/shortcuteditor/shortcuteditormodel.cpp 11
+
+ TODO
+
+ \section1 Setting Up the Data in the Model
+
+ We use the \c setupModelData() function to set up the initial data in
+ the model. This function retrieves the registered actions text and creates
+ item objects that record both the data and the overall model structure.
+ Naturally, this function works in a way that is very specific to
+ this model. We provide the following description of its behavior,
+ and refer the reader to the example code itself for more information.
+
+ To ensure that the model works correctly, it is only necessary to
+ create instances of ShortcutEditorModelItem with the correct data and
+ parent item.
+*/
diff --git a/examples/widgets/widgets/CMakeLists.txt b/examples/widgets/widgets/CMakeLists.txt
index e341da5459..35118b984d 100644
--- a/examples/widgets/widgets/CMakeLists.txt
+++ b/examples/widgets/widgets/CMakeLists.txt
@@ -18,6 +18,7 @@ endif()
qt_internal_add_example(mousebuttons)
qt_internal_add_example(scribble)
qt_internal_add_example(shapedclock)
+qt_internal_add_example(shortcuteditor)
qt_internal_add_example(sliders)
qt_internal_add_example(spinboxes)
qt_internal_add_example(styles)
diff --git a/examples/widgets/widgets/shortcuteditor/CMakeLists.txt b/examples/widgets/widgets/shortcuteditor/CMakeLists.txt
new file mode 100644
index 0000000000..941cffbb09
--- /dev/null
+++ b/examples/widgets/widgets/shortcuteditor/CMakeLists.txt
@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 3.16)
+project(shortcuteditor LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/widgets/shortcuteditor")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
+
+qt_add_executable(shortcuteditor
+ actionmanager.cpp actionmanager.h
+ application.cpp application.h
+ main.cpp
+ mainwindow.cpp mainwindow.h
+ shortcuteditordelegate.cpp shortcuteditordelegate.h
+ shortcuteditormodel.cpp shortcuteditormodel.h
+ shortcuteditorwidget.cpp shortcuteditorwidget.h
+)
+
+set_target_properties(shortcuteditor PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(shortcuteditor PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Widgets
+)
+
+install(TARGETS shortcuteditor
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/widgets/widgets/shortcuteditor/actionmanager.cpp b/examples/widgets/widgets/shortcuteditor/actionmanager.cpp
new file mode 100644
index 0000000000..cfe5f42674
--- /dev/null
+++ b/examples/widgets/widgets/shortcuteditor/actionmanager.cpp
@@ -0,0 +1,57 @@
+// Copyright (C) 2022 Laszlo Papp <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "actionmanager.h"
+
+#include <QAction>
+#include <QApplication>
+#include <QString>
+#include <QVariant>
+
+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<QAction *> 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<ActionIdentifier>().context;
+}
+
+QString ActionManager::categoryForAction(QAction *action)
+{
+ return action->property(kIdPropertyName).value<ActionIdentifier>().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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef ACTIONMANAGER_H
+#define ACTIONMANAGER_H
+
+#include <QList>
+#include <QString>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+QT_END_NAMESPACE
+
+class ActionManager
+{
+public:
+ ActionManager() = default;
+ ~ActionManager() = default;
+
+ QList<QAction*> 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<QAction *> 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 <lpapp@kde.org>
+// 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>();
+}
+
+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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef APPLICATION_H
+#define APPLICATION_H
+
+#include "actionmanager.h"
+
+#include <QApplication>
+
+#include <memory>
+
+class Application : public QApplication
+{
+ Q_OBJECT
+
+public:
+ Application(int &argc, char **argv);
+ ~Application() override = default;
+
+ ActionManager *actionManager() const;
+
+private:
+ std::unique_ptr<ActionManager> 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 <lpapp@kde.org>
+// 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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "mainwindow.h"
+
+#include "actionmanager.h"
+#include "application.h"
+#include "shortcuteditorwidget.h"
+
+#include <QAction>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+MainWindow::MainWindow()
+{
+ QPushButton *topPushButton = new QPushButton("Left");
+ QPushButton *bottomPushButton = new QPushButton("Right");
+ for (auto nameShortcut : std::vector<std::vector<const char *>>{{"red", "r", "shift+r"}, {"green", "g", "shift+g"}, {"blue", "b", "shift+b"}}) {
+ Application *application = static_cast<Application *>(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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "shortcuteditordelegate.h"
+
+#include <QAbstractItemModel>
+#include <QKeySequenceEdit>
+
+//! [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<QKeySequenceEdit *>(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<QKeySequenceEdit *>(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<QKeySequenceEdit *>(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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SHORTCUTEDITORDELEGATE_H
+#define SHORTCUTEDITORDELEGATE_H
+
+#include <QStyledItemDelegate>
+
+//! [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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "shortcuteditormodel.h"
+
+#include "actionmanager.h"
+#include "application.h"
+
+#include <QAction>
+#include <QModelIndex>
+
+// List of actions for all categories
+using CategoryActionsMap = QMap<QString, QList<QAction *>>;
+
+// List of categories for all contexts
+using ActionsMap = QMap<QString, CategoryActionsMap>;
+
+
+ShortcutEditorModel::ShortcutEditorModelItem::ShortcutEditorModelItem(const QList<QVariant> &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<int>(Column::Shortcut) || columnVariant.canConvert<QString>())
+ return columnVariant;
+
+ QAction *action = static_cast<QAction *>(columnVariant.value<void *>());
+ 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<ShortcutEditorModelItem*>(this));
+
+ return 0;
+}
+
+QAction *ShortcutEditorModel::ShortcutEditorModelItem::action() const
+{
+ QVariant actionVariant = m_itemData.at(static_cast<int>(Column::Shortcut));
+ return static_cast<QAction*>(actionVariant.value<void *>());
+}
+
+
+//! [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<ShortcutEditorModelItem*>(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<ShortcutEditorModelItem*>(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<ShortcutEditorModelItem*>(parent.internalPointer());
+
+ return parentItem->childCount();
+}
+//! [5]
+
+//! [6]
+int ShortcutEditorModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return static_cast<ShortcutEditorModelItem*>(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<ShortcutEditorModelItem*>(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<int>(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<Application *>(QCoreApplication::instance());
+ ActionManager *actionManager = application->actionManager();
+ const QList<QAction *> 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<void *>(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<int>(Column::Shortcut)) {
+ QString keySequenceString = value.toString();
+ ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem *>(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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SHORTCUTEDITORMODEL_H
+#define SHORTCUTEDITORMODEL_H
+
+#include <QAbstractItemModel>
+#include <QList>
+#include <QVariant>
+
+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<QVariant> &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<ShortcutEditorModelItem *> m_childItems;
+ QList<QVariant> 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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "shortcuteditorwidget.h"
+
+#include "shortcuteditordelegate.h"
+#include "shortcuteditormodel.h"
+
+#include <QHeaderView>
+#include <QTreeView>
+#include <QVBoxLayout>
+
+//! [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<int>(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 <lpapp@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SHORTCUTEDITORWIDGET_H
+#define SHORTCUTEDITORWIDGET_H
+
+#include <QWidget>
+
+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