ctkCollapsibleButton.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. All rights reserved.
  5. Distributed under a BSD License. See LICENSE.txt file.
  6. This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even
  7. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8. See the above copyright notice for more information.
  9. =========================================================================*/
  10. // Qt includes
  11. #include <QApplication>
  12. #include <QCleanlooksStyle>
  13. #include <QDebug>
  14. #include <QLayout>
  15. #include <QMouseEvent>
  16. #include <QPainter>
  17. #include <QPushButton>
  18. #include <QStyle>
  19. #include <QStyleOptionButton>
  20. #include <QStyleOptionFrameV3>
  21. // CTK includes
  22. #include "ctkCollapsibleButton.h"
  23. //-----------------------------------------------------------------------------
  24. class ctkCollapsibleButtonPrivate : public ctkPrivate<ctkCollapsibleButton>
  25. {
  26. public:
  27. CTK_DECLARE_PUBLIC(ctkCollapsibleButton);
  28. void init();
  29. bool Collapsed;
  30. // Contents frame
  31. QFrame::Shape ContentsFrameShape;
  32. QFrame::Shadow ContentsFrameShadow;
  33. int ContentsLineWidth;
  34. int ContentsMidLineWidth;
  35. int CollapsedHeight;
  36. bool ExclusiveMouseOver;
  37. bool LookOffWhenChecked;
  38. int MaximumHeight; // use carefully
  39. };
  40. //-----------------------------------------------------------------------------
  41. void ctkCollapsibleButtonPrivate::init()
  42. {
  43. CTK_P(ctkCollapsibleButton);
  44. p->setCheckable(true);
  45. // checked and Collapsed are synchronized: checked != Collapsed
  46. p->setChecked(true);
  47. this->Collapsed = false;
  48. this->ContentsFrameShape = QFrame::NoFrame;
  49. this->ContentsFrameShadow = QFrame::Plain;
  50. this->ContentsLineWidth = 1;
  51. this->ContentsMidLineWidth = 0;
  52. this->CollapsedHeight = 10;
  53. this->ExclusiveMouseOver = false;
  54. this->LookOffWhenChecked = true; // set as a prop ?
  55. this->MaximumHeight = p->maximumHeight();
  56. p->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,
  57. QSizePolicy::Preferred,
  58. QSizePolicy::DefaultType));
  59. p->setContentsMargins(0, p->buttonSizeHint().height(),0 , 0);
  60. QObject::connect(p, SIGNAL(toggled(bool)),
  61. p, SLOT(onToggled(bool)));
  62. }
  63. //-----------------------------------------------------------------------------
  64. void ctkCollapsibleButton::initStyleOption(QStyleOptionButton* option)const
  65. {
  66. CTK_D(const ctkCollapsibleButton);
  67. if (option == 0)
  68. {
  69. return;
  70. }
  71. option->initFrom(this);
  72. if (this->isDown() )
  73. {
  74. option->state |= QStyle::State_Sunken;
  75. }
  76. if (this->isChecked() && !d->LookOffWhenChecked)
  77. {
  78. option->state |= QStyle::State_On;
  79. }
  80. if (!this->isDown())
  81. {
  82. option->state |= QStyle::State_Raised;
  83. }
  84. option->text = this->text();
  85. option->icon = this->icon();
  86. option->iconSize = QSize(this->style()->pixelMetric(QStyle::PM_IndicatorWidth, option, this),
  87. this->style()->pixelMetric(QStyle::PM_IndicatorHeight, option, this));
  88. int buttonHeight = this->buttonSizeHint().height();//qMax(this->fontMetrics().height(), option->iconSize.height());
  89. option->rect.setHeight(buttonHeight);
  90. }
  91. //-----------------------------------------------------------------------------
  92. ctkCollapsibleButton::ctkCollapsibleButton(QWidget* _parent)
  93. :QAbstractButton(_parent)
  94. {
  95. CTK_INIT_PRIVATE(ctkCollapsibleButton);
  96. ctk_d()->init();
  97. }
  98. //-----------------------------------------------------------------------------
  99. ctkCollapsibleButton::ctkCollapsibleButton(const QString& title, QWidget* _parent)
  100. :QAbstractButton(_parent)
  101. {
  102. CTK_INIT_PRIVATE(ctkCollapsibleButton);
  103. ctk_d()->init();
  104. this->setText(title);
  105. }
  106. //-----------------------------------------------------------------------------
  107. ctkCollapsibleButton::~ctkCollapsibleButton()
  108. {
  109. }
  110. //-----------------------------------------------------------------------------
  111. void ctkCollapsibleButton::setCollapsed(bool c)
  112. {
  113. if (!this->isCheckable())
  114. {
  115. // not sure if one should handle this case...
  116. this->collapse(c);
  117. return;
  118. }
  119. this->setChecked(!c);
  120. }
  121. //-----------------------------------------------------------------------------
  122. bool ctkCollapsibleButton::collapsed()const
  123. {
  124. return ctk_d()->Collapsed;
  125. }
  126. //-----------------------------------------------------------------------------
  127. void ctkCollapsibleButton::setCollapsedHeight(int h)
  128. {
  129. ctk_d()->CollapsedHeight = h;
  130. this->updateGeometry();
  131. }
  132. //-----------------------------------------------------------------------------
  133. int ctkCollapsibleButton::collapsedHeight()const
  134. {
  135. return ctk_d()->CollapsedHeight;
  136. }
  137. //-----------------------------------------------------------------------------
  138. void ctkCollapsibleButton::onToggled(bool checked)
  139. {
  140. if (this->isCheckable())
  141. {
  142. this->collapse(!checked);
  143. }
  144. }
  145. //-----------------------------------------------------------------------------
  146. void ctkCollapsibleButton::collapse(bool c)
  147. {
  148. CTK_D(ctkCollapsibleButton);
  149. if (c == d->Collapsed)
  150. {
  151. return;
  152. }
  153. d->Collapsed = c;
  154. // we do that here as setVisible calls will correctly refresh the widget
  155. if (c)
  156. {
  157. d->MaximumHeight = this->maximumHeight();
  158. this->setMaximumHeight(this->sizeHint().height());
  159. //this->updateGeometry();
  160. }
  161. else
  162. {
  163. // restore maximumheight
  164. this->setMaximumHeight(d->MaximumHeight);
  165. this->updateGeometry();
  166. }
  167. QObjectList childList = this->children();
  168. for (int i = 0; i < childList.size(); ++i)
  169. {
  170. QObject *o = childList.at(i);
  171. if (!o->isWidgetType())
  172. {
  173. continue;
  174. }
  175. QWidget *w = static_cast<QWidget *>(o);
  176. w->setVisible(!c);
  177. }
  178. // this might be too many updates...
  179. QWidget* _parent = this->parentWidget();
  180. if (!d->Collapsed && (!_parent || !_parent->layout()))
  181. {
  182. this->resize(this->sizeHint());
  183. }
  184. else
  185. {
  186. this->updateGeometry();
  187. }
  188. //this->update(QRect(QPoint(0,0), this->sizeHint()));
  189. //this->repaint(QRect(QPoint(0,0), this->sizeHint()));
  190. emit contentsCollapsed(c);
  191. }
  192. //-----------------------------------------------------------------------------
  193. QFrame::Shape ctkCollapsibleButton::contentsFrameShape() const
  194. {
  195. return ctk_d()->ContentsFrameShape;
  196. }
  197. //-----------------------------------------------------------------------------
  198. void ctkCollapsibleButton::setContentsFrameShape(QFrame::Shape s)
  199. {
  200. ctk_d()->ContentsFrameShape = s;
  201. }
  202. //-----------------------------------------------------------------------------
  203. QFrame::Shadow ctkCollapsibleButton::contentsFrameShadow() const
  204. {
  205. return ctk_d()->ContentsFrameShadow;
  206. }
  207. //-----------------------------------------------------------------------------
  208. void ctkCollapsibleButton::setContentsFrameShadow(QFrame::Shadow s)
  209. {
  210. ctk_d()->ContentsFrameShadow = s;
  211. }
  212. //-----------------------------------------------------------------------------
  213. int ctkCollapsibleButton:: contentsLineWidth() const
  214. {
  215. return ctk_d()->ContentsLineWidth;
  216. }
  217. //-----------------------------------------------------------------------------
  218. void ctkCollapsibleButton::setContentsLineWidth(int w)
  219. {
  220. ctk_d()->ContentsLineWidth = w;
  221. }
  222. //-----------------------------------------------------------------------------
  223. int ctkCollapsibleButton::contentsMidLineWidth() const
  224. {
  225. return ctk_d()->ContentsMidLineWidth;
  226. }
  227. //-----------------------------------------------------------------------------
  228. void ctkCollapsibleButton::setContentsMidLineWidth(int w)
  229. {
  230. ctk_d()->ContentsMidLineWidth = w;
  231. }
  232. //-----------------------------------------------------------------------------
  233. QSize ctkCollapsibleButton::buttonSizeHint()const
  234. {
  235. int w = 0, h = 0;
  236. QStyleOptionButton opt;
  237. opt.initFrom(this);
  238. // indicator
  239. QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),
  240. style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));
  241. int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);
  242. int ih = indicatorSize.height();
  243. int iw = indicatorSize.width() + indicatorSpacing;
  244. w += iw;
  245. h = qMax(h, ih);
  246. // text
  247. QString string(this->text());
  248. bool empty = string.isEmpty();
  249. if (empty)
  250. {
  251. string = QString::fromLatin1("XXXX");
  252. }
  253. QFontMetrics fm = this->fontMetrics();
  254. QSize sz = fm.size(Qt::TextShowMnemonic, string);
  255. if(!empty || !w)
  256. {
  257. w += sz.width();
  258. }
  259. h = qMax(h, sz.height());
  260. //opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
  261. QSize buttonSize = (style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this).
  262. expandedTo(QApplication::globalStrut()));
  263. return buttonSize;
  264. }
  265. //-----------------------------------------------------------------------------
  266. QSize ctkCollapsibleButton::minimumSizeHint()const
  267. {
  268. CTK_D(const ctkCollapsibleButton);
  269. QSize buttonSize = this->buttonSizeHint();
  270. if (d->Collapsed)
  271. {
  272. return buttonSize + QSize(0,d->CollapsedHeight);
  273. }
  274. // open
  275. if (this->layout() == 0)
  276. {// no layout, means the button is empty ?
  277. return buttonSize;
  278. }
  279. QSize s = this->QAbstractButton::minimumSizeHint();
  280. return s.expandedTo(buttonSize);
  281. }
  282. //-----------------------------------------------------------------------------
  283. QSize ctkCollapsibleButton::sizeHint()const
  284. {
  285. CTK_D(const ctkCollapsibleButton);
  286. QSize buttonSize = this->buttonSizeHint();
  287. if (d->Collapsed)
  288. {
  289. return buttonSize + QSize(0,d->CollapsedHeight);
  290. }
  291. // open
  292. // QAbstractButton works well only if a layout is set
  293. QSize s = this->QAbstractButton::sizeHint();
  294. return s.expandedTo(buttonSize + QSize(0, d->CollapsedHeight));
  295. }
  296. //-----------------------------------------------------------------------------
  297. void ctkCollapsibleButton::paintEvent(QPaintEvent * _event)
  298. {
  299. CTK_D(ctkCollapsibleButton);
  300. QPainter p(this);
  301. // Draw Button
  302. QStyleOptionButton opt;
  303. this->initStyleOption(&opt);
  304. // We don't want to have the highlight effect on the button when mouse is
  305. // over a child. We want the highlight effect only when the mouse is just
  306. // over itself.
  307. // same as this->underMouse()
  308. bool exclusiveMouseOver = false;
  309. if (opt.state & QStyle::State_MouseOver)
  310. {
  311. QRect buttonRect = opt.rect;
  312. QList<QWidget*> _children = this->findChildren<QWidget*>();
  313. QList<QWidget*>::ConstIterator it;
  314. for (it = _children.constBegin(); it != _children.constEnd(); ++it )
  315. {
  316. if ((*it)->underMouse())
  317. {
  318. // the mouse has been moved from the collapsible button to one
  319. // of its children. The paint event rect is the child rect, this
  320. // is why we have to request another paint event to redraw the
  321. // button to remove the highlight effect.
  322. if (!_event->rect().contains(buttonRect))
  323. {// repaint the button rect.
  324. this->update(buttonRect);
  325. }
  326. opt.state &= ~QStyle::State_MouseOver;
  327. exclusiveMouseOver = true;
  328. break;
  329. }
  330. }
  331. if (d->ExclusiveMouseOver && !exclusiveMouseOver)
  332. {
  333. // the mouse is over the widget, but not over the children. As it
  334. // has been de-highlighted in the past, we should refresh the button
  335. // rect to re-highlight the button.
  336. if (!_event->rect().contains(buttonRect))
  337. {// repaint the button rect.
  338. this->update(buttonRect);
  339. }
  340. }
  341. }
  342. d->ExclusiveMouseOver = exclusiveMouseOver;
  343. QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),
  344. style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));
  345. opt.iconSize = indicatorSize;
  346. style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this);
  347. // is PE_PanelButtonCommand better ?
  348. //style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);
  349. int buttonHeight = opt.rect.height();
  350. // Draw Indicator
  351. QStyleOption indicatorOpt;
  352. indicatorOpt.init(this);
  353. indicatorOpt.rect = QRect((buttonHeight - indicatorSize.width()) / 2,
  354. (buttonHeight - indicatorSize.height()) / 2,
  355. indicatorSize.width(), indicatorSize.height());
  356. if (d->Collapsed)
  357. {
  358. style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &indicatorOpt, &p, this);
  359. }
  360. else
  361. {
  362. style()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &indicatorOpt, &p, this);
  363. }
  364. // Draw Text
  365. int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);
  366. opt.rect.setLeft( indicatorOpt.rect.right() + indicatorSpacing);
  367. uint tf = Qt::AlignVCenter | Qt::AlignLeft;
  368. if (this->style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this))
  369. {
  370. tf |= Qt::TextShowMnemonic;
  371. }
  372. else
  373. {
  374. tf |= Qt::TextHideMnemonic;
  375. }
  376. style()->drawItemText(&p, opt.rect, tf, opt.palette, (opt.state & QStyle::State_Enabled),
  377. opt.text, QPalette::ButtonText);
  378. // Draw Frame around contents
  379. QStyleOptionFrameV3 f;
  380. f.init(this);
  381. // HACK: on some styles, the frame doesn't exactly touch the button.
  382. // this is because the button has some kind of extra border.
  383. if (qobject_cast<QCleanlooksStyle*>(this->style()) != 0)
  384. {
  385. f.rect.setTop(buttonHeight - 1);
  386. }
  387. else
  388. {
  389. f.rect.setTop(buttonHeight);
  390. }
  391. f.frameShape = d->ContentsFrameShape;
  392. switch (d->ContentsFrameShadow)
  393. {
  394. case QFrame::Sunken:
  395. f.state |= QStyle::State_Sunken;
  396. break;
  397. case QFrame::Raised:
  398. f.state |= QStyle::State_Raised;
  399. break;
  400. default:
  401. case QFrame::Plain:
  402. break;
  403. }
  404. f.lineWidth = d->ContentsLineWidth;
  405. f.midLineWidth = d->ContentsMidLineWidth;
  406. style()->drawControl(QStyle::CE_ShapedFrame, &f, &p, this);
  407. }
  408. //-----------------------------------------------------------------------------
  409. bool ctkCollapsibleButton::hitButton(const QPoint & _pos)const
  410. {
  411. QStyleOptionButton opt;
  412. this->initStyleOption(&opt);
  413. return opt.rect.contains(_pos);
  414. }
  415. //-----------------------------------------------------------------------------
  416. void ctkCollapsibleButton::childEvent(QChildEvent* c)
  417. {
  418. if(c && c->type() == QEvent::ChildAdded)
  419. {
  420. if (c->child() && c->child()->isWidgetType())
  421. {
  422. QWidget *w = static_cast<QWidget*>(c->child());
  423. w->setVisible(!ctk_d()->Collapsed);
  424. }
  425. }
  426. QWidget::childEvent(c);
  427. }