ctkCollapsibleButton.cpp 14 KB

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