QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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
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
33Qt::ItemFlags QgsCheckableItemModel::flags( const QModelIndex &index ) const
34{
35 return QStandardItemModel::flags( index ) | Qt::ItemIsUserCheckable;
36}
37
38QVariant 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
50bool 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
69void 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
115void 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
129void QgsCheckableComboBox::setDefaultText( const QString &text )
130{
131 if ( mDefaultText != text )
132 {
133 mDefaultText = text;
134 updateDisplayText();
135 }
136}
137
138void 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
180Qt::CheckState QgsCheckableComboBox::itemCheckState( int index ) const
181{
182 return static_cast<Qt::CheckState>( itemData( index, Qt::CheckStateRole ).toInt() );
183}
184
185void 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
239bool 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
276void 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
287void QgsCheckableComboBox::resizeEvent( QResizeEvent *event )
288{
289 QComboBox::resizeEvent( event );
290 updateDisplayText();
291}
292
293void QgsCheckableComboBox::updateCheckedItems()
294{
295 const QStringList items = checkedItems();
296 updateDisplayText();
297 emit checkedItemsChanged( items );
298}
299
300void 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.
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
QgsCheckableItemModel * model() const
Returns the custom item model which handles checking the items.
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.