BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
selectablecomboboxeditor.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // qt-mvvm: Model-view-view-model framework for large GUI applications
4 //
5 //! @file mvvm/viewmodel/mvvm/editors/selectablecomboboxeditor.cpp
6 //! @brief Implements class 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 2020
11 //! @authors Gennady Pospelov et al, Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
15 // ----------------------------------------------------------------------------
16 // https://stackoverflow.com/questions/8422760/combobox-of-checkboxes
17 // https://stackoverflow.com/questions/21186779/catch-mouse-button-pressed-signal-from-qcombobox-popup-menu
18 // https://gist.github.com/mistic100/c3b7f3eabc65309687153fe3e0a9a720
19 // ----------------------------------------------------------------------------
20 
25 #include <QAbstractItemView>
26 #include <QComboBox>
27 #include <QLineEdit>
28 #include <QMouseEvent>
29 #include <QStandardItem>
30 #include <QStandardItemModel>
31 #include <QStyledItemDelegate>
32 #include <QVBoxLayout>
33 
34 using namespace ModelView;
35 
36 //! Provides custom style delegate for QComboBox to allow checkboxes.
37 
38 class QCheckListStyledItemDelegate : public QStyledItemDelegate {
39 public:
40  QCheckListStyledItemDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
41 
42  void paint(QPainter* painter, const QStyleOptionViewItem& option,
43  const QModelIndex& index) const override
44  {
45  auto styleOption = const_cast<QStyleOptionViewItem&>(option);
46  styleOption.showDecorationSelected = false;
47  QStyledItemDelegate::paint(painter, styleOption, index);
48  }
49 };
50 
51 // ----------------------------------------------------------------------------
52 
54  : CustomEditor(parent)
55  , m_box(new QComboBox)
56  , m_wheelEventFilter(new WheelEventFilter(this))
57  , m_model(new QStandardItemModel(this))
58 {
59  setAutoFillBackground(true);
60  setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
61 
62  m_box->installEventFilter(m_wheelEventFilter);
63  m_box->view()->viewport()->installEventFilter(this);
64 
65  // Editable mode will be used to have None/Multiple labels on top
66  m_box->setEditable(true);
67  m_box->lineEdit()->setReadOnly(true);
68  m_box->lineEdit()->installEventFilter(this);
69  connect(m_box->lineEdit(), &QLineEdit::selectionChanged, m_box->lineEdit(),
70  &QLineEdit::deselect);
71 
72  // transforms ordinary combo box into check list
73  m_box->setItemDelegate(new QCheckListStyledItemDelegate(this));
74  m_box->setModel(m_model);
75 
76  auto layout = new QVBoxLayout;
77  layout->setMargin(0);
78  layout->setSpacing(0);
79  layout->addWidget(m_box);
80  setLayout(layout);
81  setConnected(true);
82 }
83 
85 {
86  return m_box->sizeHint();
87 }
88 
90 {
91  return m_box->minimumSizeHint();
92 }
93 
95 {
96  return true;
97 }
98 
99 //! Propagate check state from the model to ComboProperty.
100 
101 void SelectableComboBoxEditor::onModelDataChanged(const QModelIndex& topLeft, const QModelIndex&,
102  const QVector<int>& roles)
103 {
104 #if QT_VERSION > QT_VERSION_CHECK(5, 9, 0)
105  // for older versions this role is always empty
106  if (!roles.contains(Qt::CheckStateRole))
107  return;
108 #endif
109 
110  auto item = m_model->itemFromIndex(topLeft);
111  if (!item)
112  return;
113 
114  ComboProperty comboProperty = m_data.value<ComboProperty>();
115  auto state = item->checkState() == Qt::Checked ? true : false;
116  comboProperty.setSelected(topLeft.row(), state);
117 
118  updateBoxLabel();
119  setDataIntern(QVariant::fromValue<ComboProperty>(comboProperty));
120 }
121 
122 //! Processes press event in QComboBox's underlying list view.
123 
124 void SelectableComboBoxEditor::onClickedList(const QModelIndex& index)
125 {
126  if (auto item = m_model->itemFromIndex(index)) {
127  auto state = item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked;
128  item->setCheckState(state);
129  }
130 }
131 
132 //! Handles mouse clicks on QComboBox elements.
133 
134 bool SelectableComboBoxEditor::eventFilter(QObject* obj, QEvent* event)
135 {
136  if (isClickToSelect(obj, event)) {
137  // Handles mouse clicks on QListView when it is expanded from QComboBox
138  // 1) Prevents list from closing while selecting items.
139  // 2) Correctly calculates underlying model index when mouse is over check box style
140  // element.
141  const auto mouseEvent = static_cast<const QMouseEvent*>(event);
142  auto index = m_box->view()->indexAt(mouseEvent->pos());
143  onClickedList(index);
144  return true;
145 
146  } else if (isClickToExpand(obj, event)) {
147  // Expands box when clicking on None/Multiple label
148  m_box->showPopup();
149  return true;
150 
151  } else {
152  // Propagate to the parent class.
153  return QObject::eventFilter(obj, event);
154  }
155 }
156 
158 {
159  if (!m_data.canConvert<ComboProperty>())
160  return;
161 
162  ComboProperty property = m_data.value<ComboProperty>();
163 
164  setConnected(false);
165  m_model->clear();
166 
167  auto labels = property.values();
168  auto selectedIndices = property.selectedIndices();
169 
170  for (size_t i = 0; i < labels.size(); ++i) {
171  auto item = new QStandardItem(QString::fromStdString(labels[i]));
172  m_model->invisibleRootItem()->appendRow(item);
173  item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
174  item->setCheckable(true);
175 
176  auto state = Utils::Contains(selectedIndices, i) ? Qt::Checked : Qt::Unchecked;
177  item->setData(state, Qt::CheckStateRole);
178  }
179 
180  setConnected(true);
181  updateBoxLabel();
182 }
183 
185 {
186  if (isConnected) {
187  connect(m_model, &QStandardItemModel::dataChanged, this,
189  } else {
190  disconnect(m_model, &QStandardItemModel::dataChanged, this,
192  }
193 }
194 
195 //! Update text on QComboBox with the label provided by combo property.
196 
198 {
199  ComboProperty combo = m_data.value<ComboProperty>();
200  m_box->setCurrentText(QString::fromStdString(combo.label()));
201 }
202 
203 bool SelectableComboBoxEditor::isClickToSelect(QObject* obj, QEvent* event) const
204 {
205  return obj == m_box->view()->viewport() && event->type() == QEvent::MouseButtonRelease;
206 }
207 
208 bool SelectableComboBoxEditor::isClickToExpand(QObject* obj, QEvent* event) const
209 {
210  return obj == m_box->lineEdit() && event->type() == QEvent::MouseButtonRelease;
211 }
Custom property to define list of string values with multiple selections.
Definition: comboproperty.h:27
std::string label() const
Returns the label to show.
void setSelected(int index, bool value=true)
Sets given index selection flag.
Base class for all custom variant editors.
Definition: customeditor.h:26
void setDataIntern(const QVariant &data)
Saves the data as given by editor's internal components and notifies the model.
bool isClickToSelect(QObject *obj, QEvent *event) const
void update_components() override
Should update widget components from m_data, if necessary.
bool isClickToExpand(QObject *obj, QEvent *event) const
bool is_persistent() const override
Returns true if editor should remains alive after editing finished.
void updateBoxLabel()
Update text on QComboBox with the label provided by combo property.
bool eventFilter(QObject *obj, QEvent *event) override
Handles mouse clicks on QComboBox elements.
void onModelDataChanged(const QModelIndex &, const QModelIndex &, const QVector< int > &)
Propagate check state from the model to ComboProperty.
void onClickedList(const QModelIndex &index)
Processes press event in QComboBox's underlying list view.
Event filter to install on combo boxes and spin boxes to ignore wheel events during scrolling.
Provides custom style delegate for QComboBox to allow checkboxes.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
QCheckListStyledItemDelegate(QObject *parent=nullptr)
Defines class CLASS?
Defines class CLASS?
Defines class CLASS?
bool Contains(const A &container, const B &element)
Returns true if container contains a given element.
materialitems.h Collection of materials to populate MaterialModel.
QVariant CheckStateRole(const SessionItem &item)
Returns check state for given item.
Defines class CLASS?