ctkCheckableComboBox.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QApplication>
  16. #include <QAbstractItemView>
  17. #include <QDebug>
  18. #include <QDesktopWidget>
  19. #include <QItemDelegate>
  20. #include <QLayout>
  21. #include <QMouseEvent>
  22. #include <QMenu>
  23. #include <QPainter>
  24. #include <QPointer>
  25. #include <QPushButton>
  26. #include <QStandardItemModel>
  27. #include <QStyle>
  28. #include <QStyleOptionButton>
  29. #include <QStylePainter>
  30. #include <QToolBar>
  31. // CTK includes
  32. #include "ctkCheckableComboBox.h"
  33. #include <ctkCheckableModelHelper.h>
  34. // Similar to QComboBoxDelegate
  35. class ctkComboBoxDelegate : public QItemDelegate
  36. {
  37. public:
  38. ctkComboBoxDelegate(QObject *parent, QComboBox *cmb)
  39. : QItemDelegate(parent), ComboBox(cmb)
  40. {}
  41. static bool isSeparator(const QModelIndex &index)
  42. {
  43. return index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator");
  44. }
  45. static void setSeparator(QAbstractItemModel *model, const QModelIndex &index)
  46. {
  47. model->setData(index, QString::fromLatin1("separator"), Qt::AccessibleDescriptionRole);
  48. if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(model))
  49. {
  50. if (QStandardItem *item = m->itemFromIndex(index))
  51. {
  52. item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
  53. }
  54. }
  55. }
  56. protected:
  57. void paint(QPainter *painter,
  58. const QStyleOptionViewItem &option,
  59. const QModelIndex &index) const
  60. {
  61. if (isSeparator(index))
  62. {
  63. QRect rect = option.rect;
  64. #if (QT_VERSION < QT_VERSION_CHECK(5,0,0))
  65. if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3*>(&option))
  66. {
  67. if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(v3->widget))
  68. {
  69. rect.setWidth(view->viewport()->width());
  70. }
  71. }
  72. #else
  73. if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(option.widget))
  74. {
  75. rect.setWidth(view->viewport()->width());
  76. }
  77. #endif
  78. QStyleOption opt;
  79. opt.rect = rect;
  80. this->ComboBox->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, this->ComboBox);
  81. }
  82. else
  83. {
  84. QItemDelegate::paint(painter, option, index);
  85. }
  86. }
  87. QSize sizeHint(const QStyleOptionViewItem &option,
  88. const QModelIndex &index) const
  89. {
  90. if (isSeparator(index))
  91. {
  92. int pm = this->ComboBox->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this->ComboBox);
  93. return QSize(pm, pm);
  94. }
  95. return this->QItemDelegate::sizeHint(option, index);
  96. }
  97. private:
  98. QComboBox* ComboBox;
  99. };
  100. //-----------------------------------------------------------------------------
  101. class ctkCheckableComboBoxPrivate
  102. {
  103. Q_DECLARE_PUBLIC(ctkCheckableComboBox);
  104. protected:
  105. ctkCheckableComboBox* const q_ptr;
  106. QModelIndexList checkedIndexes()const;
  107. QModelIndexList uncheckedIndexes()const;
  108. public:
  109. ctkCheckableComboBoxPrivate(ctkCheckableComboBox& object);
  110. void init();
  111. QModelIndexList cachedCheckedIndexes()const;
  112. void updateCheckedList();
  113. ctkCheckableModelHelper* CheckableModelHelper;
  114. bool MouseButtonPressed;
  115. private:
  116. QModelIndexList persistentIndexesToModelIndexes(
  117. const QList<QPersistentModelIndex>& persistentModels)const;
  118. QList<QPersistentModelIndex> modelIndexesToPersistentIndexes(
  119. const QModelIndexList& modelIndexes)const;
  120. mutable QList<QPersistentModelIndex> CheckedList;
  121. };
  122. //-----------------------------------------------------------------------------
  123. ctkCheckableComboBoxPrivate::ctkCheckableComboBoxPrivate(ctkCheckableComboBox& object)
  124. : q_ptr(&object)
  125. {
  126. this->CheckableModelHelper = 0;
  127. this->MouseButtonPressed = false;
  128. }
  129. //-----------------------------------------------------------------------------
  130. void ctkCheckableComboBoxPrivate::init()
  131. {
  132. Q_Q(ctkCheckableComboBox);
  133. this->CheckableModelHelper = new ctkCheckableModelHelper(Qt::Horizontal, q);
  134. this->CheckableModelHelper->setForceCheckability(true);
  135. q->setCheckableModel(q->model());
  136. q->view()->installEventFilter(q);
  137. q->view()->viewport()->installEventFilter(q);
  138. // QCleanLooksStyle uses a delegate that doesn't show the checkboxes in the
  139. // popup list.
  140. q->setItemDelegate(new ctkComboBoxDelegate(q->view(), q));
  141. }
  142. //-----------------------------------------------------------------------------
  143. void ctkCheckableComboBoxPrivate::updateCheckedList()
  144. {
  145. Q_Q(ctkCheckableComboBox);
  146. QList<QPersistentModelIndex> newCheckedPersistentList =
  147. this->modelIndexesToPersistentIndexes(this->checkedIndexes());
  148. if (newCheckedPersistentList == this->CheckedList)
  149. {
  150. return;
  151. }
  152. this->CheckedList = newCheckedPersistentList;
  153. emit q->checkedIndexesChanged();
  154. }
  155. //-----------------------------------------------------------------------------
  156. QList<QPersistentModelIndex> ctkCheckableComboBoxPrivate
  157. ::modelIndexesToPersistentIndexes(const QModelIndexList& indexes)const
  158. {
  159. QList<QPersistentModelIndex> res;
  160. foreach(const QModelIndex& index, indexes)
  161. {
  162. QPersistentModelIndex persistent(index);
  163. if (persistent.isValid())
  164. {
  165. res << persistent;
  166. }
  167. }
  168. return res;
  169. }
  170. //-----------------------------------------------------------------------------
  171. QModelIndexList ctkCheckableComboBoxPrivate
  172. ::persistentIndexesToModelIndexes(
  173. const QList<QPersistentModelIndex>& indexes)const
  174. {
  175. QModelIndexList res;
  176. foreach(const QPersistentModelIndex& index, indexes)
  177. {
  178. if (index.isValid())
  179. {
  180. res << index;
  181. }
  182. }
  183. return res;
  184. }
  185. //-----------------------------------------------------------------------------
  186. QModelIndexList ctkCheckableComboBoxPrivate::cachedCheckedIndexes()const
  187. {
  188. return this->persistentIndexesToModelIndexes(this->CheckedList);
  189. }
  190. //-----------------------------------------------------------------------------
  191. QModelIndexList ctkCheckableComboBoxPrivate::checkedIndexes()const
  192. {
  193. Q_Q(const ctkCheckableComboBox);
  194. QModelIndex startIndex = q->model()->index(0,0, q->rootModelIndex());
  195. return q->model()->match(
  196. startIndex, Qt::CheckStateRole,
  197. static_cast<int>(Qt::Checked), -1, Qt::MatchRecursive);
  198. }
  199. //-----------------------------------------------------------------------------
  200. QModelIndexList ctkCheckableComboBoxPrivate::uncheckedIndexes()const
  201. {
  202. Q_Q(const ctkCheckableComboBox);
  203. QModelIndex startIndex = q->model()->index(0,0, q->rootModelIndex());
  204. return q->model()->match(
  205. startIndex, Qt::CheckStateRole,
  206. static_cast<int>(Qt::Unchecked), -1, Qt::MatchRecursive);
  207. }
  208. //-----------------------------------------------------------------------------
  209. ctkCheckableComboBox::ctkCheckableComboBox(QWidget* parentWidget)
  210. : QComboBox(parentWidget)
  211. , d_ptr(new ctkCheckableComboBoxPrivate(*this))
  212. {
  213. Q_D(ctkCheckableComboBox);
  214. d->init();
  215. }
  216. //-----------------------------------------------------------------------------
  217. ctkCheckableComboBox::~ctkCheckableComboBox()
  218. {
  219. }
  220. //-----------------------------------------------------------------------------
  221. bool ctkCheckableComboBox::eventFilter(QObject *o, QEvent *e)
  222. {
  223. Q_D(ctkCheckableComboBox);
  224. switch (e->type())
  225. {
  226. case QEvent::MouseButtonPress:
  227. {
  228. if (this->view()->isVisible())
  229. {
  230. d->MouseButtonPressed = true;
  231. }
  232. break;
  233. }
  234. case QEvent::MouseButtonRelease:
  235. {
  236. QMouseEvent *m = static_cast<QMouseEvent *>(e);
  237. if (this->view()->isVisible() &&
  238. this->view()->rect().contains(m->pos()) &&
  239. this->view()->currentIndex().isValid()
  240. //&& !blockMouseReleaseTimer.isActive()
  241. && (this->view()->currentIndex().flags() & Qt::ItemIsEnabled)
  242. && (this->view()->currentIndex().flags() & Qt::ItemIsSelectable))
  243. {
  244. // The signal to open the menu is fired when the mouse button is
  245. // pressed, we don't want to toggle the item under the mouse cursor
  246. // when the button used to open the popup is released.
  247. if (d->MouseButtonPressed)
  248. {
  249. // make the item current, it will then call QComboBox::update (and
  250. // repaint) when the current index data is changed (checkstate
  251. // toggled fires dataChanged signal which is observed).
  252. this->setCurrentIndex(this->view()->currentIndex().row());
  253. d->CheckableModelHelper->toggleCheckState(this->view()->currentIndex());
  254. }
  255. d->MouseButtonPressed = false;
  256. return true;
  257. }
  258. d->MouseButtonPressed = false;
  259. break;
  260. }
  261. default:
  262. break;
  263. }
  264. return this->QComboBox::eventFilter(o, e);
  265. }
  266. //-----------------------------------------------------------------------------
  267. void ctkCheckableComboBox::setCheckableModel(QAbstractItemModel* newModel)
  268. {
  269. Q_D(ctkCheckableComboBox);
  270. this->disconnect(this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
  271. this, SLOT(onDataChanged(QModelIndex,QModelIndex)));
  272. if (newModel != this->model())
  273. {
  274. this->setModel(newModel);
  275. }
  276. this->connect(this->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
  277. this, SLOT(onDataChanged(QModelIndex,QModelIndex)));
  278. d->CheckableModelHelper->setModel(newModel);
  279. d->updateCheckedList();
  280. }
  281. //-----------------------------------------------------------------------------
  282. QAbstractItemModel* ctkCheckableComboBox::checkableModel()const
  283. {
  284. return this->model();
  285. }
  286. //-----------------------------------------------------------------------------
  287. QModelIndexList ctkCheckableComboBox::checkedIndexes()const
  288. {
  289. Q_D(const ctkCheckableComboBox);
  290. return d->cachedCheckedIndexes();
  291. }
  292. //-----------------------------------------------------------------------------
  293. bool ctkCheckableComboBox::allChecked()const
  294. {
  295. Q_D(const ctkCheckableComboBox);
  296. return d->uncheckedIndexes().count() == 0;
  297. }
  298. //-----------------------------------------------------------------------------
  299. bool ctkCheckableComboBox::noneChecked()const
  300. {
  301. Q_D(const ctkCheckableComboBox);
  302. return d->cachedCheckedIndexes().count() == 0;
  303. }
  304. //-----------------------------------------------------------------------------
  305. void ctkCheckableComboBox::setCheckState(const QModelIndex& index, Qt::CheckState check)
  306. {
  307. Q_D(ctkCheckableComboBox);
  308. return d->CheckableModelHelper->setCheckState(index, check);
  309. }
  310. //-----------------------------------------------------------------------------
  311. Qt::CheckState ctkCheckableComboBox::checkState(const QModelIndex& index)const
  312. {
  313. Q_D(const ctkCheckableComboBox);
  314. return d->CheckableModelHelper->checkState(index);
  315. }
  316. //-----------------------------------------------------------------------------
  317. ctkCheckableModelHelper* ctkCheckableComboBox::checkableModelHelper()const
  318. {
  319. Q_D(const ctkCheckableComboBox);
  320. return d->CheckableModelHelper;
  321. }
  322. //-----------------------------------------------------------------------------
  323. void ctkCheckableComboBox::onDataChanged(const QModelIndex& start, const QModelIndex& end)
  324. {
  325. Q_D(ctkCheckableComboBox);
  326. Q_UNUSED(start);
  327. Q_UNUSED(end);
  328. d->updateCheckedList();
  329. }
  330. //-----------------------------------------------------------------------------
  331. void ctkCheckableComboBox::paintEvent(QPaintEvent *)
  332. {
  333. Q_D(ctkCheckableComboBox);
  334. QStylePainter painter(this);
  335. painter.setPen(palette().color(QPalette::Text));
  336. // draw the combobox frame, focusrect and selected etc.
  337. QStyleOptionComboBox opt;
  338. this->initStyleOption(&opt);
  339. if (this->allChecked())
  340. {
  341. opt.currentText = "All";
  342. opt.currentIcon = QIcon();
  343. }
  344. else if (this->noneChecked())
  345. {
  346. opt.currentText = "None";
  347. opt.currentIcon = QIcon();
  348. }
  349. else
  350. {
  351. //search the checked items
  352. QModelIndexList indexes = d->cachedCheckedIndexes();
  353. if (indexes.count() == 1)
  354. {
  355. opt.currentText = this->model()->data(indexes[0], Qt::DisplayRole).toString();
  356. opt.currentIcon = qvariant_cast<QIcon>(this->model()->data(indexes[0], Qt::DecorationRole));
  357. }
  358. else
  359. {
  360. QStringList indexesText;
  361. foreach(QModelIndex checkedIndex, indexes)
  362. {
  363. indexesText << this->model()->data(checkedIndex, Qt::DisplayRole).toString();
  364. }
  365. opt.currentText = indexesText.join(", ");
  366. opt.currentIcon = QIcon();
  367. }
  368. }
  369. painter.drawComplexControl(QStyle::CC_ComboBox, opt);
  370. // draw the icon and text
  371. painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
  372. }