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  }
38 
39 protected:
40  bool hitButton(const QPoint& pos) const
41  {
42  // Omit QCheckBox::hitButton() check as it returns true only if
43  // user actually clicked on the checkbox or on its title. In this
44  // case, we want to detect hits on entire widget, which spans
45  // as a whitespace area covering entire row in combobox' list.
46  return QAbstractButton::hitButton(pos);
47  }
48 };
49 
50 
51 // internal private delegate
52 class MultiComboBoxDelegate : public QItemDelegate
53 {
54 public:
55 
56  MultiComboBoxDelegate(QObject *parent)
57  : QItemDelegate(parent)
58  {
59  }
60 
61  void paint(QPainter *painter, const QStyleOptionViewItem &option,
62  const QModelIndex &index) const
63  {
64  //Get item data
65  bool value = index.data(Qt::UserRole).toBool();
66  QString text = index.data(Qt::DisplayRole).toString();
67 
68  // fill style options with item data
69  const QStyle *style = QApplication::style();
70  QStyleOptionButton opt;
71  opt.state |= value ? QStyle::State_On : QStyle::State_Off;
72  opt.state |= QStyle::State_Enabled;
73  opt.text = text;
74  opt.rect = option.rect;
75 
76  // draw item data as CheckBox
77  style->drawControl(QStyle::CE_CheckBox,&opt,painter);
78  }
79 
80  QWidget *createEditor(QWidget *parent,
81  const QStyleOptionViewItem & option ,
82  const QModelIndex & index ) const
83  {
84  return new MultiComboBoxEditor(parent);
85  }
86 
87  void setEditorData(QWidget *editor, const QModelIndex &index) const
88  {
89  QCheckBox *myEditor = static_cast<QCheckBox*>(editor);
90  myEditor->setText(index.data(Qt::DisplayRole).toString());
91  myEditor->setChecked(index.data(Qt::UserRole).toBool());
92  }
93 
94  void setModelData(QWidget *editor, QAbstractItemModel *model,
95  const QModelIndex &index) const
96  {
97  //get the value from the editor (CheckBox)
98  QCheckBox *myEditor = static_cast<QCheckBox*>(editor);
99  bool value = myEditor->isChecked();
100 
101 
102  //set model data
103  QMap<int,QVariant> data;
104  data.insert(Qt::DisplayRole,myEditor->text());
105  data.insert(Qt::UserRole,value);
106  model->setItemData(index,data);
107  }
108 
109  void updateEditorGeometry(QWidget *editor,
110  const QStyleOptionViewItem &option, const QModelIndex &index ) const
111  {
112  editor->setGeometry(option.rect);
113  }
114 };
115 
116 
117 //min-width:10em;
118 MultiComboBox::MultiComboBox(QWidget *widget )
119 : QComboBox(widget)
120 {
121  view()->setItemDelegate(new MultiComboBoxDelegate(this));
122  // Enable editing on items view
123  view()->setEditTriggers(QAbstractItemView::CurrentChanged);
124  view()->viewport()->installEventFilter(this);
125  view()->setAlternatingRowColors(true);
126 }
127 
128 
129 MultiComboBox::~MultiComboBox()
130 {
131 }
132 
133 QString MultiComboBox::displayText() const
134 {
135  return selectedItemTexts().join(", ");
136 }
137 
138 bool MultiComboBox::eventFilter(QObject *object, QEvent *event)
139 {
140  if (object == view()->viewport())
141  {
142  if (handleViewViewportEvent(event))
143  {
144  return true;
145  }
146  }
147  return QComboBox::eventFilter(object,event);
148 }
149 
150 bool MultiComboBox::handleViewViewportEvent(QEvent* event)
151 {
152  switch (event->type())
153  {
154  default:
155  break;
156 
157  case QEvent::Hide:
158  // TODO: Have this react only if value actually changed.
159  emit valueChanged();
160  break;
161 
162  case QEvent::Show:
163  if (count() >= 1)
164  {
165  // Without this, the first object on the list will be
166  // impossible to edit unless other object is hovered
167  // over first, or in case if there's only one object
168  // on the list, the list won't be editable at all.
169  view()->edit(model()->index(0, 0));
170  }
171  break;
172 
173  case QEvent::MouseButtonRelease:
174  // Don't close items view after we release the mouse button
175  // by simple eating MouseButtonRelease in viewport of items
176  // view.
177  return true;
178  }
179  return false;
180 }
181 
182 void MultiComboBox::paintEvent(QPaintEvent *)
183 {
184  QStylePainter painter(this);
185  painter.setPen(palette().color(QPalette::Text));
186 
187  // draw the combobox frame, focusrect and selected etc.
188  QStyleOptionComboBox opt;
189  initStyleOption(&opt);
190 
191  // draw the icon and text
192  opt.currentText = displayText();
193  painter.drawComplexControl(QStyle::CC_ComboBox, opt);
194  painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
195 }
196 
197 QStringList MultiComboBox::selectedItemTexts() const
198 {
199  QStringList result;
200  for (int i = 0; i < count(); ++i)
201  {
202  bool checked = itemData(i).toBool();
203  if (checked)
204  {
205  result << itemText(i);
206  }
207  }
208  return result;
209 }
210 
211 void MultiComboBox::setSelectedTexts(const QStringList& texts)
212 {
213  for (int i = 0; i < count(); ++i)
214  {
215  setItemData(i, static_cast<bool>(texts.contains(itemText(i))));
216  }
217  // Prompt widget repaint or the display text may not change.
218  update();
219 }