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