QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
20 #include <QEvent>
21 #include <QMouseEvent>
22 #include <QLineEdit>
23 #include <QPoint>
24 #include <QAbstractItemView>
25 
26 
28  : QStandardItemModel( 0, 1, parent )
29 {
30 }
31 
32 Qt::ItemFlags QgsCheckableItemModel::flags( const QModelIndex &index ) const
33 {
34  return QStandardItemModel::flags( index ) | Qt::ItemIsUserCheckable;
35 }
36 
37 QVariant QgsCheckableItemModel::data( const QModelIndex &index, int role ) const
38 {
39  QVariant value = QStandardItemModel::data( index, role );
40 
41  if ( index.isValid() && role == Qt::CheckStateRole && !value.isValid() )
42  {
43  value = Qt::Unchecked;
44  }
45 
46  return value;
47 }
48 
49 bool QgsCheckableItemModel::setData( const QModelIndex &index, const QVariant &value, int role )
50 {
51  bool ok = QStandardItemModel::setData( index, value, role );
52 
53  if ( ok && role == Qt::CheckStateRole )
54  {
55  emit dataChanged( index, index );
56  emit itemCheckStateChanged();
57  }
58 
59  return ok;
60 }
61 
62 
64  : QStyledItemDelegate( parent )
65 {
66 }
67 
68 void QgsCheckBoxDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
69 {
70  QStyleOptionViewItem &nonConstOpt = const_cast<QStyleOptionViewItem &>( option );
71  nonConstOpt.showDecorationSelected = false;
72  QStyledItemDelegate::paint( painter, nonConstOpt, index );
73 }
74 
75 
77  : QComboBox( parent )
78  , mSeparator( QStringLiteral( ", " ) )
79 {
80  setModel( new QgsCheckableItemModel( this ) );
81  setItemDelegate( new QgsCheckBoxDelegate( this ) );
82 
83  QLineEdit *lineEdit = new QLineEdit( this );
84  lineEdit->setReadOnly( true );
85  setLineEdit( lineEdit );
86 
87  mContextMenu = new QMenu( this );
88  mSelectAllAction = mContextMenu->addAction( tr( "Select All" ) );
89  mDeselectAllAction = mContextMenu->addAction( tr( "Deselect All" ) );
90  connect( mSelectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::selectAllOptions );
91  connect( mDeselectAllAction, &QAction::triggered, this, &QgsCheckableComboBox::deselectAllOptions );
92 
93  view()->viewport()->installEventFilter( this );
94  view()->setContextMenuPolicy( Qt::CustomContextMenu );
95  connect( view(), &QAbstractItemView::customContextMenuRequested, this, &QgsCheckableComboBox::showContextMenu );
96 
97  QgsCheckableItemModel *myModel = qobject_cast<QgsCheckableItemModel *>( model() );
98  connect( myModel, &QgsCheckableItemModel::itemCheckStateChanged, this, &QgsCheckableComboBox::updateCheckedItems );
99  connect( model(), &QStandardItemModel::rowsInserted, this, [ = ]( const QModelIndex &, int, int ) { updateCheckedItems(); } );
100  connect( model(), &QStandardItemModel::rowsRemoved, this, [ = ]( const QModelIndex &, int, int ) { updateCheckedItems(); } );
101 
102  connect( this, static_cast< void ( QComboBox::* )( int ) >( &QComboBox::activated ), this, &QgsCheckableComboBox::toggleItemCheckState );
103 }
104 
105 QString QgsCheckableComboBox::separator() const
106 {
107  return mSeparator;
108 }
109 
111 {
112  if ( mSeparator != separator )
113  {
114  mSeparator = separator;
115  updateDisplayText();
116  }
117 }
118 
119 QString QgsCheckableComboBox::defaultText() const
120 {
121  return mDefaultText;
122 }
123 
124 void QgsCheckableComboBox::setDefaultText( const QString &text )
125 {
126  if ( mDefaultText != text )
127  {
128  mDefaultText = text;
129  updateDisplayText();
130  }
131 }
132 
133 QStringList QgsCheckableComboBox::checkedItems() const
134 {
135  QStringList items;
136 
137  if ( model() )
138  {
139  QModelIndex index = model()->index( 0, modelColumn(), rootModelIndex() );
140  QModelIndexList indexes = model()->match( index, Qt::CheckStateRole, Qt::Checked, -1, Qt::MatchExactly );
141  const auto constIndexes = indexes;
142  for ( const QModelIndex &index : constIndexes )
143  {
144  items += index.data().toString();
145  }
146  }
147 
148  return items;
149 }
150 
151 Qt::CheckState QgsCheckableComboBox::itemCheckState( int index ) const
152 {
153  return static_cast<Qt::CheckState>( itemData( index, Qt::CheckStateRole ).toInt() );
154 }
155 
156 void QgsCheckableComboBox::setItemCheckState( int index, Qt::CheckState state )
157 {
158  setItemData( index, state, Qt::CheckStateRole );
159 }
160 
162 {
163  QVariant value = itemData( index, Qt::CheckStateRole );
164  if ( value.isValid() )
165  {
166  Qt::CheckState state = static_cast<Qt::CheckState>( value.toInt() );
167  setItemData( index, ( state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked ), Qt::CheckStateRole );
168  }
169 }
170 
172 {
173  if ( !mSkipHide )
174  {
175  QComboBox::hidePopup();
176  }
177  mSkipHide = false;
178 }
179 
181 {
182  Q_UNUSED( pos )
183 
184  mContextMenu->exec( QCursor::pos() );
185 }
186 
188 {
189  blockSignals( true );
190  for ( int i = 0; i < count(); i++ )
191  {
192  setItemData( i, Qt::Checked, Qt::CheckStateRole );
193  }
194  blockSignals( false );
195  updateCheckedItems();
196 }
197 
199 {
200  blockSignals( true );
201  for ( int i = 0; i < count(); i++ )
202  {
203  setItemData( i, Qt::Unchecked, Qt::CheckStateRole );
204  }
205  blockSignals( false );
206  updateCheckedItems();
207 }
208 
209 bool QgsCheckableComboBox::eventFilter( QObject *object, QEvent *event )
210 {
211  if ( ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
212  && object == view()->viewport() )
213  {
214  mSkipHide = true;
215  }
216 
217  if ( event->type() == QEvent::MouseButtonRelease )
218  {
219  if ( static_cast<QMouseEvent *>( event )->button() == Qt::RightButton )
220  {
221  return true;
222  }
223  }
224  return QComboBox::eventFilter( object, event );
225 }
226 
227 void QgsCheckableComboBox::setCheckedItems( const QStringList &items )
228 {
229  const auto constItems = items;
230  for ( const QString &text : constItems )
231  {
232  const int index = findText( text );
233  setItemCheckState( index, index != -1 ? Qt::Checked : Qt::Unchecked );
234  }
235 }
236 
237 void QgsCheckableComboBox::resizeEvent( QResizeEvent *event )
238 {
239  QComboBox::resizeEvent( event );
240  updateDisplayText();
241 }
242 
243 void QgsCheckableComboBox::updateCheckedItems()
244 {
245  QStringList items = checkedItems();
246  updateDisplayText();
247  emit checkedItemsChanged( items );
248 }
249 
250 void QgsCheckableComboBox::updateDisplayText()
251 {
252  QString text;
253  QStringList items = checkedItems();
254  if ( items.isEmpty() )
255  {
256  text = mDefaultText;
257  }
258  else
259  {
260  text = items.join( mSeparator );
261  }
262 
263  QRect rect = lineEdit()->rect();
264  QFontMetrics fontMetrics( font() );
265  text = fontMetrics.elidedText( text, Qt::ElideRight, rect.width() );
266  setEditText( text );
267 }
268 
void setDefaultText(const QString &text)
Set default text which will be displayed in the widget when no items selected.
QgsCheckableItemModel(QObject *parent=nullptr)
Constructor for QgsCheckableItemModel.
void selectAllOptions()
Selects all items.
void setCheckedItems(const QStringList &items)
Set items which should be checked/selected.
QStyledItemDelegate subclass for QgsCheckableComboBox.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Sets the role data for the item at index to value.
void showContextMenu(QPoint pos)
Display context menu which allows selecting/deselecting all items at once.
bool eventFilter(QObject *object, QEvent *event) override
Filters events to enable context menu.
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.
QString separator() const
Returns separator used to separate items in the display text.
void deselectAllOptions()
Removes selection from all items.
QString defaultText() const
Returns default text which will be displayed in the widget when no items selected.
void hidePopup() override
Hides the list of items in the combobox if it is currently visible and resets the internal state...
QgsCheckableComboBox(QWidget *parent=nullptr)
Constructor for QgsCheckableComboBox.
void toggleItemCheckState(int index)
Toggles the item check state.
void setItemCheckState(int index, Qt::CheckState state)
Sets the item check state to state.
void itemCheckStateChanged()
Emitted whenever the items checkstate has changed.
void resizeEvent(QResizeEvent *event) override
Handler for widget resizing.
Qt::CheckState itemCheckState(int index) const
Returns the checked state of the item identified by index.
QStringList checkedItems() const
Returns currently checked items.
Qt::ItemFlags flags(const QModelIndex &index) const override
Returns a combination of the item flags: items are enabled (ItemIsEnabled), selectable (ItemIsSelecta...
void setSeparator(const QString &separator)
Set separator used to separate items in the display text.
void checkedItemsChanged(const QStringList &items)
Emitted whenever the checked items list changed.
QStandardItemModel subclass which makes all items checkable by default.
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.