We now have a current svn snapshot of libqxt in our contrib dir, and
[quassel.git] / src / contrib / libqxt-2007-10-24 / src / gui / qxtcheckcombobox.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) Qxt Foundation. Some rights reserved.
4 **
5 ** This file is part of the QxtGui module of the Qt eXTension library
6 **
7 ** This library is free software; you can redistribute it and/or modify it
8 ** under the terms of th Common Public License, version 1.0, as published by
9 ** IBM.
10 **
11 ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
12 ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
13 ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
14 ** FITNESS FOR A PARTICULAR PURPOSE.
15 **
16 ** You should have received a copy of the CPL along with this file.
17 ** See the LICENSE file and the cpl1.0.txt file included with the source
18 ** distribution for more information. If you did not receive a copy of the
19 ** license, contact the Qxt Foundation.
20 **
21 ** <http://libqxt.sourceforge.net>  <foundation@libqxt.org>
22 **
23 ****************************************************************************/
24 #include "qxtcheckcombobox.h"
25 #include "qxtcheckcombobox_p.h"
26 #include <QStyleOptionButton>
27 #include <QMouseEvent>
28 #include <QLineEdit>
29 #include <QTimer>
30
31 QxtCheckComboBoxPrivate::QxtCheckComboBoxPrivate()
32 {
33     separator = QLatin1String(",");
34 }
35
36 void QxtCheckComboBoxPrivate::hidePopup()
37 {
38     qxt_p().hidePopup();
39 }
40
41 void QxtCheckComboBoxPrivate::updateCheckedItems()
42 {
43     checkedItems.clear();
44     for (int i = 0; i < qxt_p().model()->rowCount(); ++i)
45     {
46         const QModelIndex& index = qxt_p().model()->index(i, 0);
47         const QVariant& data = index.data(Qt::CheckStateRole);
48         const Qt::CheckState state = static_cast<Qt::CheckState>(data.toInt());
49         if (state == Qt::Checked)
50         {
51             checkedItems += index.data().toString();
52         }
53     }
54
55     if (checkedItems.count() > 0)
56         qxt_p().lineEdit()->setText(checkedItems.join(separator));
57     else
58         qxt_p().lineEdit()->setText(defaultText);
59
60     // TODO: find a way to recalculate a meaningful size hint
61
62     emit qxt_p().checkedItemsChanged(checkedItems);
63 }
64
65 QxtCheckComboView::QxtCheckComboView(QWidget* parent)
66         : QListView(parent), mode(QxtCheckComboBox::CheckIndicator)
67 {}
68
69 QxtCheckComboView::~QxtCheckComboView()
70 {}
71
72 bool QxtCheckComboView::eventFilter(QObject* object, QEvent* event)
73 {
74     Q_UNUSED(object);
75     if (event->type() == QEvent::MouseButtonRelease)
76     {
77         QMouseEvent* mouse = static_cast<QMouseEvent*>(event);
78         const QModelIndex& index = indexAt(mouse->pos());
79         if (index.isValid())
80         {
81             bool change = false;
82             switch (mode)
83             {
84             case QxtCheckComboBox::CheckIndicator:
85                 change = handleIndicatorRelease(mouse, index);
86                 break;
87
88             case QxtCheckComboBox::CheckWholeItem:
89                 change = handleItemRelease(mouse, index);
90                 break;
91
92             default:
93                 qWarning("QxtCheckComboView::eventFilter(): unknown mode");
94                 break;
95             }
96
97             if (change)
98             {
99                 // the check state is about to change, bypass
100                 // combobox and deliver the event just for the listview
101                 QListView::mouseReleaseEvent(mouse);
102             }
103             else
104             {
105                 // otherwise it's ok to close
106                 emit hideRequested();
107             }
108             return true;
109         }
110     }
111     return false;
112 }
113
114 bool QxtCheckComboView::handleIndicatorRelease(QMouseEvent* event, const QModelIndex& index)
115 {
116     // check if the mouse was released over the checkbox
117     QStyleOptionButton option;
118     option.QStyleOption::operator=(viewOptions());
119     option.rect = visualRect(index);
120     const QRect& rect = style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &option);
121     return rect.contains(event->pos());
122 }
123
124 bool QxtCheckComboView::handleItemRelease(QMouseEvent* event, const QModelIndex& index)
125 {
126     // check if the mouse was released outside the checkbox
127     QStyleOptionButton option;
128     option.QStyleOption::operator=(viewOptions());
129     option.rect = visualRect(index);
130     const QRect& rect = style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &option);
131     if (!rect.contains(event->pos()))
132     {
133         Qt::CheckState state = (Qt::CheckState) index.data(Qt::CheckStateRole).toInt();
134         switch (state)
135         {
136         case Qt::Unchecked:
137             state = Qt::Checked;
138             break;
139
140         case Qt::Checked:
141             state = Qt::Unchecked;
142             break;
143
144         default:
145             qWarning("QxtCheckComboView::handleItemRelease(): partially checked item");
146             break;
147         }
148         model()->setData(index, state, Qt::CheckStateRole);
149     }
150     return true;
151 }
152
153 QxtCheckComboModel::QxtCheckComboModel(QObject* parent)
154         : QStandardItemModel(0, 1, parent) // rows,cols
155 {
156 }
157
158 QxtCheckComboModel::~QxtCheckComboModel()
159 {}
160
161 Qt::ItemFlags QxtCheckComboModel::flags(const QModelIndex& index) const
162 {
163     return QStandardItemModel::flags(index) | Qt::ItemIsUserCheckable;
164 }
165
166 QVariant QxtCheckComboModel::data(const QModelIndex& index, int role) const
167 {
168     QVariant value =  QStandardItemModel::data(index, role);
169     if (role == Qt::CheckStateRole && !value.isValid())
170         value = Qt::Unchecked;
171     return value;
172 }
173
174 bool QxtCheckComboModel::setData(const QModelIndex& index, const QVariant& value, int role)
175 {
176     bool ok = QStandardItemModel::setData(index, value, role);
177     if (ok)
178     {
179         if (role == Qt::CheckStateRole)
180         {
181             emit checkStateChanged();
182         }
183     }
184     return ok;
185 }
186
187 /*!
188     \class QxtCheckComboBox QxtCheckComboBox
189     \ingroup QxtGui
190     \brief An extended QComboBox with checkable items.
191
192     QxtComboBox is a specialized combo box with checkable items.
193     Checked items are collected together in the line edit.
194
195     \image html qxtcheckcombobox.png "QxtCheckComboBox in Plastique style."
196  */
197
198 /*!
199     \enum QxtCheckComboBox::CheckMode
200
201     This enum describes the check mode.
202
203     \sa QxtCheckComboBox::checkMode
204  */
205
206 /*!
207     \var QxtCheckComboBox::CheckMode QxtCheckComboBox::CheckIndicator
208
209     The check state changes only via the check indicator (like in item views).
210  */
211
212 /*!
213     \var QxtCheckComboBox::CheckMode QxtCheckComboBox::CheckWholeItem
214
215     The check state changes via the whole item (like with a combo box).
216  */
217
218 /*!
219     \fn QxtCheckComboBox::checkedItemsChanged(const QStringList& items)
220
221     This signal is emitted whenever the checked items have been changed.
222  */
223
224 /*!
225     Constructs a new QxtCheckComboBox with \a parent.
226  */
227 QxtCheckComboBox::QxtCheckComboBox(QWidget* parent) : QComboBox(parent)
228 {
229     QXT_INIT_PRIVATE(QxtCheckComboBox);
230     QxtCheckComboModel* model = new QxtCheckComboModel(this);
231     QxtCheckComboView*  view  = new QxtCheckComboView(this);
232     qxt_d().view = view;
233     setModel(model);
234     setView(view);
235
236     // these 2 lines below are important and must be
237     // applied AFTER QComboBox::setView() because
238     // QComboBox installs its own filter on the view
239     view->installEventFilter(view);                     // <--- !!!
240     view->viewport()->installEventFilter(view); // <--- !!!
241
242     // read-only contents
243     QLineEdit* lineEdit = new QLineEdit(this);
244     lineEdit->setReadOnly(true);
245     setLineEdit(lineEdit);
246
247     connect(view, SIGNAL(hideRequested()), &qxt_d(), SLOT(hidePopup()));
248     connect(model, SIGNAL(checkStateChanged()), &qxt_d(), SLOT(updateCheckedItems()));
249     QTimer::singleShot(0, &qxt_d(), SLOT(updateCheckedItems()));
250 }
251
252 /*!
253     Destructs the combo box.
254  */
255 QxtCheckComboBox::~QxtCheckComboBox()
256 {}
257
258 /*!
259     Returns the check state of the item at \a index.
260  */
261 Qt::CheckState QxtCheckComboBox::itemCheckState(int index) const
262 {
263     return static_cast<Qt::CheckState>(itemData(index, Qt::CheckStateRole).toInt());
264 }
265
266 /*!
267     Sets the check state of the item at \a index to \a state.
268  */
269 void QxtCheckComboBox::setItemCheckState(int index, Qt::CheckState state)
270 {
271     setItemData(index, state, Qt::CheckStateRole);
272 }
273
274 /*!
275     \property QxtCheckComboBox::checkedItems
276     \brief This property holds the checked items.
277  */
278 QStringList QxtCheckComboBox::checkedItems() const
279 {
280     return qxt_d().checkedItems;
281 }
282
283 void QxtCheckComboBox::setCheckedItems(const QStringList& items)
284 {
285     // not the most efficient solution but most likely nobody
286     // will put too many items into a combo box anyway so...
287     foreach (const QString& text, items)
288     {
289         const int index = findText(text);
290         setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked);
291     }
292 }
293
294 /*!
295     \property QxtCheckComboBox::defaultText
296     \brief This property holds the default text.
297
298     The default text is shown when there are no checked items.
299     The default value is an empty string.
300  */
301 QString QxtCheckComboBox::defaultText() const
302 {
303     return qxt_d().defaultText;
304 }
305
306 void QxtCheckComboBox::setDefaultText(const QString& text)
307 {
308     if (qxt_d().defaultText != text)
309     {
310         qxt_d().defaultText = text;
311         qxt_d().updateCheckedItems();
312     }
313 }
314
315 /*!
316     \property QxtCheckComboBox::separator
317     \brief This property holds the default separator.
318
319     The checked items are joined together with the separator string.
320     The default value is a comma (",").
321  */
322 QString QxtCheckComboBox::separator() const
323 {
324     return qxt_d().separator;
325 }
326
327 void QxtCheckComboBox::setSeparator(const QString& separator)
328 {
329     if (qxt_d().separator != separator)
330     {
331         qxt_d().separator = separator;
332         qxt_d().updateCheckedItems();
333     }
334 }
335
336 /*!
337     \property QxtCheckComboBox::checkMode
338     \brief This property holds the check mode.
339
340     The check mode describes item checking behaviour.
341     The default value is \b QxtCheckComboBox::CheckIndicator.
342
343     \sa QxtCheckComboBox::CheckMode
344  */
345 QxtCheckComboBox::CheckMode QxtCheckComboBox::checkMode() const
346 {
347     return qxt_d().view->mode;
348 }
349
350 void QxtCheckComboBox::setCheckMode(QxtCheckComboBox::CheckMode mode)
351 {
352     if (qxt_d().view->mode != mode)
353     {
354         qxt_d().view->mode = mode;
355     }
356 }
357
358 void QxtCheckComboBox::keyPressEvent(QKeyEvent* event)
359 {
360     if (event->key() != Qt::Key_Up && event->key() != Qt::Key_Down)
361     {
362         QComboBox::keyPressEvent(event);
363     }
364     else
365     {
366         showPopup();
367     }
368 }
369
370 void QxtCheckComboBox::keyReleaseEvent(QKeyEvent* event)
371 {
372     if (event->key() != Qt::Key_Up && event->key() != Qt::Key_Down)
373     {
374         QComboBox::keyReleaseEvent(event);
375     }
376     else
377     {
378         showPopup();
379     }
380 }