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>
bb10
Laszlo Papp 2022-07-09 20:57:14 +01:00
parent f263211484
commit faeeb42b85
17 changed files with 997 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -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.
*/

View File

@ -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)

View File

@ -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}"
)

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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