BornAgain  1.19.79
Simulate and fit neutron and x-ray scattering at grazing incidence
ItemViewOverlayButtons.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/View/Common/ItemViewOverlayButtons.cpp
6 //! @brief Implements class ItemViewOverlayButtons
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2021
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
16 #include <QAbstractItemView>
17 #include <QAction>
18 #include <QApplication>
19 #include <QBoxLayout>
20 #include <QKeyEvent>
21 #include <QStyledItemDelegate>
22 #include <QToolButton>
23 
24 namespace {
25 
26 class ItemViewOverlayWidget : public QWidget {
27 public:
28  ItemViewOverlayWidget(QAbstractItemView* view, const QModelIndex& index);
29 
30  static int heightForDelegate();
31 
32  void setHover(bool b);
33  void create();
34  void hover(bool h);
35  void setHorizontalAlignment(Qt::Alignment a);
36 
37 protected:
38  void enterEvent(QEvent* event) override;
39  void leaveEvent(QEvent* event) override;
40  void mouseDoubleClickEvent(QMouseEvent* event) override;
41 
42 private:
43  bool m_hover;
44  QAbstractItemView* m_view;
45  QModelIndex m_index;
46  Qt::Alignment m_horizontalAlignment;
47 };
48 
49 class ItemViewOverlayDelegate : public QStyledItemDelegate {
50 public:
51  QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
52  {
53  QSize s = QStyledItemDelegate::sizeHint(option, index);
54  if (index.parent().isValid()) {
55  const int h = ItemViewOverlayWidget::heightForDelegate();
56  s.setHeight(std::max(s.height(), h));
57  }
58 
59  return s;
60  }
61 };
62 
63 ItemViewOverlayWidget::ItemViewOverlayWidget(QAbstractItemView* view, const QModelIndex& index)
64  : QWidget(view)
65  , m_hover(false)
66  , m_view(view)
67  , m_index(index)
68  , m_horizontalAlignment(Qt::AlignRight)
69 {
70  setMouseTracking(true);
71  setFocusPolicy(Qt::NoFocus); // do not receive keypress!
72 }
73 
74 void ItemViewOverlayWidget::setHover(bool b)
75 {
76  m_hover = b;
77 }
78 
79 int ItemViewOverlayWidget::heightForDelegate()
80 {
81  QToolButton btn;
82  const int size = QApplication::style()->pixelMetric(QStyle::PM_ToolBarIconSize);
83  btn.setIconSize(QSize(size, size));
84  return btn.sizeHint().height() + 6; // 3px on top and bottom
85 }
86 
87 void ItemViewOverlayWidget::enterEvent(QEvent*)
88 {
89  hover(true);
90 }
91 
92 void ItemViewOverlayWidget::leaveEvent(QEvent*)
93 {
94  hover(false);
95 }
96 
97 void ItemViewOverlayWidget::mouseDoubleClickEvent(QMouseEvent* ev)
98 {
99  if (m_view->editTriggers().testFlag(QAbstractItemView::DoubleClicked)
100  && m_index.flags().testFlag(Qt::ItemIsEditable)) {
101  m_view->setIndexWidget(m_index, nullptr);
102  m_view->edit(m_index);
103  ev->accept();
104  return;
105  }
106 
107  ev->ignore();
108 }
109 
110 void ItemViewOverlayWidget::create()
111 {
112  auto* h1 = new QHBoxLayout;
113 
114  h1->setContentsMargins(0, 3, 5, 3);
115  h1->setAlignment(m_horizontalAlignment | Qt::AlignTop);
116  setLayout(h1);
117 
118  for (auto* a : actions()) {
119  auto* btn = new QToolButton(this);
120  btn->setDefaultAction(a);
121  Qt::ToolButtonStyle btnStyle = (Qt::ToolButtonStyle)a->data().toInt();
122  btn->setToolButtonStyle(btnStyle);
123 
124  const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
125  btn->setIconSize(QSize(size, size));
126 
127  if (a->menu() != nullptr)
128  btn->setPopupMode(QToolButton::InstantPopup);
129 
130  h1->addWidget(btn);
131 
132  if (m_hover)
133  btn->hide();
134  }
135 }
136 
137 void ItemViewOverlayWidget::hover(bool h)
138 {
139  if (!m_hover)
140  return;
141 
142  if (h) {
143  if (actions().size() != findChildren<QToolButton*>().size())
144  create();
145  for (auto* w : findChildren<QToolButton*>())
146  w->show();
147  } else {
148  for (auto* w : findChildren<QToolButton*>())
149  w->hide();
150  }
151 }
152 
153 void ItemViewOverlayWidget::setHorizontalAlignment(Qt::Alignment a)
154 {
155  m_horizontalAlignment = a;
156 }
157 
158 } // namespace
159 
160 void ItemViewOverlayButtons::install(QAbstractItemView* view, FnGetActions fnGetActions)
161 {
162  auto* h = new ItemViewOverlayButtons(view);
163  h->m_getActions = fnGetActions;
164  h->m_view = view;
165  auto* d = new ItemViewOverlayDelegate;
166  view->setItemDelegate(d);
167  view->installEventFilter(h);
168  h->update();
169 
170  connect(d, &QAbstractItemDelegate::closeEditor, h, &ItemViewOverlayButtons::update);
171 
172  connect(view->model(), &QAbstractItemModel::modelReset, h, &ItemViewOverlayButtons::update,
173  Qt::QueuedConnection);
174  connect(view->model(), &QAbstractItemModel::rowsInserted, h, &ItemViewOverlayButtons::update,
175  Qt::QueuedConnection); // Queued: important!
176  connect(view->model(), &QAbstractItemModel::rowsRemoved, h, &ItemViewOverlayButtons::update,
177  Qt::QueuedConnection); // Queued: important!
178 }
179 
180 bool ItemViewOverlayButtons::eventFilter(QObject* obj, QEvent* event)
181 {
182  // F2 would start the editing. Since OverlayWidgets are an editor already (indexWidget uses the
183  // editor internals), we remove the overlay at this place, so the view will properly open the
184  // editor
185  if (event->type() == QEvent::KeyPress) {
186  auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
187  if (keyEvent->key() == Qt::Key_F2) {
188  QModelIndex ci = m_view->currentIndex();
189  if (ci.isValid() && ci.flags().testFlag(Qt::ItemIsEditable))
190  m_view->setIndexWidget(ci, nullptr);
191  }
192  }
193  return QObject::eventFilter(obj, event);
194 }
195 
197  : QObject(parent)
198 {
199 }
200 
201 void ItemViewOverlayButtons::updateRecursive(const QModelIndex& index)
202 {
203  const auto hoverIfNecessary = [&](QModelIndex index) {
204  QPoint viewPortCoordinatesOfMouse = m_view->mapFromGlobal(QCursor::pos());
205  if (m_view->indexAt(viewPortCoordinatesOfMouse) == index)
206  if (auto* w = dynamic_cast<ItemViewOverlayWidget*>(m_view->indexWidget(index)))
207  w->hover(true);
208  };
209 
210  if (m_view->indexWidget(index) == nullptr)
211  installOverlay(index);
212  hoverIfNecessary(index);
213 
214  auto* m = m_view->model();
215  for (int childRow = 0; childRow < m->rowCount(index); childRow++)
216  updateRecursive(m->index(childRow, 0, index));
217 }
218 
219 
221 {
222  if (m_view->model() == nullptr)
223  return;
224  auto* m = m_view->model();
225  for (int row = 0; row < m->rowCount(); row++)
226  updateRecursive(m->index(row, 0));
227 }
228 
229 void ItemViewOverlayButtons::installOverlay(const QModelIndex& index)
230 {
231  const auto permanentActions = m_getActions(index, false);
232  const auto hoverActions = m_getActions(index, true);
233 
234  if (permanentActions.isEmpty() && hoverActions.isEmpty())
235  return;
236 
237  auto* w = new ItemViewOverlayWidget(m_view, index);
238 
239  const auto setAlignment = [&](const QList<QAction*> actions) {
240  w->setHorizontalAlignment(Qt::AlignRight);
241  if (actions.first() == nullptr && actions.last() == nullptr)
242  w->setHorizontalAlignment(Qt::AlignCenter);
243  else if (actions.first() != nullptr && actions.last() == nullptr)
244  w->setHorizontalAlignment(Qt::AlignLeft);
245  };
246 
247  if (!permanentActions.isEmpty()) {
248  setAlignment(permanentActions);
249  w->addActions(permanentActions);
250  w->setHover(false);
251  } else if (!hoverActions.isEmpty()) {
252  setAlignment(hoverActions);
253  w->addActions(hoverActions);
254  w->setHover(true);
255  }
256 
257  w->create();
258  m_view->setIndexWidget(index, w);
259 }
Defines class ItemViewOverlayButtons.
void updateRecursive(const QModelIndex &index)
bool eventFilter(QObject *obj, QEvent *event) override
static void install(QAbstractItemView *view, FnGetActions fnGetActions)
std::function< QList< QAction * >(const QModelIndex &, bool)> FnGetActions
ItemViewOverlayButtons(QObject *parent)
QAbstractItemView * m_view
void installOverlay(const QModelIndex &index)