BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
MultiComboPropertyEditor.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/coregui/Views/PropertyEditor/MultiComboPropertyEditor.cpp
6 //! @brief Defines MultiComboPropertyEditor class
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
18 #include <QComboBox>
19 #include <QEvent>
20 #include <QLineEdit>
21 #include <QListView>
22 #include <QMouseEvent>
23 #include <QStandardItem>
24 #include <QStandardItemModel>
25 #include <QVBoxLayout>
26 
28  : QStyledItemDelegate(parent)
29 {
30 }
31 
32 void QCheckListStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
33  const QModelIndex& index) const
34 {
35  QStyleOptionViewItem& styleOption = const_cast<QStyleOptionViewItem&>(option);
36  styleOption.showDecorationSelected = false;
37  QStyledItemDelegate::paint(painter, styleOption, index);
38 }
39 
40 // ----------------------------------------------------------------------------
41 // https://stackoverflow.com/questions/8422760/combobox-of-checkboxes
42 // https://stackoverflow.com/questions/21186779/catch-mouse-button-pressed-signal-from-qcombobox-popup-menu
43 // https://gist.github.com/mistic100/c3b7f3eabc65309687153fe3e0a9a720
44 // ----------------------------------------------------------------------------
45 
47  : CustomEditor(parent)
48  , m_box(new QComboBox)
49  , m_wheel_event_filter(new WheelEventEater(this))
50  , m_model(new QStandardItemModel(this))
51 {
52  setAutoFillBackground(true);
53  setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
54 
55  m_box->installEventFilter(m_wheel_event_filter);
56  m_box->view()->viewport()->installEventFilter(this);
57 
58  // Editable mode will be used to have None/Multiple labels on top
59  m_box->setEditable(true);
60  m_box->lineEdit()->setReadOnly(true);
61  m_box->lineEdit()->installEventFilter(this);
62  connect(m_box->lineEdit(), &QLineEdit::selectionChanged, m_box->lineEdit(),
63  &QLineEdit::deselect);
64 
65  // transforms ordinary combo box into check list
66  m_box->setItemDelegate(new QCheckListStyledItemDelegate(this));
67  m_box->setModel(m_model);
68 
69  auto layout = new QVBoxLayout;
70  layout->setMargin(0);
71  layout->setSpacing(0);
72  layout->addWidget(m_box);
73  setLayout(layout);
74  setConnected(true);
75 }
76 
78 {
79  return m_box->sizeHint();
80 }
81 
83 {
84  return m_box->minimumSizeHint();
85 }
86 
87 //! Propagate check state from the model to ComboProperty.
88 
89 void MultiComboPropertyEditor::onModelDataChanged(const QModelIndex& topLeft, const QModelIndex&,
90  const QVector<int>&)
91 {
92  // on Qt 5.9 roles remains empty for checked state. It will stop working if uncomment.
93  // if (!roles.contains(Qt::CheckStateRole))
94  // return;
95 
96  auto item = m_model->itemFromIndex(topLeft);
97  if (!item)
98  return;
99 
100  ComboProperty comboProperty = m_data.value<ComboProperty>();
101  auto state = item->checkState() == Qt::Checked;
102  comboProperty.setSelected(topLeft.row(), state);
103 
104  updateBoxLabel();
105  setDataIntern(QVariant::fromValue<ComboProperty>(comboProperty));
106 }
107 
108 //! Processes press event in QComboBox's underlying list view.
109 
110 void MultiComboPropertyEditor::onClickedList(const QModelIndex& index)
111 {
112  if (auto item = m_model->itemFromIndex(index)) {
113  auto state = item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked;
114  item->setCheckState(state);
115  }
116 }
117 
118 //! Handles mouse clicks on QComboBox elements.
119 
120 bool MultiComboPropertyEditor::eventFilter(QObject* obj, QEvent* event)
121 {
122  if (isClickToSelect(obj, event)) {
123  // Handles mouse clicks on QListView when it is expanded from QComboBox
124  // 1) Prevents list from closing while selecting items.
125  // 2) Correctly calculates underlying model index when mouse is over check box style
126  // element.
127  const auto mouseEvent = static_cast<const QMouseEvent*>(event);
128  auto index = m_box->view()->indexAt(mouseEvent->pos());
129  onClickedList(index);
130  return true;
131 
132  } else if (isClickToExpand(obj, event)) {
133  // Expands box when clicking on None/Multiple label
134  m_box->showPopup();
135  return true;
136 
137  } else {
138  // Propagate to the parent class.
139  return QObject::eventFilter(obj, event);
140  }
141 }
142 
144 {
145  if (!m_data.canConvert<ComboProperty>())
146  return;
147 
148  ComboProperty property = m_data.value<ComboProperty>();
149 
150  setConnected(false);
151  m_model->clear();
152 
153  auto labels = property.getValues();
154  auto selectedIndices = property.selectedIndices();
155 
156  for (int i = 0; i < labels.size(); ++i) {
157  auto item = new QStandardItem(labels[i]);
158  m_model->invisibleRootItem()->appendRow(item);
159  item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
160  item->setCheckable(true);
161 
162  auto state = selectedIndices.contains(i) ? Qt::Checked : Qt::Unchecked;
163  item->setData(state, Qt::CheckStateRole);
164  }
165 
166  setConnected(true);
167  updateBoxLabel();
168 }
169 
171 {
172  if (isConnected) {
173  connect(m_model, &QStandardItemModel::dataChanged, this,
175  } else {
176  disconnect(m_model, &QStandardItemModel::dataChanged, this,
178  }
179 }
180 
181 //! Update text on QComboBox with the label provided by combo property.
182 
184 {
185  ComboProperty combo = m_data.value<ComboProperty>();
186  m_box->setCurrentText(combo.label());
187 }
188 
189 bool MultiComboPropertyEditor::isClickToSelect(QObject* obj, QEvent* event) const
190 {
191  return obj == m_box->view()->viewport() && event->type() == QEvent::MouseButtonRelease;
192 }
193 
194 bool MultiComboPropertyEditor::isClickToExpand(QObject* obj, QEvent* event) const
195 {
196  return obj == m_box->lineEdit() && event->type() == QEvent::MouseButtonRelease;
197 }
Defines class ComboProperty.
Defines classes releted to event filtering.
Defines MultiComboPropertyEditor class.
Custom property to define list of string values with multiple selections.
Definition: ComboProperty.h:25
void setSelected(int index, bool value=true)
Sets given index selection flag.
QString label() const
Returns the label to show.
Base class for all custom variants editors.
Definition: CustomEditors.h:28
QVariant m_data
Definition: CustomEditors.h:45
void setDataIntern(const QVariant &data)
Saves the data from the editor and informs external delegates.
MultiComboPropertyEditor(QWidget *parent=nullptr)
void onModelDataChanged(const QModelIndex &, const QModelIndex &, const QVector< int > &)
Propagate check state from the model to ComboProperty.
bool isClickToExpand(QObject *obj, QEvent *event) const
void updateBoxLabel()
Update text on QComboBox with the label provided by combo property.
void onClickedList(const QModelIndex &index)
Processes press event in QComboBox's underlying list view.
bool isClickToSelect(QObject *obj, QEvent *event) const
bool eventFilter(QObject *obj, QEvent *event)
Handles mouse clicks on QComboBox elements.
class WheelEventEater * m_wheel_event_filter
void initEditor()
Inits editor widgets from m_data.
void setConnected(bool isConnected)
Provides custom style delegate for QComboBox to allow checkboxes.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
QCheckListStyledItemDelegate(QObject *parent=nullptr)
Event filter to install on combo boxes and spin boxes to not to react on wheel events during scrollin...
QVariant CheckStateRole(const SessionItem &item)
Returns check state for given item.