ctkCheckableHeaderView.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 
00022    Program: ParaView
00023    Module:    $RCSfile: pqCheckableHeaderView.cxx,v $
00024 
00025    Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
00026    All rights reserved.
00027 
00028    ParaView is a free software; you can redistribute it and/or modify it
00029    under the terms of the ParaView license version 1.2. 
00030 
00031    See License_v1.2.txt for the full ParaView license.
00032    A copy of this license can be obtained by contacting
00033    Kitware Inc.
00034    28 Corporate Drive
00035    Clifton Park, NY 12065
00036    USA
00037 
00038 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00039 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00040 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00041 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
00042 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00043 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00044 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00045 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00046 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00047 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00048 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00049 
00050 =========================================================================*/
00051 
00052 // Qt includes
00053 #include <QAbstractItemModel>
00054 #include <QApplication>
00055 #include <QDebug>
00056 #include <QEvent>
00057 #include <QList>
00058 #include <QMouseEvent>
00059 #include <QPainter>
00060 #include <QPixmap>
00061 #include <QStyle>
00062 
00063 // CTK includes
00064 #include "ctkCheckableHeaderView.h"
00065 #include "ctkCheckBoxPixmaps.h"
00066 
00067 //-----------------------------------------------------------------------------
00068 class ctkCheckableHeaderViewPrivate: public ctkPrivate<ctkCheckableHeaderView>
00069 {
00070   CTK_DECLARE_PUBLIC(ctkCheckableHeaderView)
00071 public:
00072   ctkCheckableHeaderViewPrivate();
00073   ~ctkCheckableHeaderViewPrivate();
00074   void init();
00075 
00076   int Pressed;
00077   ctkCheckBoxPixmaps* CheckBoxPixmaps;
00078   bool HeaderIsUpdating;
00079   bool ItemsAreUpdating;
00080   bool PropagateToItems;
00081 };
00082 
00083 //----------------------------------------------------------------------------
00084 ctkCheckableHeaderViewPrivate::ctkCheckableHeaderViewPrivate()
00085 {
00086   this->HeaderIsUpdating = false;
00087   this->ItemsAreUpdating = false;
00088   this->CheckBoxPixmaps = 0;
00089   this->Pressed = -1;
00090   this->PropagateToItems = false;
00091 }
00092 
00093 //-----------------------------------------------------------------------------
00094 ctkCheckableHeaderViewPrivate::~ctkCheckableHeaderViewPrivate()
00095 {
00096   if (this->CheckBoxPixmaps)
00097     {
00098     delete this->CheckBoxPixmaps;
00099     this->CheckBoxPixmaps = 0;
00100     }
00101 }
00102 
00103 //----------------------------------------------------------------------------
00104 void ctkCheckableHeaderViewPrivate::init()
00105 {
00106   CTK_P(ctkCheckableHeaderView);
00107   this->CheckBoxPixmaps = new ctkCheckBoxPixmaps(p);
00108 }
00109 
00110 //----------------------------------------------------------------------------
00111 ctkCheckableHeaderView::ctkCheckableHeaderView(
00112   Qt::Orientation orient, QWidget *widgetParent)
00113   : QHeaderView(orient, widgetParent)
00114 {
00115   CTK_INIT_PRIVATE(ctkCheckableHeaderView);
00116   ctk_d()->init();
00117   if(widgetParent)
00118     {
00119     // Listen for focus change events.
00120     widgetParent->installEventFilter(this);
00121     }
00122 }
00123 
00124 //-----------------------------------------------------------------------------
00125 ctkCheckableHeaderView::~ctkCheckableHeaderView()
00126 {
00127 }
00128 
00129 //-----------------------------------------------------------------------------
00130 bool ctkCheckableHeaderView::eventFilter(QObject *, QEvent *e)
00131 {
00132   if(e->type() != QEvent::FocusIn && 
00133      e->type() != QEvent::FocusOut)
00134     {
00135     return false;
00136     }
00137   this->updateHeaders();
00138   return false;
00139 }
00140 
00141 //-----------------------------------------------------------------------------
00142 void ctkCheckableHeaderView::setModel(QAbstractItemModel *newModel)
00143 {
00144   CTK_D(ctkCheckableHeaderView);
00145   QAbstractItemModel *current = this->model();
00146   if (current == newModel)
00147     {
00148     return;
00149     }
00150   if(current)
00151     {
00152     this->disconnect(
00153       current, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
00154       this, SLOT(updateHeaderData(Qt::Orientation, int, int)));
00155     this->disconnect(
00156       current, SIGNAL(modelReset()),
00157       this, SLOT(updateHeaders()));
00158     this->disconnect(
00159       current, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00160       this, SLOT(updateHeadersFromItems(const QModelIndex&, const QModelIndex&)));
00161     this->disconnect(
00162       current, SIGNAL(columnsInserted(const QModelIndex &, int, int)), 
00163       this, SLOT(insertHeaderSection(const QModelIndex &, int, int)));
00164     this->disconnect(
00165       current, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
00166       this, SLOT(insertHeaderSection(const QModelIndex &, int, int)));
00167     }
00168 
00169   this->QHeaderView::setModel(newModel);
00170   if(newModel)
00171     {
00172     this->connect(
00173       newModel, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
00174       this, SLOT(updateHeaderData(Qt::Orientation, int, int)));
00175     this->connect(
00176       newModel, SIGNAL(modelReset()),
00177       this, SLOT(updateHeaders()));
00178     if (d->PropagateToItems)
00179       {
00180       this->connect(
00181         newModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00182         this, SLOT(updateHeadersFromItems(const QModelIndex&, const QModelIndex&)));
00183       }
00184     if(this->orientation() == Qt::Horizontal)
00185       {
00186       this->connect(
00187         newModel, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
00188         this, SLOT(insertHeaderSection(const QModelIndex &, int, int)));
00189       }
00190     else
00191       {
00192       this->connect(
00193         newModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
00194         this, SLOT(insertHeaderSection(const QModelIndex &, int, int)));
00195       }
00196     }
00197 
00198   // Determine which sections are clickable and setup the icons.
00199   this->updateHeaders();
00200 }
00201 
00202 //-----------------------------------------------------------------------------
00203 void ctkCheckableHeaderView::setRootIndex(const QModelIndex &index)
00204 {
00205   this->QHeaderView::setRootIndex(index);
00206   this->updateHeaders();
00207 }
00208 
00209 //-----------------------------------------------------------------------------
00210 void ctkCheckableHeaderView::setPropagateToItems(bool propagate)
00211 {
00212   CTK_D(ctkCheckableHeaderView);
00213   d->PropagateToItems = propagate;
00214   if (!this->model())
00215     {
00216     return;
00217     }
00218   if (propagate)
00219     {
00220     this->connect(
00221       this->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00222       this, SLOT(updateHeadersFromItems(const QModelIndex&, const QModelIndex&)));
00223     }
00224   else
00225     {
00226     this->disconnect(
00227       this->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00228       this, SLOT(updateHeadersFromItems(const QModelIndex&, const QModelIndex&)));
00229     }
00230 }
00231 
00232 //-----------------------------------------------------------------------------
00233 bool ctkCheckableHeaderView::propagateToItems()const
00234 {
00235   CTK_D(const ctkCheckableHeaderView);
00236   return d->PropagateToItems;
00237 }
00238 
00239 //-----------------------------------------------------------------------------
00240 void ctkCheckableHeaderView::toggleCheckState(int section)
00241 {
00242   // If the section is checkable, toggle the check state.
00243   if(!this->isCheckable(section))
00244     {    
00245     return;
00246     }
00247   // I've no strong feeling to turn the state checked or unchecked when the 
00248   // state is PartiallyChecked.
00249   this->setCheckState(section, this->checkState(section) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
00250 }
00251 
00252 //-----------------------------------------------------------------------------
00253 void ctkCheckableHeaderView::setCheckState(int section, Qt::CheckState checkState)
00254 {
00255   // If the section is checkable, toggle the check state.
00256   QAbstractItemModel *current = this->model();
00257   if(current == 0)
00258     {    
00259     return;
00260     }
00261   // If the state is unchecked or partially checked, the state
00262   // should be changed to checked.
00263   current->setHeaderData(section, this->orientation(),
00264                          checkState, Qt::CheckStateRole);
00265 }
00266 
00267 //-----------------------------------------------------------------------------
00268 void ctkCheckableHeaderView::updateHeaderData(Qt::Orientation orient,
00269                                                int first, int last)
00270 {
00271   if(orient != this->orientation())
00272     {
00273     return;
00274     }
00275   this->updateHeaders(first, last);
00276 }
00277 
00278 //-----------------------------------------------------------------------------
00279 void ctkCheckableHeaderView::updateHeaders(int first, int last)
00280 {
00281   CTK_D(ctkCheckableHeaderView);
00282   if(d->HeaderIsUpdating)
00283     {
00284     return;
00285     }
00286   d->HeaderIsUpdating = true;
00287   QAbstractItemModel *current = this->model();
00288   Q_ASSERT(current);
00289 
00290   first = qBound(0, first, this->count() -1);
00291   last = qBound(0, last, this->count() -1);
00292 
00293   bool active = true;
00294   if(this->parentWidget())
00295     {
00296     active = this->parentWidget()->hasFocus();
00297     }
00298   int maxJ = this->orientation() == Qt::Horizontal ? 
00299     current->rowCount() : current->columnCount();
00300   
00301   for(int i = first; i <= last; i++)
00302     {
00303     QVariant decoration;
00304     Qt::CheckState checkState;
00305     if (this->checkState(i, checkState))
00306       {
00307       decoration = d->CheckBoxPixmaps->pixmap(checkState, active);
00308       if (d->PropagateToItems && 
00309           checkState != Qt::PartiallyChecked &&
00310           !d->ItemsAreUpdating)
00311         {
00312         for (int j = 0 ; j < maxJ; ++j)
00313           {
00314           QModelIndex index = this->orientation() == Qt::Horizontal ? 
00315             current->index(j, i,this->rootIndex()) :
00316             current->index(i, j,this->rootIndex()) ;
00317           current->setData(index, checkState, Qt::CheckStateRole);
00318           }
00319         }
00320       }
00321     current->setHeaderData(i, this->orientation(), decoration,
00322                            Qt::DecorationRole);
00323     }
00324   d->HeaderIsUpdating = false;
00325 }
00326 
00327 //-----------------------------------------------------------------------------
00328 void ctkCheckableHeaderView::updateHeadersFromItems(const QModelIndex & topLeft,
00329                                                      const QModelIndex & bottomRight)
00330 {
00331   CTK_D(ctkCheckableHeaderView);
00332   if(d->ItemsAreUpdating || !d->PropagateToItems || 
00333      topLeft.parent() != this->rootIndex())
00334     {
00335     return;
00336     }
00337   d->ItemsAreUpdating = true;
00338   
00339   QAbstractItemModel *current = this->model();
00340   Q_ASSERT(current);
00341 
00342   int lastI;
00343   int lastJ;
00344   if (this->orientation() == Qt::Horizontal)
00345     {
00346     lastI = this->count();
00347     lastJ = current->rowCount();
00348     }
00349   else
00350     {
00351     lastI = this->count();
00352     lastJ = current->columnCount();
00353     }
00354   
00355   for(int i = 0; i <= lastI; ++i)
00356     {
00357     Qt::CheckState sectionState;
00358     if (!this->checkState(i, sectionState))
00359       {
00360       continue;
00361       }
00362     bool itemIsCheckable = false;
00363     // get the first item state
00364     Qt::CheckState itemsState;
00365     int j = 0;
00366     for ( ; j <= lastJ; ++j)
00367       {
00368       QModelIndex index = this->orientation() == Qt::Horizontal ? 
00369         current->index(j, i, topLeft.parent()) : 
00370         current->index(i, j, topLeft.parent());
00371       itemsState = static_cast<Qt::CheckState>(
00372         index.data(Qt::CheckStateRole).toInt(&itemIsCheckable));
00373       if (itemIsCheckable)
00374         {
00375         break;
00376         }
00377       }
00378     if (j > lastJ)
00379       {// the first item check state couldn't be found
00380       continue;
00381       }
00382     // check the other states to make sure it is the same state
00383     for (; j <= lastJ; ++j)
00384       {
00385       QModelIndex index = this->orientation() == Qt::Horizontal ? 
00386         current->index(j, i, topLeft.parent()) : 
00387         current->index(i, j, topLeft.parent());
00388       Qt::CheckState itemState = 
00389         static_cast<Qt::CheckState>(index.data(Qt::CheckStateRole).toInt(&itemIsCheckable));
00390       if (itemIsCheckable && itemState!= itemsState)
00391         {// there is at least 1 item with a different state
00392         this->setCheckState(i, Qt::PartiallyChecked);
00393         break;
00394         }
00395       }
00396     if (j > lastJ)
00397       {
00398       this->setCheckState(i, itemsState);
00399       }
00400     }
00401   d->ItemsAreUpdating = false;
00402 }
00403 
00404 
00405 //-----------------------------------------------------------------------------
00406 void ctkCheckableHeaderView::insertHeaderSection(const QModelIndex &parentIndex,
00407     int first, int last)
00408 {
00409   if (this->rootIndex() != parentIndex)
00410     {
00411     return;
00412     }
00413   this->updateHeaders(first, last);
00414 }
00415 
00416 //-----------------------------------------------------------------------------
00417 bool ctkCheckableHeaderView::isCheckable(int section)const
00418 {
00419   return !this->model()->headerData(section, this->orientation(), Qt::CheckStateRole).isNull();
00420 }
00421 
00422 //-----------------------------------------------------------------------------
00423 Qt::CheckState ctkCheckableHeaderView::checkState(int section)const
00424 {
00425   return static_cast<Qt::CheckState>(
00426     this->model()->headerData(section, this->orientation(), Qt::CheckStateRole).toInt());
00427 }
00428 
00429 //-----------------------------------------------------------------------------
00430 bool ctkCheckableHeaderView::checkState(int section, Qt::CheckState& checkState)const
00431 {
00432   bool checkable = false;
00433   checkState = static_cast<Qt::CheckState>(
00434     this->model()->headerData(section, this->orientation(), Qt::CheckStateRole).toInt(&checkable));
00435   return checkable;
00436 }
00437 
00438 //-----------------------------------------------------------------------------
00439 void ctkCheckableHeaderView::mousePressEvent(QMouseEvent *e)
00440 {
00441   CTK_D(ctkCheckableHeaderView);
00442   if (e->button() != Qt::LeftButton || 
00443       d->Pressed >= 0)
00444     {
00445     d->Pressed = -1;
00446     this->QHeaderView::mousePressEvent(e);
00447     return;
00448     }
00449   d->Pressed = -1;
00450   //check if the check box is pressed
00451   int pos = this->orientation() == Qt::Horizontal ? e->x() : e->y();
00452   int section = this->logicalIndexAt(pos);
00453   if (this->isCheckable(section) &&
00454       this->isPointInCheckBox(section, e->pos()))
00455     {
00456     d->Pressed = section;
00457     }
00458   this->QHeaderView::mousePressEvent(e);
00459 }
00460 
00461 //-----------------------------------------------------------------------------
00462 void ctkCheckableHeaderView::mouseReleaseEvent(QMouseEvent *e)
00463 {
00464   CTK_D(ctkCheckableHeaderView);
00465   if (e->button() != Qt::LeftButton || 
00466       d->Pressed < 0)
00467     {
00468     d->Pressed = -1;
00469     this->QHeaderView::mouseReleaseEvent(e);
00470     return;
00471     }
00472   //check if the check box is pressed
00473   int pos = this->orientation() == Qt::Horizontal ? e->x() : e->y();
00474   int section = this->logicalIndexAt(pos);
00475   if (section == d->Pressed && 
00476       this->isPointInCheckBox(section, e->pos()))
00477     {
00478     d->Pressed = -1;
00479     this->toggleCheckState(section);
00480     }
00481   this->QHeaderView::mousePressEvent(e);
00482 }
00483 
00484 //-----------------------------------------------------------------------------
00485 bool ctkCheckableHeaderView::isPointInCheckBox(int section, QPoint pos)const
00486 {
00487   QRect sectionRect = this->orientation() == Qt::Horizontal ? 
00488     QRect(this->sectionPosition(section), 0, 
00489           this->sectionSize(section), this->height()):
00490     QRect(0, this->sectionPosition(section), 
00491           this->width(), this->sectionSize(section));
00492   QStyleOptionHeader opt;
00493   this->initStyleOption(&opt);
00494   this->initStyleSectionOption(&opt, section, sectionRect);
00495   QRect headerLabelRect = this->style()->subElementRect(QStyle::SE_HeaderLabel, &opt, this);
00496   // from qcommonstyle.cpp:1541
00497   if (opt.icon.isNull()) 
00498     {
00499     return false;
00500     }
00501   QPixmap pixmap
00502     = opt.icon.pixmap(this->style()->pixelMetric(QStyle::PM_SmallIconSize), 
00503                       (opt.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled);
00504   QRect aligned = this->style()->alignedRect(opt.direction, QFlag(opt.iconAlignment), 
00505                               pixmap.size(), headerLabelRect);
00506   QRect inter = aligned.intersected(headerLabelRect);
00507   return inter.contains(pos);
00508 }
00509 
00510 //-----------------------------------------------------------------------------
00511 void ctkCheckableHeaderView::initStyleSectionOption(QStyleOptionHeader *option, int section, QRect rect)const
00512 {
00513   // from qheaderview.cpp:paintsection
00514   QStyle::State state = QStyle::State_None;
00515   if (this->isEnabled())
00516     {
00517     state |= QStyle::State_Enabled;
00518     }
00519   if (this->window()->isActiveWindow())
00520     {
00521     state |= QStyle::State_Active;
00522     }
00523   if (this->isSortIndicatorShown() && 
00524       this->sortIndicatorSection() == section)
00525     {
00526     option->sortIndicator = (this->sortIndicatorOrder() == Qt::AscendingOrder)
00527       ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
00528     }
00529 
00530   // setup the style option structure
00531   QVariant textAlignment = 
00532     this->model()->headerData(section, this->orientation(),
00533                               Qt::TextAlignmentRole);
00534   option->rect = rect;
00535   option->section = section;
00536   option->state |= state;
00537   option->textAlignment = Qt::Alignment(textAlignment.isValid()
00538                                         ? Qt::Alignment(textAlignment.toInt())
00539                                         : this->defaultAlignment());
00540   
00541   option->iconAlignment = Qt::AlignVCenter;
00542   option->text = this->model()->headerData(section, this->orientation(),
00543                                   Qt::DisplayRole).toString();
00544   if (this->textElideMode() != Qt::ElideNone)
00545     {
00546     option->text = option->fontMetrics.elidedText(option->text, this->textElideMode() , rect.width() - 4);
00547     }
00548 
00549   QVariant variant = this->model()->headerData(section, this->orientation(),
00550                                           Qt::DecorationRole);
00551   option->icon = qvariant_cast<QIcon>(variant);
00552   if (option->icon.isNull())
00553     {
00554     option->icon = qvariant_cast<QPixmap>(variant);
00555     }
00556   QVariant foregroundBrush = this->model()->headerData(section, this->orientation(),
00557                                                   Qt::ForegroundRole);
00558   if (qVariantCanConvert<QBrush>(foregroundBrush))
00559     {
00560     option->palette.setBrush(QPalette::ButtonText, qvariant_cast<QBrush>(foregroundBrush));
00561     }
00562 
00563   //QPointF oldBO = painter->brushOrigin();
00564   QVariant backgroundBrush = this->model()->headerData(section, this->orientation(),
00565                                                   Qt::BackgroundRole);
00566   if (qVariantCanConvert<QBrush>(backgroundBrush)) 
00567     {
00568     option->palette.setBrush(QPalette::Button, qvariant_cast<QBrush>(backgroundBrush));
00569     option->palette.setBrush(QPalette::Window, qvariant_cast<QBrush>(backgroundBrush));
00570     //painter->setBrushOrigin(option->rect.topLeft());
00571     }
00572 
00573   // the section position
00574   int visual = this->visualIndex(section);
00575   Q_ASSERT(visual != -1);
00576   if (this->count() == 1)
00577     {
00578     option->position = QStyleOptionHeader::OnlyOneSection;
00579     }
00580   else if (visual == 0)
00581     {
00582     option->position = QStyleOptionHeader::Beginning;
00583     }
00584   else if (visual == this->count() - 1)
00585     {
00586     option->position = QStyleOptionHeader::End;
00587     }
00588   else
00589     {
00590     option->position = QStyleOptionHeader::Middle;
00591     }
00592   option->orientation = this->orientation();
00593   /* the selected position
00594   bool previousSelected = d->isSectionSelected(this->logicalIndex(visual - 1));
00595   bool nextSelected =  d->isSectionSelected(this->logicalIndex(visual + 1));
00596   if (previousSelected && nextSelected)
00597     option->selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected;
00598   else if (previousSelected)
00599     option->selectedPosition = QStyleOptionHeader::PreviousIsSelected;
00600   else if (nextSelected)
00601     option->selectedPosition = QStyleOptionHeader::NextIsSelected;
00602   else
00603     option->selectedPosition = QStyleOptionHeader::NotAdjacent;
00604   */
00605 }
00606     
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines

Generated on 21 May 2010 for CTK by  doxygen 1.6.1