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-2021 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 
29 namespace {
30 
31 char const persistentIndexPropertyName[] = "BtMenuView persistent index";
32 constexpr auto const rebuildEventType = QEvent::User;
33 
34 } // anonymous namespace
35 
37  : BtMenuView(QString(), parent)
38 {}
39 
40 BtMenuView::BtMenuView(QString const & title, QWidget * parent)
41  : QMenu(title, parent)
42  , m_model(nullptr)
43  , m_actions(nullptr)
44 {}
45 
47  delete m_actions;
48 }
49 
51  if (m_model)
52  m_model->disconnect(this);
53  m_model = model;
54  rebuildMenu();
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 
75 bool BtMenuView::event(QEvent * const e) {
76  if (e->type() == rebuildEventType) {
77  e->accept();
78  QCoreApplication::removePostedEvents(this, rebuildEventType);
79  rebuildMenu();
80  return true;
81  }
82  return QMenu::event(e);
83 }
84 
85 void BtMenuView::preBuildMenu(QActionGroup *) {
86  // Intentionally empty. Reimplement in subclass if needed.
87 }
88 
89 void BtMenuView::postBuildMenu(QActionGroup *) {
90  // Intentionally empty. Reimplement in subclass if needed.
91 }
92 
93 QAction *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 
143 QMenu *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 
203 void 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
QAbstractItemModel * model() const
Definition: btmenuview.h:66
virtual QMenu * newMenu(QMenu *parentMenu, const QModelIndex &itemIndex)
Definition: btmenuview.cpp:143
~BtMenuView() override
Definition: btmenuview.cpp:46
void triggered(QModelIndex index)
QAbstractItemModel * m_model
Definition: btmenuview.h:129
void buildMenu(QMenu *parentMenu, const QModelIndex &parentIndex)
Definition: btmenuview.cpp:203
void rebuildMenu()
Rebuilds the menu.
Definition: btmenuview.cpp:180
QActionGroup * m_actions
Definition: btmenuview.h:130
virtual void preBuildMenu(QActionGroup *actions)
Definition: btmenuview.cpp:85
virtual QAction * newAction(QMenu *parentMenu, const QModelIndex &itemIndex)
Definition: btmenuview.cpp:93
void removeMenus()
Definition: btmenuview.cpp:237
void setModel(QAbstractItemModel *model)
Definition: btmenuview.cpp:50
bool event(QEvent *e) override
Definition: btmenuview.cpp:75
BtMenuView(QWidget *parent=nullptr)
Definition: btmenuview.cpp:36
virtual void postBuildMenu(QActionGroup *actions)
Definition: btmenuview.cpp:89
constexpr auto const rebuildEventType
Definition: btmenuview.cpp:32