BibleTime
btmenuview.cpp
Go to the documentation of this file.
1/*********
2*
3* In the name of the Father, and of the Son, and of the Holy Spirit.
4*
5* This file is part of BibleTime's source code, https://bibletime.info/
6*
7* Copyright 1999-2025 by the BibleTime developers.
8* The BibleTime source code is licensed under the GNU General Public License
9* version 2.0.
10*
11**********/
12
13#include "btmenuview.h"
14
15#include <QAbstractItemModel>
16#include <QAction>
17#include <QActionGroup>
18#include <QCoreApplication>
19#include <QEvent>
20#include <QIcon>
21#include <QModelIndex>
22#include <QPersistentModelIndex>
23#include <QVariant>
24#include <Qt>
25#include "../util/btassert.h"
26#include "../util/btconnect.h"
27
28
29namespace {
30
31char const persistentIndexPropertyName[] = "BtMenuView persistent index";
32constexpr auto const rebuildEventType = QEvent::User;
33
34} // anonymous namespace
35
37 : BtMenuView(QString(), parent)
38{}
39
40BtMenuView::BtMenuView(QString const & title, QWidget * parent)
41 : QMenu(title, parent)
42 , m_model(nullptr)
43 , m_actions(nullptr)
44{}
45
49
51 if (m_model)
52 m_model->disconnect(this);
53 m_model = model;
55 if (model) {
56 /* Instead of connecting each signal to rebuildMenu() directly, we
57 instead queue events to this BtMenuView instance. The multiple
58 "rebuild" events can be merged by the event handler to avoid
59 consecutive rebuild events from causing needless rebuilds. */
60 auto const triggerRebuild =
61 [this] {
62 QCoreApplication::postEvent(this,
63 new QEvent(rebuildEventType),
64 Qt::HighEventPriority);
65 };
66 BT_CONNECT(model, &QAbstractItemModel::dataChanged, triggerRebuild);
67 BT_CONNECT(model, &QAbstractItemModel::layoutChanged, triggerRebuild);
68 BT_CONNECT(model, &QAbstractItemModel::modelReset, triggerRebuild);
69 BT_CONNECT(model, &QAbstractItemModel::rowsInserted, triggerRebuild);
70 BT_CONNECT(model, &QAbstractItemModel::rowsMoved, triggerRebuild);
71 BT_CONNECT(model, &QAbstractItemModel::rowsRemoved, triggerRebuild);
72 }
73}
74
75bool BtMenuView::event(QEvent * const e) {
76 if (e->type() == rebuildEventType) {
77 e->accept();
78 QCoreApplication::removePostedEvents(this, rebuildEventType);
80 return true;
81 }
82 return QMenu::event(e);
83}
84
85void BtMenuView::preBuildMenu(QActionGroup *) {
86 // Intentionally empty. Reimplement in subclass if needed.
87}
88
89void BtMenuView::postBuildMenu(QActionGroup *) {
90 // Intentionally empty. Reimplement in subclass if needed.
91}
92
93QAction *BtMenuView::newAction(QMenu *parentMenu, const QModelIndex &itemIndex) {
94 QVariant displayData(m_model->data(itemIndex, Qt::DisplayRole));
95 QVariant iconData(m_model->data(itemIndex, Qt::DecorationRole));
96 QVariant toolTipData(m_model->data(itemIndex, Qt::ToolTipRole));
97 QVariant statusTipData(m_model->data(itemIndex, Qt::StatusTipRole));
98 QVariant whatsThisData(m_model->data(itemIndex, Qt::WhatsThisRole));
99
100 QAction *childAction = new QAction(parentMenu);
101
102 // Set text:
103 if (displayData.canConvert<QString>()) {
104 childAction->setText(displayData.toString());
105 }
106
107 // Set icon:
108 if (iconData.canConvert<QIcon>()) {
109 childAction->setIcon(iconData.value<QIcon>());
110 }
111
112 // Set tooltip:
113 if (toolTipData.canConvert<QString>()) {
114 childAction->setToolTip(toolTipData.toString());
115 }
116
117 // Set status tip:
118 if (statusTipData.canConvert<QString>()) {
119 childAction->setStatusTip(statusTipData.toString());
120 }
121
122 // Set whatsthis:
123 if (whatsThisData.canConvert<QString>()) {
124 childAction->setWhatsThis(whatsThisData.toString());
125 }
126
127 // Set checkable:
128 if (m_model->flags(itemIndex).testFlag(Qt::ItemIsUserCheckable)) {
129 childAction->setCheckable(true);
130 }
131
132 // Set checked:
133 QVariant checkData(m_model->data(itemIndex, Qt::CheckStateRole));
134 bool ok;
135 Qt::CheckState const state =
136 static_cast<Qt::CheckState>(checkData.toInt(&ok));
137 if (ok)
138 childAction->setChecked(state == Qt::Checked);
139
140 return childAction;
141}
142
143QMenu *BtMenuView::newMenu(QMenu *parentMenu, const QModelIndex &itemIndex) {
144 QVariant displayData(m_model->data(itemIndex, Qt::DisplayRole));
145 QVariant iconData(m_model->data(itemIndex, Qt::DecorationRole));
146 QVariant toolTipData(m_model->data(itemIndex, Qt::ToolTipRole));
147 QVariant statusTipData(m_model->data(itemIndex, Qt::StatusTipRole));
148 QVariant whatsThisData(m_model->data(itemIndex, Qt::WhatsThisRole));
149
150 QMenu *childMenu = new QMenu(parentMenu);
151
152 // Set text:
153 if (displayData.canConvert<QString>()) {
154 childMenu->setTitle(displayData.toString());
155 }
156
157 // Set icon:
158 if (iconData.canConvert<QIcon>()) {
159 childMenu->setIcon(iconData.value<QIcon>());
160 }
161
162 // Set tooltip:
163 if (toolTipData.canConvert<QString>()) {
164 childMenu->setToolTip(toolTipData.toString());
165 }
166
167 // Set status tip:
168 if (statusTipData.canConvert<QString>()) {
169 childMenu->setStatusTip(statusTipData.toString());
170 }
171
172 // Set whatsthis:
173 if (whatsThisData.canConvert<QString>()) {
174 childMenu->setWhatsThis(whatsThisData.toString());
175 }
176
177 return childMenu;
178}
179
181 removeMenus();
182
183 delete m_actions;
184 m_actions = m_model ? new QActionGroup(this) : nullptr;
185
187 if (m_model)
188 buildMenu(this, QModelIndex());
190
191 if (m_actions)
192 BT_CONNECT(m_actions, &QActionGroup::triggered,
193 [this](QAction * const action){
194 auto const indexProperty =
195 action->property(persistentIndexPropertyName);
196 if (!indexProperty.isValid())
197 return;
198 Q_EMIT triggered(
199 indexProperty.toPersistentModelIndex());
200 });
201}
202
203void BtMenuView::buildMenu(QMenu *parentMenu, const QModelIndex &parentIndex) {
206
207 int children = m_model->rowCount(parentIndex);
208 for (int i = 0; i < children; i++) {
209 QModelIndex childIndex(m_model->index(i, 0, parentIndex));
210
211 if (m_model->rowCount(childIndex) > 0) {
212 QMenu *childMenu = newMenu(parentMenu, childIndex);
213
214 if (childMenu != nullptr) {
215 // Add the child menu and populate it:
216 parentMenu->addMenu(childMenu);
217 buildMenu(childMenu, childIndex);
218 }
219 } else {
220 QAction *childAction = newAction(parentMenu, childIndex);
221
222 if (childAction != nullptr) {
223
224 // Add action to action group:
225 childAction->setActionGroup(m_actions);
226
227 // Add action to menu:
228 parentMenu->addAction(childAction);
229
230 // Pass persistent model index for handling of triggered():
231 childAction->setProperty(persistentIndexPropertyName,
232 QPersistentModelIndex(childIndex));
233 }
234 }
235 }
236}
238 // QMenu::clear() is documented only to delete direct child actions:
239 clear();
240
241 // Delete submenus also:
242 for (auto * const childMenu
243 : findChildren<QMenu *>(QString(), Qt::FindDirectChildrenOnly))
244 childMenu->deleteLater();
245}
#define BT_ASSERT(...)
Definition btassert.h:17
#define BT_CONNECT(...)
Definition btconnect.h:20
virtual QMenu * newMenu(QMenu *parentMenu, const QModelIndex &itemIndex)
~BtMenuView() override
void triggered(QModelIndex index)
QAbstractItemModel * model() const
Definition btmenuview.h:66
QAbstractItemModel * m_model
Definition btmenuview.h:129
void buildMenu(QMenu *parentMenu, const QModelIndex &parentIndex)
void rebuildMenu()
Rebuilds the menu.
QActionGroup * m_actions
Definition btmenuview.h:130
virtual void preBuildMenu(QActionGroup *actions)
virtual QAction * newAction(QMenu *parentMenu, const QModelIndex &itemIndex)
void removeMenus()
void setModel(QAbstractItemModel *model)
bool event(QEvent *e) override
BtMenuView(QWidget *parent=nullptr)
virtual void postBuildMenu(QActionGroup *actions)
constexpr auto const rebuildEventType