| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 | /*=========================================================================  Library:   CTK  Copyright (c) Kitware Inc.   All rights reserved.  Distributed under a BSD License. See LICENSE.txt file.  This software is distributed "AS IS" WITHOUT ANY WARRANTY; without even  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the above copyright notice for more information.=========================================================================*/// Qt includes#include <QApplication>#include <QCleanlooksStyle>#include <QDebug>#include <QLayout>#include <QMouseEvent>#include <QPainter>#include <QPushButton>#include <QStyle>#include <QStyleOptionButton>#include <QStyleOptionFrameV3>// CTK includes#include "ctkCollapsibleButton.h"//-----------------------------------------------------------------------------class ctkCollapsibleButtonPrivate : public ctkPrivate<ctkCollapsibleButton>{public:  CTK_DECLARE_PUBLIC(ctkCollapsibleButton);  void init();  bool     Collapsed;  // Contents frame  QFrame::Shape  ContentsFrameShape;  QFrame::Shadow ContentsFrameShadow;  int            ContentsLineWidth;  int            ContentsMidLineWidth;  int      CollapsedHeight;  bool     ExclusiveMouseOver;  bool     LookOffWhenChecked;  int      MaximumHeight;  // use carefully};//-----------------------------------------------------------------------------void ctkCollapsibleButtonPrivate::init(){  CTK_P(ctkCollapsibleButton);  p->setCheckable(true);  // checked and Collapsed are synchronized: checked != Collapsed  p->setChecked(true);  this->Collapsed = false;  this->ContentsFrameShape = QFrame::NoFrame;  this->ContentsFrameShadow = QFrame::Plain;  this->ContentsLineWidth = 1;  this->ContentsMidLineWidth = 0;  this->CollapsedHeight = 10;  this->ExclusiveMouseOver = false;    this->LookOffWhenChecked = true; // set as a prop ?    this->MaximumHeight = p->maximumHeight();  p->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,                               QSizePolicy::Preferred,                                QSizePolicy::DefaultType));  p->setContentsMargins(0, p->buttonSizeHint().height(),0 , 0);  QObject::connect(p, SIGNAL(toggled(bool)),                   p, SLOT(onToggled(bool)));}//-----------------------------------------------------------------------------void ctkCollapsibleButton::initStyleOption(QStyleOptionButton* option)const{  CTK_D(const ctkCollapsibleButton);  if (option == 0)    {    return;    }  option->initFrom(this);  if (this->isDown() )    {    option->state |= QStyle::State_Sunken;    }  if (this->isChecked() && !d->LookOffWhenChecked)    {    option->state |= QStyle::State_On;    }  if (!this->isDown())    {    option->state |= QStyle::State_Raised;    }  option->text = this->text();  option->icon = this->icon();  option->iconSize = QSize(this->style()->pixelMetric(QStyle::PM_IndicatorWidth, option, this),                       this->style()->pixelMetric(QStyle::PM_IndicatorHeight, option, this));  int buttonHeight = this->buttonSizeHint().height();//qMax(this->fontMetrics().height(), option->iconSize.height());  option->rect.setHeight(buttonHeight);}//-----------------------------------------------------------------------------ctkCollapsibleButton::ctkCollapsibleButton(QWidget* _parent)  :QAbstractButton(_parent){  CTK_INIT_PRIVATE(ctkCollapsibleButton);  ctk_d()->init();}//-----------------------------------------------------------------------------ctkCollapsibleButton::ctkCollapsibleButton(const QString& title, QWidget* _parent)  :QAbstractButton(_parent){  CTK_INIT_PRIVATE(ctkCollapsibleButton);  ctk_d()->init();  this->setText(title);}//-----------------------------------------------------------------------------ctkCollapsibleButton::~ctkCollapsibleButton(){}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setCollapsed(bool c){  if (!this->isCheckable())    {    // not sure if one should handle this case...    this->collapse(c);    return;    }  this->setChecked(!c);}//-----------------------------------------------------------------------------bool ctkCollapsibleButton::collapsed()const{  return ctk_d()->Collapsed;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setCollapsedHeight(int h){  ctk_d()->CollapsedHeight = h;  this->updateGeometry();}//-----------------------------------------------------------------------------int ctkCollapsibleButton::collapsedHeight()const{  return ctk_d()->CollapsedHeight;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::onToggled(bool checked){  if (this->isCheckable())    {    this->collapse(!checked);    }}//-----------------------------------------------------------------------------void ctkCollapsibleButton::collapse(bool c){  CTK_D(ctkCollapsibleButton);  if (c == d->Collapsed)    {    return;    }  d->Collapsed = c;  // we do that here as setVisible calls will correctly refresh the widget  if (c)    {    d->MaximumHeight = this->maximumHeight();    this->setMaximumHeight(this->sizeHint().height());    //this->updateGeometry();    }  else    {    // restore maximumheight    this->setMaximumHeight(d->MaximumHeight);    this->updateGeometry();    }  QObjectList childList = this->children();  for (int i = 0; i < childList.size(); ++i)    {    QObject *o = childList.at(i);    if (!o->isWidgetType())      {      continue;      }    QWidget *w = static_cast<QWidget *>(o);    w->setVisible(!c);    }  // this might be too many updates...  QWidget* _parent = this->parentWidget();  if (!d->Collapsed && (!_parent || !_parent->layout()))    {    this->resize(this->sizeHint());    }  else    {    this->updateGeometry();    }  //this->update(QRect(QPoint(0,0), this->sizeHint()));  //this->repaint(QRect(QPoint(0,0), this->sizeHint()));  emit contentsCollapsed(c);}//-----------------------------------------------------------------------------QFrame::Shape ctkCollapsibleButton::contentsFrameShape() const{  return ctk_d()->ContentsFrameShape;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setContentsFrameShape(QFrame::Shape s){  ctk_d()->ContentsFrameShape = s;}//-----------------------------------------------------------------------------QFrame::Shadow ctkCollapsibleButton::contentsFrameShadow() const{  return ctk_d()->ContentsFrameShadow;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setContentsFrameShadow(QFrame::Shadow s){  ctk_d()->ContentsFrameShadow = s;}//-----------------------------------------------------------------------------int ctkCollapsibleButton:: contentsLineWidth() const{  return ctk_d()->ContentsLineWidth;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setContentsLineWidth(int w){  ctk_d()->ContentsLineWidth = w;}//-----------------------------------------------------------------------------int ctkCollapsibleButton::contentsMidLineWidth() const{  return ctk_d()->ContentsMidLineWidth;}//-----------------------------------------------------------------------------void ctkCollapsibleButton::setContentsMidLineWidth(int w){  ctk_d()->ContentsMidLineWidth = w;}//-----------------------------------------------------------------------------QSize ctkCollapsibleButton::buttonSizeHint()const{  int w = 0, h = 0;  QStyleOptionButton opt;  opt.initFrom(this);    // indicator  QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),                              style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));  int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);  int ih = indicatorSize.height();  int iw = indicatorSize.width() + indicatorSpacing;  w += iw;  h = qMax(h, ih);    // text   QString string(this->text());  bool empty = string.isEmpty();  if (empty)    {    string = QString::fromLatin1("XXXX");    }  QFontMetrics fm = this->fontMetrics();  QSize sz = fm.size(Qt::TextShowMnemonic, string);  if(!empty || !w)    {    w += sz.width();    }  h = qMax(h, sz.height());  //opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height  QSize buttonSize = (style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this).                      expandedTo(QApplication::globalStrut()));  return buttonSize;}//-----------------------------------------------------------------------------QSize ctkCollapsibleButton::minimumSizeHint()const{  CTK_D(const ctkCollapsibleButton);  QSize buttonSize = this->buttonSizeHint();  if (d->Collapsed)    {    return buttonSize + QSize(0,d->CollapsedHeight);    }  // open  if (this->layout() == 0)    {// no layout, means the button is empty ?    return buttonSize;    }  QSize s = this->QAbstractButton::minimumSizeHint();   return s.expandedTo(buttonSize);}//-----------------------------------------------------------------------------QSize ctkCollapsibleButton::sizeHint()const{  CTK_D(const ctkCollapsibleButton);  QSize buttonSize = this->buttonSizeHint();  if (d->Collapsed)    {    return buttonSize + QSize(0,d->CollapsedHeight);    }  // open  // QAbstractButton works well only if a layout is set  QSize s = this->QAbstractButton::sizeHint();   return s.expandedTo(buttonSize + QSize(0, d->CollapsedHeight));}//-----------------------------------------------------------------------------void ctkCollapsibleButton::paintEvent(QPaintEvent * _event){  CTK_D(ctkCollapsibleButton);  QPainter p(this);  // Draw Button  QStyleOptionButton opt;  this->initStyleOption(&opt);  // We don't want to have the highlight effect on the button when mouse is  // over a child. We want the highlight effect only when the mouse is just  // over itself.  // same as this->underMouse()  bool exclusiveMouseOver = false;  if (opt.state & QStyle::State_MouseOver)    {    QRect buttonRect = opt.rect;    QList<QWidget*> _children = this->findChildren<QWidget*>();    QList<QWidget*>::ConstIterator it;    for (it = _children.constBegin(); it != _children.constEnd(); ++it )       {      if ((*it)->underMouse())        {        // the mouse has been moved from the collapsible button to one         // of its children. The paint event rect is the child rect, this        // is why we have to request another paint event to redraw the         // button to remove the highlight effect.        if (!_event->rect().contains(buttonRect))          {// repaint the button rect.          this->update(buttonRect);          }        opt.state &= ~QStyle::State_MouseOver;        exclusiveMouseOver = true;        break;        }      }    if (d->ExclusiveMouseOver && !exclusiveMouseOver)      {      // the mouse is over the widget, but not over the children. As it       // has been de-highlighted in the past, we should refresh the button      // rect to re-highlight the button.      if (!_event->rect().contains(buttonRect))        {// repaint the button rect.        this->update(buttonRect);        }      }    }  d->ExclusiveMouseOver = exclusiveMouseOver;  QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),                              style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));  opt.iconSize = indicatorSize;  style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this);  // is PE_PanelButtonCommand better ?  //style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);  int buttonHeight = opt.rect.height();  // Draw Indicator  QStyleOption indicatorOpt;  indicatorOpt.init(this);  indicatorOpt.rect = QRect((buttonHeight - indicatorSize.width()) / 2,                             (buttonHeight - indicatorSize.height()) / 2,                            indicatorSize.width(), indicatorSize.height());  if (d->Collapsed)    {    style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &indicatorOpt, &p, this);    }  else    {    style()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &indicatorOpt, &p, this);    }  // Draw Text  int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);  opt.rect.setLeft( indicatorOpt.rect.right() + indicatorSpacing);  uint tf = Qt::AlignVCenter | Qt::AlignLeft;  if (this->style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this))    {    tf |= Qt::TextShowMnemonic;    }  else    {    tf |= Qt::TextHideMnemonic;    }  style()->drawItemText(&p, opt.rect, tf, opt.palette, (opt.state & QStyle::State_Enabled),                        opt.text, QPalette::ButtonText);  // Draw Frame around contents  QStyleOptionFrameV3 f;  f.init(this);  // HACK: on some styles, the frame doesn't exactly touch the button.  // this is because the button has some kind of extra border.   if (qobject_cast<QCleanlooksStyle*>(this->style()) != 0)    {    f.rect.setTop(buttonHeight - 1);    }  else    {    f.rect.setTop(buttonHeight);    }  f.frameShape = d->ContentsFrameShape;  switch (d->ContentsFrameShadow)    {    case QFrame::Sunken:      f.state |= QStyle::State_Sunken;      break;    case QFrame::Raised:      f.state |= QStyle::State_Raised;      break;    default:    case QFrame::Plain:      break;    }  f.lineWidth = d->ContentsLineWidth;  f.midLineWidth = d->ContentsMidLineWidth;  style()->drawControl(QStyle::CE_ShapedFrame, &f, &p, this);}//-----------------------------------------------------------------------------bool ctkCollapsibleButton::hitButton(const QPoint & _pos)const{  QStyleOptionButton opt;  this->initStyleOption(&opt);  return opt.rect.contains(_pos);}//-----------------------------------------------------------------------------void ctkCollapsibleButton::childEvent(QChildEvent* c){  if(c && c->type() == QEvent::ChildAdded)    {    if (c->child() && c->child()->isWidgetType())      {      QWidget *w = static_cast<QWidget*>(c->child());      w->setVisible(!ctk_d()->Collapsed);      }    }  QWidget::childEvent(c);}
 |