QGIS API Documentation  3.25.0-Master (dec16ba68b)
qgscheckablecombobox.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscheckablecombobox.cpp
3  ------------------------
4  begin : March 21, 2017
5  copyright : (C) 2017 by Alexander Bruy
6  email : alexander dot bruy at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscheckablecombobox.h"
19 #include "qgsapplication.h"
20 
21 #include <QEvent>
22 #include <QMouseEvent>
23 #include <QLineEdit>
24 #include <QPoint>
25 #include <QAbstractItemView>
26 
27 
29  : QStandardItemModel( 0, 1, parent )
30 {
31 }
32 
33 Qt::ItemFlags QgsCheckableItemModel::flags( const QModelIndex &index ) const
34 {
35  return QStandardItemModel::flags( index ) | Qt::ItemIsUserCheckable;
36 }
37 
38 QVariant QgsCheckableItemModel::data( const QModelIndex &index, int role ) const
39 {
40  QVariant value = QStandardItemModel::data( index, role );
41 
42  if ( index.isValid() && role == Qt::CheckStateRole && !value.isValid() )
43  {
44  value = Qt::Unchecked;
45  }
46 
47  return value;
48 }
49 
50 bool QgsCheckableItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
51 {
52  const bool ok = QStandardItemModel::setData( index, value, role );
53 
54  if ( ok && role == Qt::CheckStateRole )
55  {
56  emit itemCheckStateChanged( index );
57  }
58 
59  emit dataChanged( index, index );
60  return ok;
61 }
62 
63 
65  : QStyledItemDelegate( parent )
66 {
67 }
68 
69 void QgsCheckBoxDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
70 {
71  QStyleOptionViewItem &nonConstOpt = const_cast<QStyleOptionViewItem &>( option );
72  nonConstOpt.showDecorationSelected = false;
73  QStyledItemDelegate::paint( painter, nonConstOpt, index );
74 }
75 
76 
78  : QComboBox( parent )
79  , mModel( new QgsCheckableItemModel( this ) )
80  , mSeparator( QStringLiteral( ", " ) )
81 {
82  setModel( mModel );
83  setItemDelegate( new QgsCheckBoxDelegate( this ) );
84 
85  QLineEdit *lineEdit = new QLineEdit( this );
86  lineEdit->setReadOnly( true );
87  QPalette pal = qApp->palette();
88  pal.setBrush( QPalette::Base, pal.button() );
89  lineEdit->setPalette( pal );
90  setLineEdit( lineEdit );
91  lineEdit->installEventFilter( this );
92  lineEdit->setContextMenuPolicy( Qt::CustomContextMenu );
93  connect( lineEdit, &QAbstractItemView::customContextMenuRequested, this, &QgsCheckableComboBox::showContextMenu );
94 
95  mContextMenu = new QMenu( this );
96  mSelectAllAction = mContextMenu->addAction( tr( "Select All" ) );
97  mDeselectAllAction = mContextMenu->addAction( tr( "Deselect All" ) );
98  connect( mSelectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::selectAllOptions );
99  connect( mDeselectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::deselectAllOptions );
100 
101  view()->viewport()->installEventFilter( this );
102  view()->setContextMenuPolicy( Qt::CustomContextMenu );
103  connect( view(), &QAbstractItemView::customContextMenuRequested, this, &QgsCheckableComboBox::showContextMenu );
104 
105  connect( model(), &QStandardItemModel::rowsInserted, this, [ = ]( const QModelIndex &, int, int ) { updateDisplayText(); } );
106  connect( model(), &QStandardItemModel::rowsRemoved, this, [ = ]( const QModelIndex &, int, int ) { updateDisplayText(); } );
107  connect( model(), &QStandardItemModel::dataChanged, this, [ = ]( const QModelIndex &, const QModelIndex &, const QVector< int > & ) { updateDisplayText(); } );
108 }
109 
111 {
112  return mSeparator;
113 }
114 
115 void QgsCheckableComboBox::setSeparator( const QString &separator )
116 {
117  if ( mSeparator != separator )
118  {
119  mSeparator = separator;
120  updateDisplayText();
121  }
122 }
123 
125 {
126  return mDefaultText;
127 }
128 
129 void QgsCheckableComboBox::setDefaultText( const QString &text )
130 {
131  if ( mDefaultText != text )
132  {
133  mDefaultText = text;
134  updateDisplayText();
135  }
136 }
137 
138 void QgsCheckableComboBox::addItemWithCheckState( const QString &text, Qt::CheckState state, const QVariant &userData )
139 {
140  QComboBox::addItem( text, userData );
141  setItemCheckState( count() - 1, state );
142 }
143 
145 {
146  QStringList items;
147 
148  if ( auto *lModel = model() )
149  {
150  const QModelIndex index = lModel->index( 0, modelColumn(), rootModelIndex() );
151  const QModelIndexList indexes = lModel->match( index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly );
152  const auto constIndexes = indexes;
153  for ( const QModelIndex &index : constIndexes )
154  {
155  items += index.data().toString();
156  }
157  }
158 
159  return items;
160 }
161 
163 {
164  QVariantList data;
165 
166  if ( auto *lModel = model() )
167  {
168  const QModelIndex index = lModel->index( 0, modelColumn(), rootModelIndex() );
169  const QModelIndexList indexes = lModel->match( index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly );
170  const auto constIndexes = indexes;
171  for ( const QModelIndex &index : constIndexes )
172  {
173  data += index.data( Qt::UserRole ).toString();
174  }
175  }
176 
177  return data;
178 }
179 
180 Qt::CheckState QgsCheckableComboBox::itemCheckState( int index ) const
181 {
182  return static_cast<Qt::CheckState>( itemData( index, Qt::CheckStateRole ).toInt() );
183 }
184 
185 void QgsCheckableComboBox::setItemCheckState( int index, Qt::CheckState state )
186 {
187  setItemData( index, state, Qt::CheckStateRole );
188 }
189 
191 {
192  const QVariant value = itemData( index, Qt::CheckStateRole );
193  if ( value.isValid() )
194  {
195  const Qt::CheckState state = static_cast<Qt::CheckState>( value.toInt() );
196  setItemData( index, ( state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked ), Qt::CheckStateRole );
197  }
198  updateCheckedItems();
199 }
200 
202 {
203  if ( !mSkipHide )
204  {
205  QComboBox::hidePopup();
206  }
207  mSkipHide = false;
208 }
209 
211 {
212  Q_UNUSED( pos )
213 
214  mContextMenu->exec( QCursor::pos() );
215 }
216 
218 {
219  blockSignals( true );
220  for ( int i = 0; i < count(); i++ )
221  {
222  setItemData( i, Qt::Checked, Qt::CheckStateRole );
223  }
224  blockSignals( false );
225  updateCheckedItems();
226 }
227 
229 {
230  blockSignals( true );
231  for ( int i = 0; i < count(); i++ )
232  {
233  setItemData( i, Qt::Unchecked, Qt::CheckStateRole );
234  }
235  blockSignals( false );
236  updateCheckedItems();
237 }
238 
239 bool QgsCheckableComboBox::eventFilter( QObject *object, QEvent *event )
240 {
241  if ( object == lineEdit() )
242  {
243  if ( event->type() == QEvent::MouseButtonPress && static_cast<QMouseEvent *>( event )->button() == Qt::LeftButton && object == lineEdit() )
244  {
245  mSkipHide = true;
246  showPopup();
247  }
248  }
249  else if ( ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
250  && object == view()->viewport() )
251  {
252  mSkipHide = true;
253 
254  if ( event->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent *>( event )->button() == Qt::RightButton )
255  {
256  return true;
257  }
258 
259  if ( event->type() == QEvent::MouseButtonRelease )
260  {
261  const QModelIndex index = view()->indexAt( static_cast<QMouseEvent *>( event )->pos() );
262  if ( index.isValid() )
263  {
264  QgsCheckableItemModel *myModel = qobject_cast<QgsCheckableItemModel *>( model() );
265  QStandardItem *item = myModel->itemFromIndex( index );
266  item->setCheckState( item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked );
267  updateCheckedItems();
268  }
269  return true;
270  }
271  }
272 
273  return QComboBox::eventFilter( object, event );
274 }
275 
276 void QgsCheckableComboBox::setCheckedItems( const QStringList &items )
277 {
278  const auto constItems = items;
279  for ( const QString &text : constItems )
280  {
281  const int index = findText( text );
282  setItemCheckState( index, index != -1 ? Qt::Checked : Qt::Unchecked );
283  }
284  updateCheckedItems();
285 }
286 
287 void QgsCheckableComboBox::resizeEvent( QResizeEvent *event )
288 {
289  QComboBox::resizeEvent( event );
290  updateDisplayText();
291 }
292 
293 void QgsCheckableComboBox::updateCheckedItems()
294 {
295  const QStringList items = checkedItems();
296  updateDisplayText();
297  emit checkedItemsChanged( items );
298 }
299 
300 void QgsCheckableComboBox::updateDisplayText()
301 {
302  // There is only a line edit if the combobox is in editable state
303  if ( !lineEdit() )
304  return;
305 
306  QString text;
307  const QStringList items = checkedItems();
308  if ( items.isEmpty() )
309  {
310  text = mDefaultText;
311  }
312  else
313  {
314  text = items.join( mSeparator );
315  }
316 
317  const QRect rect = lineEdit()->rect();
318  const QFontMetrics fontMetrics( font() );
319  text = fontMetrics.elidedText( text, Qt::ElideRight, rect.width() );
320  setEditText( text );
321 }
322 
QStyledItemDelegate subclass for QgsCheckableComboBox.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
Renders the delegate using the given painter and style option for the item specified by index.
QgsCheckBoxDelegate(QObject *parent=nullptr)
Constructor for QgsCheckBoxDelegate.
void setSeparator(const QString &separator)
Set separator used to separate items in the display text.
void setItemCheckState(int index, Qt::CheckState state)
Sets the item check state to state.
void deselectAllOptions()
Removes selection from all items.
void selectAllOptions()
Selects all items.
void checkedItemsChanged(const QStringList &items)
Emitted whenever the checked items list changed.
void setCheckedItems(const QStringList &items)
Set items which should be checked/selected.
void toggleItemCheckState(int index)
Toggles the item check state.
QgsCheckableItemModel * model() const
Returns the custom item model which handles checking the items.
bool eventFilter(QObject *object, QEvent *event) override
Filters events to enable context menu.
QgsCheckableComboBox(QWidget *parent=nullptr)
Constructor for QgsCheckableComboBox.
QVariantList checkedItemsData() const
Returns userData (stored in the Qt::UserRole) associated with currently checked items.
QgsCheckableItemModel * mModel
Qt::CheckState itemCheckState(int index) const
Returns the checked state of the item identified by index.
void addItemWithCheckState(const QString &text, Qt::CheckState state, const QVariant &userData=QVariant())
Adds an item to the combobox with the given text, check state (stored in the Qt::CheckStateRole) and ...
void showContextMenu(QPoint pos)
Display context menu which allows selecting/deselecting all items at once.
void hidePopup() override
Hides the list of items in the combobox if it is currently visible and resets the internal state.
void resizeEvent(QResizeEvent *event) override
Handler for widget resizing.
void setDefaultText(const QString &text)
Set default text which will be displayed in the widget when no items selected.
QStandardItemModel subclass which makes all items checkable by default.
void itemCheckStateChanged(const QModelIndex &index)
Emitted whenever the item's checkstate has changed.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Sets the role data for the item at index to value.
Qt::ItemFlags flags(const QModelIndex &index) const override
Returns a combination of the item flags: items are enabled (ItemIsEnabled), selectable (ItemIsSelecta...
QgsCheckableItemModel(QObject *parent=nullptr)
Constructor for QgsCheckableItemModel.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Returns the data stored under the given role for the item referred to by the index.