ctkCollapsibleButton.cpp

Go to the documentation of this file.
00001 /*=========================================================================
00002 
00003   Library:   CTK
00004  
00005   Copyright (c) 2010  Kitware Inc.
00006 
00007   Licensed under the Apache License, Version 2.0 (the "License");
00008   you may not use this file except in compliance with the License.
00009   You may obtain a copy of the License at
00010 
00011       http://www.commontk.org/LICENSE
00012 
00013   Unless required by applicable law or agreed to in writing, software
00014   distributed under the License is distributed on an "AS IS" BASIS,
00015   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00016   See the License for the specific language governing permissions and
00017   limitations under the License.
00018  
00019 =========================================================================*/
00020 
00021 // Qt includes
00022 #include <QApplication>
00023 #include <QCleanlooksStyle>
00024 #include <QDebug>
00025 #include <QLayout>
00026 #include <QMouseEvent>
00027 #include <QPainter>
00028 #include <QPushButton>
00029 #include <QStyle>
00030 #include <QStyleOptionButton>
00031 #include <QStyleOptionFrameV3>
00032 
00033 // CTK includes
00034 #include "ctkCollapsibleButton.h"
00035 
00036 //-----------------------------------------------------------------------------
00037 class ctkCollapsibleButtonPrivate : public ctkPrivate<ctkCollapsibleButton>
00038 {
00039 public:
00040   CTK_DECLARE_PUBLIC(ctkCollapsibleButton);
00041   void init();
00042 
00043   bool     Collapsed;
00044 
00045   // Contents frame
00046   QFrame::Shape  ContentsFrameShape;
00047   QFrame::Shadow ContentsFrameShadow;
00048   int            ContentsLineWidth;
00049   int            ContentsMidLineWidth;
00050 
00051   int      CollapsedHeight;
00052   bool     ExclusiveMouseOver;
00053   bool     LookOffWhenChecked;
00054 
00055   int      MaximumHeight;  // use carefully
00056 };
00057 
00058 //-----------------------------------------------------------------------------
00059 void ctkCollapsibleButtonPrivate::init()
00060 {
00061   CTK_P(ctkCollapsibleButton);
00062   p->setCheckable(true);
00063   // checked and Collapsed are synchronized: checked != Collapsed
00064   p->setChecked(true);
00065 
00066   this->Collapsed = false;
00067 
00068   this->ContentsFrameShape = QFrame::NoFrame;
00069   this->ContentsFrameShadow = QFrame::Plain;
00070   this->ContentsLineWidth = 1;
00071   this->ContentsMidLineWidth = 0;
00072 
00073   this->CollapsedHeight = 10;
00074   this->ExclusiveMouseOver = false;  
00075   this->LookOffWhenChecked = true; // set as a prop ?
00076   
00077   this->MaximumHeight = p->maximumHeight();
00078 
00079   p->setSizePolicy(QSizePolicy(QSizePolicy::Minimum,
00080                                QSizePolicy::Preferred, 
00081                                QSizePolicy::DefaultType));
00082   p->setContentsMargins(0, p->buttonSizeHint().height(),0 , 0);
00083 
00084   QObject::connect(p, SIGNAL(toggled(bool)),
00085                    p, SLOT(onToggled(bool)));
00086 }
00087 
00088 //-----------------------------------------------------------------------------
00089 void ctkCollapsibleButton::initStyleOption(QStyleOptionButton* option)const
00090 {
00091   CTK_D(const ctkCollapsibleButton);
00092   if (option == 0)
00093     {
00094     return;
00095     }
00096   option->initFrom(this);
00097 
00098   if (this->isDown() )
00099     {
00100     option->state |= QStyle::State_Sunken;
00101     }
00102   if (this->isChecked() && !d->LookOffWhenChecked)
00103     {
00104     option->state |= QStyle::State_On;
00105     }
00106   if (!this->isDown())
00107     {
00108     option->state |= QStyle::State_Raised;
00109     }
00110 
00111   option->text = this->text();
00112   option->icon = this->icon();
00113   option->iconSize = QSize(this->style()->pixelMetric(QStyle::PM_IndicatorWidth, option, this),
00114                        this->style()->pixelMetric(QStyle::PM_IndicatorHeight, option, this));
00115   int buttonHeight = this->buttonSizeHint().height();//qMax(this->fontMetrics().height(), option->iconSize.height());
00116   option->rect.setHeight(buttonHeight);
00117 }
00118 
00119 //-----------------------------------------------------------------------------
00120 ctkCollapsibleButton::ctkCollapsibleButton(QWidget* _parent)
00121   :QAbstractButton(_parent)
00122 {
00123   CTK_INIT_PRIVATE(ctkCollapsibleButton);
00124   ctk_d()->init();
00125 }
00126 
00127 //-----------------------------------------------------------------------------
00128 ctkCollapsibleButton::ctkCollapsibleButton(const QString& title, QWidget* _parent)
00129   :QAbstractButton(_parent)
00130 {
00131   CTK_INIT_PRIVATE(ctkCollapsibleButton);
00132   ctk_d()->init();
00133   this->setText(title);
00134 }
00135 
00136 //-----------------------------------------------------------------------------
00137 ctkCollapsibleButton::~ctkCollapsibleButton()
00138 {
00139 }
00140 
00141 //-----------------------------------------------------------------------------
00142 void ctkCollapsibleButton::setCollapsed(bool c)
00143 {
00144   if (!this->isCheckable())
00145     {
00146     // not sure if one should handle this case...
00147     this->collapse(c);
00148     return;
00149     }
00150   this->setChecked(!c);
00151 }
00152 
00153 //-----------------------------------------------------------------------------
00154 bool ctkCollapsibleButton::collapsed()const
00155 {
00156   return ctk_d()->Collapsed;
00157 }
00158 
00159 //-----------------------------------------------------------------------------
00160 void ctkCollapsibleButton::setCollapsedHeight(int h)
00161 {
00162   ctk_d()->CollapsedHeight = h;
00163   this->updateGeometry();
00164 }
00165 
00166 //-----------------------------------------------------------------------------
00167 int ctkCollapsibleButton::collapsedHeight()const
00168 {
00169   return ctk_d()->CollapsedHeight;
00170 }
00171 
00172 //-----------------------------------------------------------------------------
00173 void ctkCollapsibleButton::onToggled(bool checked)
00174 {
00175   if (this->isCheckable())
00176     {
00177     this->collapse(!checked);
00178     }
00179 }
00180 
00181 //-----------------------------------------------------------------------------
00182 void ctkCollapsibleButton::collapse(bool c)
00183 {
00184   CTK_D(ctkCollapsibleButton);
00185   if (c == d->Collapsed)
00186     {
00187     return;
00188     }
00189 
00190   d->Collapsed = c;
00191 
00192   // we do that here as setVisible calls will correctly refresh the widget
00193   if (c)
00194     {
00195     d->MaximumHeight = this->maximumHeight();
00196     this->setMaximumHeight(this->sizeHint().height());
00197     //this->updateGeometry();
00198     }
00199   else
00200     {
00201     // restore maximumheight
00202     this->setMaximumHeight(d->MaximumHeight);
00203     this->updateGeometry();
00204     }
00205 
00206   QObjectList childList = this->children();
00207   for (int i = 0; i < childList.size(); ++i)
00208     {
00209     QObject *o = childList.at(i);
00210     if (!o->isWidgetType())
00211       {
00212       continue;
00213       }
00214     QWidget *w = static_cast<QWidget *>(o);
00215     w->setVisible(!c);
00216     }
00217   // this might be too many updates...
00218   QWidget* _parent = this->parentWidget();
00219   if (!d->Collapsed && (!_parent || !_parent->layout()))
00220     {
00221     this->resize(this->sizeHint());
00222     }
00223   else
00224     {
00225     this->updateGeometry();
00226     }
00227   //this->update(QRect(QPoint(0,0), this->sizeHint()));
00228   //this->repaint(QRect(QPoint(0,0), this->sizeHint()));
00229   emit contentsCollapsed(c);
00230 }
00231 
00232 //-----------------------------------------------------------------------------
00233 QFrame::Shape ctkCollapsibleButton::contentsFrameShape() const
00234 {
00235   return ctk_d()->ContentsFrameShape;
00236 }
00237 
00238 //-----------------------------------------------------------------------------
00239 void ctkCollapsibleButton::setContentsFrameShape(QFrame::Shape s)
00240 {
00241   ctk_d()->ContentsFrameShape = s;
00242 }
00243 
00244 //-----------------------------------------------------------------------------
00245 QFrame::Shadow ctkCollapsibleButton::contentsFrameShadow() const
00246 {
00247   return ctk_d()->ContentsFrameShadow;
00248 }
00249 
00250 //-----------------------------------------------------------------------------
00251 void ctkCollapsibleButton::setContentsFrameShadow(QFrame::Shadow s)
00252 {
00253   ctk_d()->ContentsFrameShadow = s;
00254 }
00255 
00256 //-----------------------------------------------------------------------------
00257 int ctkCollapsibleButton:: contentsLineWidth() const
00258 {
00259   return ctk_d()->ContentsLineWidth;
00260 }
00261 
00262 //-----------------------------------------------------------------------------
00263 void ctkCollapsibleButton::setContentsLineWidth(int w)
00264 {
00265   ctk_d()->ContentsLineWidth = w;
00266 }
00267 
00268 //-----------------------------------------------------------------------------
00269 int ctkCollapsibleButton::contentsMidLineWidth() const
00270 {
00271   return ctk_d()->ContentsMidLineWidth;
00272 }
00273 
00274 //-----------------------------------------------------------------------------
00275 void ctkCollapsibleButton::setContentsMidLineWidth(int w)
00276 {
00277   ctk_d()->ContentsMidLineWidth = w;
00278 }
00279 
00280 //-----------------------------------------------------------------------------
00281 QSize ctkCollapsibleButton::buttonSizeHint()const
00282 {
00283   int w = 0, h = 0;
00284 
00285   QStyleOptionButton opt;
00286   opt.initFrom(this);
00287   
00288   // indicator
00289   QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),
00290                               style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));
00291   int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);
00292   int ih = indicatorSize.height();
00293   int iw = indicatorSize.width() + indicatorSpacing;
00294   w += iw;
00295   h = qMax(h, ih);
00296   
00297   // text 
00298   QString string(this->text());
00299   bool empty = string.isEmpty();
00300   if (empty)
00301     {
00302     string = QString::fromLatin1("XXXX");
00303     }
00304   QFontMetrics fm = this->fontMetrics();
00305   QSize sz = fm.size(Qt::TextShowMnemonic, string);
00306   if(!empty || !w)
00307     {
00308     w += sz.width();
00309     }
00310   h = qMax(h, sz.height());
00311   //opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
00312   QSize buttonSize = (style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this).
00313                       expandedTo(QApplication::globalStrut()));
00314   return buttonSize;
00315 }
00316 
00317 //-----------------------------------------------------------------------------
00318 QSize ctkCollapsibleButton::minimumSizeHint()const
00319 {
00320   CTK_D(const ctkCollapsibleButton);
00321   QSize buttonSize = this->buttonSizeHint();
00322   if (d->Collapsed)
00323     {
00324     return buttonSize + QSize(0,d->CollapsedHeight);
00325     }
00326   // open
00327   if (this->layout() == 0)
00328     {// no layout, means the button is empty ?
00329     return buttonSize;
00330     }
00331   QSize s = this->QAbstractButton::minimumSizeHint(); 
00332   return s.expandedTo(buttonSize);
00333 }
00334 
00335 //-----------------------------------------------------------------------------
00336 QSize ctkCollapsibleButton::sizeHint()const
00337 {
00338   CTK_D(const ctkCollapsibleButton);
00339   QSize buttonSize = this->buttonSizeHint();
00340   if (d->Collapsed)
00341     {
00342     return buttonSize + QSize(0,d->CollapsedHeight);
00343     }
00344   // open
00345   // QAbstractButton works well only if a layout is set
00346   QSize s = this->QAbstractButton::sizeHint(); 
00347   return s.expandedTo(buttonSize + QSize(0, d->CollapsedHeight));
00348 }
00349 
00350 //-----------------------------------------------------------------------------
00351 void ctkCollapsibleButton::paintEvent(QPaintEvent * _event)
00352 {
00353   CTK_D(ctkCollapsibleButton);
00354 
00355   QPainter p(this);
00356   // Draw Button
00357   QStyleOptionButton opt;
00358   this->initStyleOption(&opt);
00359 
00360   // We don't want to have the highlight effect on the button when mouse is
00361   // over a child. We want the highlight effect only when the mouse is just
00362   // over itself.
00363   // same as this->underMouse()
00364   bool exclusiveMouseOver = false;
00365   if (opt.state & QStyle::State_MouseOver)
00366     {
00367     QRect buttonRect = opt.rect;
00368     QList<QWidget*> _children = this->findChildren<QWidget*>();
00369     QList<QWidget*>::ConstIterator it;
00370     for (it = _children.constBegin(); it != _children.constEnd(); ++it ) 
00371       {
00372       if ((*it)->underMouse())
00373         {
00374         // the mouse has been moved from the collapsible button to one 
00375         // of its children. The paint event rect is the child rect, this
00376         // is why we have to request another paint event to redraw the 
00377         // button to remove the highlight effect.
00378         if (!_event->rect().contains(buttonRect))
00379           {// repaint the button rect.
00380           this->update(buttonRect);
00381           }
00382         opt.state &= ~QStyle::State_MouseOver;
00383         exclusiveMouseOver = true;
00384         break;
00385         }
00386       }
00387     if (d->ExclusiveMouseOver && !exclusiveMouseOver)
00388       {
00389       // the mouse is over the widget, but not over the children. As it 
00390       // has been de-highlighted in the past, we should refresh the button
00391       // rect to re-highlight the button.
00392       if (!_event->rect().contains(buttonRect))
00393         {// repaint the button rect.
00394         this->update(buttonRect);
00395         }
00396       }
00397     }
00398   d->ExclusiveMouseOver = exclusiveMouseOver;
00399   QSize indicatorSize = QSize(style()->pixelMetric(QStyle::PM_IndicatorWidth, &opt, this),
00400                               style()->pixelMetric(QStyle::PM_IndicatorHeight, &opt, this));
00401   opt.iconSize = indicatorSize;
00402   style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this);
00403   // is PE_PanelButtonCommand better ?
00404   //style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &opt, &p, this);
00405   int buttonHeight = opt.rect.height();
00406 
00407   // Draw Indicator
00408   QStyleOption indicatorOpt;
00409   indicatorOpt.init(this);
00410   indicatorOpt.rect = QRect((buttonHeight - indicatorSize.width()) / 2, 
00411                             (buttonHeight - indicatorSize.height()) / 2,
00412                             indicatorSize.width(), indicatorSize.height());
00413   if (d->Collapsed)
00414     {
00415     style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &indicatorOpt, &p, this);
00416     }
00417   else
00418     {
00419     style()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &indicatorOpt, &p, this);
00420     }
00421 
00422   // Draw Text
00423   int indicatorSpacing = style()->pixelMetric(QStyle::PM_CheckBoxLabelSpacing, &opt, this);
00424   opt.rect.setLeft( indicatorOpt.rect.right() + indicatorSpacing);
00425   uint tf = Qt::AlignVCenter | Qt::AlignLeft;
00426   if (this->style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this))
00427     {
00428     tf |= Qt::TextShowMnemonic;
00429     }
00430   else
00431     {
00432     tf |= Qt::TextHideMnemonic;
00433     }
00434   style()->drawItemText(&p, opt.rect, tf, opt.palette, (opt.state & QStyle::State_Enabled),
00435                         opt.text, QPalette::ButtonText);
00436 
00437   // Draw Frame around contents
00438   QStyleOptionFrameV3 f;
00439   f.init(this);
00440   // HACK: on some styles, the frame doesn't exactly touch the button.
00441   // this is because the button has some kind of extra border. 
00442   if (qobject_cast<QCleanlooksStyle*>(this->style()) != 0)
00443     {
00444     f.rect.setTop(buttonHeight - 1);
00445     }
00446   else
00447     {
00448     f.rect.setTop(buttonHeight);
00449     }
00450   f.frameShape = d->ContentsFrameShape;
00451   switch (d->ContentsFrameShadow)
00452     {
00453     case QFrame::Sunken:
00454       f.state |= QStyle::State_Sunken;
00455       break;
00456     case QFrame::Raised:
00457       f.state |= QStyle::State_Raised;
00458       break;
00459     default:
00460     case QFrame::Plain:
00461       break;
00462     }
00463   f.lineWidth = d->ContentsLineWidth;
00464   f.midLineWidth = d->ContentsMidLineWidth;
00465   style()->drawControl(QStyle::CE_ShapedFrame, &f, &p, this);
00466 }
00467 
00468 //-----------------------------------------------------------------------------
00469 bool ctkCollapsibleButton::hitButton(const QPoint & _pos)const
00470 {
00471   QStyleOptionButton opt;
00472   this->initStyleOption(&opt);
00473   return opt.rect.contains(_pos);
00474 }
00475 
00476 //-----------------------------------------------------------------------------
00477 void ctkCollapsibleButton::childEvent(QChildEvent* c)
00478 {
00479   if(c && c->type() == QEvent::ChildAdded)
00480     {
00481     if (c->child() && c->child()->isWidgetType())
00482       {
00483       QWidget *w = static_cast<QWidget*>(c->child());
00484       w->setVisible(!ctk_d()->Collapsed);
00485       }
00486     }
00487   QWidget::childEvent(c);
00488 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines

Generated on 21 May 2010 for CTK by  doxygen 1.6.1