multicombobox.cpp
1 //------------------------------------------------------------------------------
2 // multicombobox.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2013 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "multicombobox.h"
24 #include <QAbstractItemView>
25 #include <QApplication>
26 #include <QCheckBox>
27 #include <QItemDelegate>
28 #include <QStylePainter>
29 
30 // internal private editor
31 class MultiComboBoxEditor : public QCheckBox
32 {
33 public:
34  MultiComboBoxEditor(QWidget *parent)
35  : QCheckBox(parent) {}
36 
37 protected:
38  bool hitButton(const QPoint &pos) const override
39  {
40  // Omit QCheckBox::hitButton() check as it returns true only if
41  // user actually clicked on the checkbox or on its title. In this
42  // case, we want to detect hits on entire widget, which spans
43  // as a whitespace area covering entire row in combobox' list.
44  return QAbstractButton::hitButton(pos);
45  }
46 };
47 
48 
49 // internal private delegate
50 class MultiComboBoxDelegate : public QItemDelegate
51 {
52 public:
53 
54  MultiComboBoxDelegate(QObject *parent)
55  : QItemDelegate(parent) {}
56 
57  void paint(QPainter *painter, const QStyleOptionViewItem &option,
58  const QModelIndex &index) const override
59  {
60  //Get item data
61  bool value = index.data(Qt::UserRole).toBool();
62  QString text = index.data(Qt::DisplayRole).toString();
63 
64  // fill style options with item data
65  const QStyle *style = QApplication::style();
66  QStyleOptionButton opt;
67  opt.state |= value ? QStyle::State_On : QStyle::State_Off;
68  opt.state |= QStyle::State_Enabled;
69  opt.text = text;
70  opt.rect = option.rect;
71 
72  // draw item data as CheckBox
73  style->drawControl(QStyle::CE_CheckBox, &opt, painter);
74  }
75 
76  QWidget *createEditor(QWidget *parent,
77  const QStyleOptionViewItem &option,
78  const QModelIndex &index ) const override
79  {
80  Q_UNUSED(option)
81  Q_UNUSED(index)
82  return new MultiComboBoxEditor(parent);
83  }
84 
85  void setEditorData(QWidget *editor, const QModelIndex &index) const override
86  {
87  auto myEditor = static_cast<QCheckBox *>(editor);
88  myEditor->setText(index.data(Qt::DisplayRole).toString());
89  myEditor->setChecked(index.data(Qt::UserRole).toBool());
90  }
91 
92  void setModelData(QWidget *editor, QAbstractItemModel *model,
93  const QModelIndex &index) const override
94  {
95  //get the value from the editor (CheckBox)
96  auto myEditor = static_cast<QCheckBox *>(editor);
97  bool value = myEditor->isChecked();
98 
99 
100  //set model data
101  QMap<int, QVariant> data;
102  data.insert(Qt::DisplayRole, myEditor->text());
103  data.insert(Qt::UserRole, value);
104  model->setItemData(index, data);
105  }
106 
107  void updateEditorGeometry(QWidget *editor,
108  const QStyleOptionViewItem &option, const QModelIndex &index ) const override
109  {
110  Q_UNUSED(index);
111  editor->setGeometry(option.rect);
112  }
113 };
114 
115 
116 //min-width:10em;
117 MultiComboBox::MultiComboBox(QWidget *widget )
118  : QComboBox(widget)
119 {
120  view()->setItemDelegate(new MultiComboBoxDelegate(this));
121  // Enable editing on items view
122  view()->setEditTriggers(QAbstractItemView::CurrentChanged);
123  view()->viewport()->installEventFilter(this);
124  view()->setAlternatingRowColors(true);
125 }
126 
127 
128 MultiComboBox::~MultiComboBox()
129 {
130 }
131 
132 QString MultiComboBox::displayText() const
133 {
134  return selectedItemTexts().join(", ");
135 }
136 
137 bool MultiComboBox::eventFilter(QObject *object, QEvent *event)
138 {
139  if (object == view()->viewport())
140  {
141  if (handleViewViewportEvent(event))
142  return true;
143  }
144  return QComboBox::eventFilter(object, event);
145 }
146 
147 bool MultiComboBox::handleViewViewportEvent(QEvent *event)
148 {
149  switch (event->type())
150  {
151  default:
152  break;
153 
154  case QEvent::Hide:
155  // TODO: Have this react only if value actually changed.
156  emit valueChanged();
157  break;
158 
159  case QEvent::Show:
160  if (count() >= 1)
161  {
162  // Without this, the first object on the list will be
163  // impossible to edit unless other object is hovered
164  // over first, or in case if there's only one object
165  // on the list, the list won't be editable at all.
166  view()->edit(model()->index(0, 0));
167  }
168  break;
169 
170  case QEvent::MouseButtonRelease:
171  // Don't close items view after we release the mouse button
172  // by simple eating MouseButtonRelease in viewport of items
173  // view.
174  return true;
175  }
176  return false;
177 }
178 
179 void MultiComboBox::paintEvent(QPaintEvent *)
180 {
181  QStylePainter painter(this);
182  painter.setPen(palette().color(QPalette::Text));
183 
184  // draw the combobox frame, focusrect and selected etc.
185  QStyleOptionComboBox opt;
186  initStyleOption(&opt);
187 
188  // draw the icon and text
189  opt.currentText = displayText();
190  painter.drawComplexControl(QStyle::CC_ComboBox, opt);
191  painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
192 }
193 
194 QStringList MultiComboBox::selectedItemTexts() const
195 {
196  QStringList result;
197  for (int i = 0; i < count(); ++i)
198  {
199  bool checked = itemData(i).toBool();
200  if (checked)
201  result << itemText(i);
202  }
203  return result;
204 }
205 
206 void MultiComboBox::setSelectedTexts(const QStringList &texts)
207 {
208  for (int i = 0; i < count(); ++i)
209  setItemData(i, static_cast<bool>(texts.contains(itemText(i))));
210  // Prompt widget repaint or the display text may not change.
211  update();
212 }