QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( const QModelIndex &index, indexes )
142  {
143  items += index.data().toString();
144  }
145  }
146 
147  return items;
148 }
149 
150 Qt::CheckState QgsCheckableComboBox::itemCheckState( int index ) const
151 {
152  return static_cast<Qt::CheckState>( itemData( index, Qt::CheckStateRole ).toInt() );
153 }
154 
155 void QgsCheckableComboBox::setItemCheckState( int index, Qt::CheckState state )
156 {
157  setItemData( index, state, Qt::CheckStateRole );
158 }
159 
161 {
162  QVariant value = itemData( index, Qt::CheckStateRole );
163  if ( value.isValid() )
164  {
165  Qt::CheckState state = static_cast<Qt::CheckState>( value.toInt() );
166  setItemData( index, ( state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked ), Qt::CheckStateRole );
167  }
168 }
169 
171 {
172  if ( !mSkipHide )
173  {
174  QComboBox::hidePopup();
175  }
176  mSkipHide = false;
177 }
178 
180 {
181  Q_UNUSED( pos );
182 
183  mContextMenu->exec( QCursor::pos() );
184 }
185 
187 {
188  blockSignals( true );
189  for ( int i = 0; i < count(); i++ )
190  {
191  setItemData( i, Qt::Checked, Qt::CheckStateRole );
192  }
193  blockSignals( false );
194  updateCheckedItems();
195 }
196 
198 {
199  blockSignals( true );
200  for ( int i = 0; i < count(); i++ )
201  {
202  setItemData( i, Qt::Unchecked, Qt::CheckStateRole );
203  }
204  blockSignals( false );
205  updateCheckedItems();
206 }
207 
208 bool QgsCheckableComboBox::eventFilter( QObject *object, QEvent *event )
209 {
210  if ( ( event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease )
211  && object == view()->viewport() )
212  {
213  mSkipHide = true;
214  }
215 
216  if ( event->type() == QEvent::MouseButtonRelease )
217  {
218  if ( static_cast<QMouseEvent *>( event )->button() == Qt::RightButton )
219  {
220  return true;
221  }
222  }
223  return QComboBox::eventFilter( object, event );
224 }
225 
226 void QgsCheckableComboBox::setCheckedItems( const QStringList &items )
227 {
228  Q_FOREACH ( const QString &text, items )
229  {
230  const int index = findText( text );
231  setItemCheckState( index, index != -1 ? Qt::Checked : Qt::Unchecked );
232  }
233 }
234 
235 void QgsCheckableComboBox::resizeEvent( QResizeEvent *event )
236 {
237  QComboBox::resizeEvent( event );
238  updateDisplayText();
239 }
240 
241 void QgsCheckableComboBox::updateCheckedItems()
242 {
243  QStringList items = checkedItems();
244  updateDisplayText();
245  emit checkedItemsChanged( items );
246 }
247 
248 void QgsCheckableComboBox::updateDisplayText()
249 {
250  QString text;
251  QStringList items = checkedItems();
252  if ( items.isEmpty() )
253  {
254  text = mDefaultText;
255  }
256  else
257  {
258  text = items.join( mSeparator );
259  }
260 
261  QRect rect = lineEdit()->rect();
262  QFontMetrics fontMetrics( font() );
263  text = fontMetrics.elidedText( text, Qt::ElideRight, rect.width() );
264  setEditText( text );
265 }
266 
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.
Qt::CheckState itemCheckState(int index) const
Returns the checked state of the item identified by index.
QString defaultText() const
Returns default text which will be displayed in the widget when no items selected.
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.
void deselectAllOptions()
Removes selection from all items.
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()
This signal is emitted whenever the items checkstate has changed.
void resizeEvent(QResizeEvent *event) override
Handler for widget resizing.
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)
This signal is emitted whenever the checked items list changed.
QString separator() const
Returns separator used to separate items in the display text.
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.