QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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
20#include "qgsapplication.h"
21
22#include <QAbstractItemView>
23#include <QEvent>
24#include <QLineEdit>
25#include <QMouseEvent>
26#include <QPoint>
27#include <QString>
28
29#include "moc_qgscheckablecombobox.cpp"
30
31using namespace Qt::StringLiterals;
32
34 : QStandardItemModel( 0, 1, parent )
35{
36}
37
38Qt::ItemFlags QgsCheckableItemModel::flags( const QModelIndex &index ) const
39{
40 return QStandardItemModel::flags( index ) | Qt::ItemIsUserCheckable;
41}
42
43QVariant QgsCheckableItemModel::data( const QModelIndex &index, int role ) const
44{
45 QVariant value = QStandardItemModel::data( index, role );
46
47 if ( index.isValid() && role == Qt::CheckStateRole && !value.isValid() )
48 {
49 value = Qt::Unchecked;
50 }
51
52 return value;
53}
54
55bool QgsCheckableItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
56{
57 const bool ok = QStandardItemModel::setData( index, value, role );
58
59 if ( ok && role == Qt::CheckStateRole )
60 {
61 emit itemCheckStateChanged( index );
62 }
63
64 emit dataChanged( index, index );
65 return ok;
66}
67
68
70 : QStyledItemDelegate( parent )
71{
72}
73
74void QgsCheckBoxDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
75{
76 QStyleOptionViewItem &nonConstOpt = const_cast<QStyleOptionViewItem &>( option );
77 nonConstOpt.showDecorationSelected = false;
78 QStyledItemDelegate::paint( painter, nonConstOpt, index );
79}
80
81
83 : QComboBox( parent )
84 , mModel( new QgsCheckableItemModel( this ) )
85 , mSeparator( u", "_s )
86{
87 setModel( mModel );
88 setItemDelegate( new QgsCheckBoxDelegate( this ) );
89
90 QLineEdit *lineEdit = new QLineEdit( this );
91 lineEdit->setReadOnly( true );
92 QPalette pal = qApp->palette();
93 pal.setBrush( QPalette::Base, pal.button() );
94 lineEdit->setPalette( pal );
95 setLineEdit( lineEdit );
96 lineEdit->installEventFilter( this );
97 lineEdit->setContextMenuPolicy( Qt::CustomContextMenu );
98 connect( lineEdit, &QAbstractItemView::customContextMenuRequested, this, &QgsCheckableComboBox::showContextMenu );
99
100 mContextMenu = new QMenu( this );
101 mSelectAllAction = mContextMenu->addAction( tr( "Select All" ) );
102 mDeselectAllAction = mContextMenu->addAction( tr( "Deselect All" ) );
103 connect( mSelectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::selectAllOptions );
104 connect( mDeselectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::deselectAllOptions );
105
106 view()->viewport()->installEventFilter( this );
107 view()->setContextMenuPolicy( Qt::CustomContextMenu );
108 connect( view(), &QAbstractItemView::customContextMenuRequested, this, &QgsCheckableComboBox::showContextMenu );
109
110 connect( model(), &QStandardItemModel::rowsInserted, this, [this]( const QModelIndex &, int, int ) { updateDisplayText(); } );
111 connect( model(), &QStandardItemModel::rowsRemoved, this, [this]( const QModelIndex &, int, int ) { updateDisplayText(); } );
112 connect( model(), &QStandardItemModel::dataChanged, this, [this]( const QModelIndex &, const QModelIndex &, const QVector<int> & ) { updateDisplayText(); } );
113}
114
116{
117 return mSeparator;
118}
119
121{
122 if ( mSeparator != separator )
123 {
124 mSeparator = separator;
125 updateDisplayText();
126 }
127}
128
130{
131 return mDefaultText;
132}
133
134void QgsCheckableComboBox::setDefaultText( const QString &text )
135{
136 if ( mDefaultText != text )
137 {
138 mDefaultText = text;
139 updateDisplayText();
140 }
141}
142
143void QgsCheckableComboBox::addItemWithCheckState( const QString &text, Qt::CheckState state, const QVariant &userData )
144{
145 QComboBox::addItem( text, userData );
146 setItemCheckState( count() - 1, state );
147}
148
150{
151 QStringList items;
152
153 if ( auto *lModel = model() )
154 {
155 const QModelIndex index = lModel->index( 0, modelColumn(), rootModelIndex() );
156 const QModelIndexList indexes = lModel->match( index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly );
157 const auto constIndexes = indexes;
158 for ( const QModelIndex &index : constIndexes )
159 {
160 items += index.data().toString();
161 }
162 }
163
164 return items;
165}
166
168{
169 QVariantList data;
170
171 if ( auto *lModel = model() )
172 {
173 const QModelIndex index = lModel->index( 0, modelColumn(), rootModelIndex() );
174 const QModelIndexList indexes = lModel->match( index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly );
175 const auto constIndexes = indexes;
176 for ( const QModelIndex &index : constIndexes )
177 {
178 data += index.data( Qt::UserRole ).toString();
179 }
180 }
181
182 return data;
183}
184
185Qt::CheckState QgsCheckableComboBox::itemCheckState( int index ) const
186{
187 return static_cast<Qt::CheckState>( itemData( index, Qt::CheckStateRole ).toInt() );
188}
189
190void QgsCheckableComboBox::setItemCheckState( int index, Qt::CheckState state )
191{
192 setItemData( index, state, Qt::CheckStateRole );
193}
194
196{
197 const QVariant value = itemData( index, Qt::CheckStateRole );
198 if ( value.isValid() )
199 {
200 const Qt::CheckState state = static_cast<Qt::CheckState>( value.toInt() );
201 setItemData( index, ( state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked ), Qt::CheckStateRole );
202 }
203 updateCheckedItems();
204}
205
207{
208 if ( !mSkipHide )
209 {
210 QComboBox::hidePopup();
211 }
212 mSkipHide = false;
213}
214
216{
217 Q_UNUSED( pos )
218
219 mContextMenu->exec( QCursor::pos() );
220}
221
223{
224 blockSignals( true );
225 for ( int i = 0; i < count(); i++ )
226 {
227 setItemData( i, Qt::Checked, Qt::CheckStateRole );
228 }
229 blockSignals( false );
230 updateCheckedItems();
231}
232
234{
235 blockSignals( true );
236 for ( int i = 0; i < count(); i++ )
237 {
238 setItemData( i, Qt::Unchecked, Qt::CheckStateRole );
239 }
240 blockSignals( false );
241 updateCheckedItems();
242}
243
244bool QgsCheckableComboBox::eventFilter( QObject *object, QEvent *event )
245{
246 if ( object == lineEdit() )
247 {
248 if ( event->type() == QEvent::MouseButtonPress && static_cast<QMouseEvent *>( event )->button() == Qt::LeftButton && object == lineEdit() )
249 {
250 mSkipHide = true;
251 showPopup();
252 }
253 }
254 else if ( ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
255 && object == view()->viewport() )
256 {
257 mSkipHide = true;
258
259 if ( event->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent *>( event )->button() == Qt::RightButton )
260 {
261 return true;
262 }
263
264 if ( event->type() == QEvent::MouseButtonRelease )
265 {
266 const QModelIndex index = view()->indexAt( static_cast<QMouseEvent *>( event )->pos() );
267 if ( index.isValid() )
268 {
269 QgsCheckableItemModel *myModel = qobject_cast<QgsCheckableItemModel *>( model() );
270 QStandardItem *item = myModel->itemFromIndex( index );
271 item->setCheckState( item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked );
272 updateCheckedItems();
273 }
274 return true;
275 }
276 }
277
278 return QComboBox::eventFilter( object, event );
279}
280
281void QgsCheckableComboBox::setCheckedItems( const QStringList &items )
282{
283 const auto constItems = items;
284 for ( const QString &text : constItems )
285 {
286 const int index = findText( text );
287 setItemCheckState( index, index != -1 ? Qt::Checked : Qt::Unchecked );
288 }
289 updateCheckedItems();
290}
291
292void QgsCheckableComboBox::resizeEvent( QResizeEvent *event )
293{
294 QComboBox::resizeEvent( event );
295 updateDisplayText();
296}
297
298void QgsCheckableComboBox::updateCheckedItems()
299{
300 const QStringList items = checkedItems();
301 updateDisplayText();
302 emit checkedItemsChanged( items );
303}
304
305void QgsCheckableComboBox::updateDisplayText()
306{
307 // There is only a line edit if the combobox is in editable state
308 if ( !lineEdit() )
309 return;
310
311 QString text;
312 const QStringList items = checkedItems();
313 if ( items.isEmpty() )
314 {
315 text = mDefaultText;
316 }
317 else
318 {
319 text = items.join( mSeparator );
320 }
321
322 const QRect rect = lineEdit()->rect();
323 const QFontMetrics fontMetrics( font() );
324 text = fontMetrics.elidedText( text, Qt::ElideRight, rect.width() );
325 setEditText( text );
326}
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.