1 /*=========================================================================
2 
3   Library:   CTK
4 
5   Copyright (c) Kitware Inc.
6 
7   Licensed under the Apache License, Version 2.0 (the "License");
8   you may not use this file except in compliance with the License.
9   You may obtain a copy of the License at
10 
11       http://www.apache.org/licenses/LICENSE-2.0.txt
12 
13   Unless required by applicable law or agreed to in writing, software
14   distributed under the License is distributed on an "AS IS" BASIS,
15   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   See the License for the specific language governing permissions and
17   limitations under the License.
18 
19 =========================================================================*/
20 /*=========================================================================
21 
22    Program: ParaView
23    Module:  pqCheckableHeaderView.cxx
24 
25    Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc.
26    All rights reserved.
27 
28    ParaView is a free software; you can redistribute it and/or modify it
29    under the terms of the ParaView license version 1.2.
30 
31    See http://www.paraview.org/paraview/project/license.html for the full ParaView license.
32    A copy of this license can be obtained by contacting
33    Kitware Inc.
34    28 Corporate Drive
35    Clifton Park, NY 12065
36    USA
37 
38 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
39 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
40 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
41 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
42 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
43 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
44 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
45 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
46 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
47 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
48 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49 
50 =========================================================================*/
51 
52 // Qt includes
53 #include <QAbstractItemModel>
54 #include <QApplication>
55 #include <QDebug>
56 #include <QEvent>
57 #include <QList>
58 #include <QMouseEvent>
59 #include <QPainter>
60 #include <QPixmap>
61 #include <QStyle>
62 
63 // CTK includes
64 #include "ctkCheckableHeaderView.h"
65 #include <ctkCheckableModelHelper.h>
66 #include "ctkCheckBoxPixmaps.h"
67 
68 //-----------------------------------------------------------------------------
69 class ctkCheckableHeaderViewPrivate
70 {
71   Q_DECLARE_PUBLIC(ctkCheckableHeaderView);
72 protected:
73   ctkCheckableHeaderView* const q_ptr;
74 public:
75   ctkCheckableHeaderViewPrivate(ctkCheckableHeaderView& object);
76   ~ctkCheckableHeaderViewPrivate();
77 
78   void init();
79 
80   ctkCheckableModelHelper* CheckableModelHelper;
81   int                 Pressed;
82   ctkCheckBoxPixmaps* CheckBoxPixmaps;
83   bool                HeaderIsUpdating;
84 };
85 
86 //----------------------------------------------------------------------------
ctkCheckableHeaderViewPrivate(ctkCheckableHeaderView & object)87 ctkCheckableHeaderViewPrivate::ctkCheckableHeaderViewPrivate(ctkCheckableHeaderView& object)
88   :q_ptr(&object)
89 {
90   this->CheckableModelHelper = 0;
91   this->Pressed = -1;
92   this->CheckBoxPixmaps = 0;
93   this->HeaderIsUpdating = false;
94 }
95 
96 //-----------------------------------------------------------------------------
~ctkCheckableHeaderViewPrivate()97 ctkCheckableHeaderViewPrivate::~ctkCheckableHeaderViewPrivate()
98 {
99   if (this->CheckBoxPixmaps)
100     {
101     delete this->CheckBoxPixmaps;
102     this->CheckBoxPixmaps = 0;
103     }
104   if (this->CheckableModelHelper)
105     {
106     delete this->CheckableModelHelper;
107     this->CheckableModelHelper = 0;
108     }
109 }
110 
111 //----------------------------------------------------------------------------
init()112 void ctkCheckableHeaderViewPrivate::init()
113 {
114   Q_Q(ctkCheckableHeaderView);
115   this->CheckBoxPixmaps = new ctkCheckBoxPixmaps(q);
116   this->CheckableModelHelper = new ctkCheckableModelHelper(q->orientation(), q);
117 }
118 
119 //----------------------------------------------------------------------------
ctkCheckableHeaderView(Qt::Orientation orient,QWidget * widgetParent)120 ctkCheckableHeaderView::ctkCheckableHeaderView(
121   Qt::Orientation orient, QWidget *widgetParent)
122   : QHeaderView(orient, widgetParent)
123   , d_ptr(new ctkCheckableHeaderViewPrivate(*this))
124 {
125   Q_D(ctkCheckableHeaderView);
126   d->init();
127   // TODO: doesn't support reparenting here.
128   if(widgetParent)
129     {
130     // Listen for focus change events.
131     widgetParent->installEventFilter(this);
132     }
133 }
134 
135 //-----------------------------------------------------------------------------
~ctkCheckableHeaderView()136 ctkCheckableHeaderView::~ctkCheckableHeaderView()
137 {
138 }
139 
140 //-----------------------------------------------------------------------------
checkState(int section) const141 Qt::CheckState ctkCheckableHeaderView::checkState(int section)const
142 {
143   Q_D(const ctkCheckableHeaderView);
144   return d->CheckableModelHelper->headerCheckState(section);
145 }
146 
147 //-----------------------------------------------------------------------------
checkState(int section,Qt::CheckState & checkState) const148 bool ctkCheckableHeaderView::checkState(int section, Qt::CheckState& checkState)const
149 {
150   Q_D(const ctkCheckableHeaderView);
151   return d->CheckableModelHelper->headerCheckState(section, checkState);
152 }
153 
154 //-----------------------------------------------------------------------------
setCheckState(int section,Qt::CheckState checkState)155 void ctkCheckableHeaderView::setCheckState(int section, Qt::CheckState checkState)
156 {
157   Q_D(ctkCheckableHeaderView);
158   d->CheckableModelHelper->setHeaderCheckState(section, checkState);
159 }
160 
161 //-----------------------------------------------------------------------------
checkableModelHelper() const162 ctkCheckableModelHelper* ctkCheckableHeaderView::checkableModelHelper()const
163 {
164   Q_D(const ctkCheckableHeaderView);
165   return d->CheckableModelHelper;
166 }
167 
168 //-----------------------------------------------------------------------------
eventFilter(QObject *,QEvent * e)169 bool ctkCheckableHeaderView::eventFilter(QObject *, QEvent *e)
170 {
171   if(e->type() != QEvent::FocusIn &&
172      e->type() != QEvent::FocusOut)
173     {
174     return false;
175     }
176   //this->updateHeaderPixmaps();
177   return false;
178 }
179 
180 //-----------------------------------------------------------------------------
setModel(QAbstractItemModel * newModel)181 void ctkCheckableHeaderView::setModel(QAbstractItemModel *newModel)
182 {
183   Q_D(ctkCheckableHeaderView);
184   QAbstractItemModel *current = this->model();
185   if (current)
186     {
187     this->disconnect(
188       current, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
189       this, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)));
190     this->disconnect(
191       current, SIGNAL(modelReset()),
192       this, SLOT(updateHeaderPixmaps()));
193     this->disconnect(
194       current, SIGNAL(columnsInserted(QModelIndex,int,int)),
195       this, SLOT(onHeaderSectionInserted()));
196     this->disconnect(
197       current, SIGNAL(rowsInserted(QModelIndex,int,int)),
198       this, SLOT(onHeaderSectionInserted()));
199     }
200   this->QHeaderView::setModel(newModel);
201   d->CheckableModelHelper->setModel(newModel);
202   if(newModel)
203     {
204     this->connect(
205       newModel, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
206       this, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)));
207     this->connect(
208       newModel, SIGNAL(modelReset()),
209       this, SLOT(updateHeaderPixmaps()));
210     if(this->orientation() == Qt::Horizontal)
211       {
212       this->connect(
213         newModel, SIGNAL(columnsInserted(QModelIndex,int,int)),
214         this, SLOT(onHeaderSectionInserted()));
215       }
216     else
217       {
218       this->connect(
219         newModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
220         this, SLOT(onHeaderSectionInserted()));
221       }
222     }
223   this->updateHeaderPixmaps();
224 }
225 
226 //-----------------------------------------------------------------------------
setRootIndex(const QModelIndex & index)227 void ctkCheckableHeaderView::setRootIndex(const QModelIndex &index)
228 {
229   Q_D(ctkCheckableHeaderView);
230   this->QHeaderView::setRootIndex(index);
231   d->CheckableModelHelper->setRootIndex(index);
232 }
233 
234 //-----------------------------------------------------------------------------
onHeaderDataChanged(Qt::Orientation orient,int firstSection,int lastSection)235 void ctkCheckableHeaderView::onHeaderDataChanged(Qt::Orientation orient,
236                                               int firstSection,
237                                               int lastSection)
238 {
239   if(orient != this->orientation())
240     {
241     return;
242     }
243   // update pixmap
244   this->updateHeaderPixmaps(firstSection, lastSection);
245 }
246 
247 //-----------------------------------------------------------------------------
updateHeaderPixmaps(int firstSection,int lastSection)248 void ctkCheckableHeaderView::updateHeaderPixmaps(int firstSection, int lastSection)
249 {
250   Q_D(ctkCheckableHeaderView);
251   QAbstractItemModel *current = this->model();
252   if(d->HeaderIsUpdating || !current)
253     {
254     return;
255     }
256   d->HeaderIsUpdating = true;
257 
258   firstSection = qBound(0, firstSection, this->count() -1);
259   lastSection = qBound(0, lastSection, this->count() -1);
260 
261   bool active = true;
262   if(this->parentWidget())
263     {
264     active = this->parentWidget()->hasFocus();
265     }
266   for(int i = firstSection; i <= lastSection; i++)
267     {
268     QVariant decoration;
269     Qt::CheckState checkState;
270     if (d->CheckableModelHelper->headerCheckState(i, checkState))
271       {
272       decoration = d->CheckBoxPixmaps->pixmap(checkState, active);
273       }
274     current->setHeaderData(i, this->orientation(), decoration,
275                            Qt::DecorationRole);
276     }
277   d->HeaderIsUpdating = false;
278 }
279 
280 //-----------------------------------------------------------------------------
onHeaderSectionInserted()281 void ctkCheckableHeaderView::onHeaderSectionInserted()
282 {
283   this->updateHeaderPixmaps();
284 }
285 
286 //-----------------------------------------------------------------------------
mousePressEvent(QMouseEvent * e)287 void ctkCheckableHeaderView::mousePressEvent(QMouseEvent *e)
288 {
289   Q_D(ctkCheckableHeaderView);
290   if (e->button() != Qt::LeftButton ||
291       d->Pressed >= 0)
292     {
293     d->Pressed = -1;
294     this->QHeaderView::mousePressEvent(e);
295     return;
296     }
297   d->Pressed = -1;
298   //check if the check box is pressed
299   int pos = this->orientation() == Qt::Horizontal ? e->x() : e->y();
300   int section = this->logicalIndexAt(pos);
301   if (d->CheckableModelHelper->isHeaderCheckable(section) &&
302       this->isPointInCheckBox(section, e->pos()))
303     {
304     d->Pressed = section;
305     }
306   else
307     {
308     this->QHeaderView::mousePressEvent(e);
309     }
310 }
311 
312 //-----------------------------------------------------------------------------
mouseReleaseEvent(QMouseEvent * e)313 void ctkCheckableHeaderView::mouseReleaseEvent(QMouseEvent *e)
314 {
315   Q_D(ctkCheckableHeaderView);
316   if (e->button() != Qt::LeftButton ||
317       d->Pressed < 0)
318     {
319     d->Pressed = -1;
320     this->QHeaderView::mouseReleaseEvent(e);
321     return;
322     }
323   //check if the check box is pressed
324   int pos = this->orientation() == Qt::Horizontal ? e->x() : e->y();
325   int section = this->logicalIndexAt(pos);
326   if (section == d->Pressed &&
327       this->isPointInCheckBox(section, e->pos()))
328     {
329     d->Pressed = -1;
330     d->CheckableModelHelper->toggleHeaderCheckState(section);
331     }
332   this->QHeaderView::mousePressEvent(e);
333 }
334 
335 //-----------------------------------------------------------------------------
isPointInCheckBox(int section,QPoint pos) const336 bool ctkCheckableHeaderView::isPointInCheckBox(int section, QPoint pos)const
337 {
338   QRect sectionRect = this->orientation() == Qt::Horizontal ?
339     QRect(this->sectionPosition(section), 0,
340           this->sectionSize(section), this->height()):
341     QRect(0, this->sectionPosition(section),
342           this->width(), this->sectionSize(section));
343   QStyleOptionHeader opt;
344   this->initStyleOption(&opt);
345   this->initStyleSectionOption(&opt, section, sectionRect);
346   QRect headerLabelRect = this->style()->subElementRect(QStyle::SE_HeaderLabel, &opt, this);
347   // from qcommonstyle.cpp:1541
348   if (opt.icon.isNull())
349     {
350     return false;
351     }
352   QPixmap pixmap
353     = opt.icon.pixmap(this->style()->pixelMetric(QStyle::PM_SmallIconSize),
354                       (opt.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled);
355   QRect aligned = this->style()->alignedRect(opt.direction, QFlag(opt.iconAlignment),
356                               pixmap.size(), headerLabelRect);
357   QRect inter = aligned.intersected(headerLabelRect);
358   return inter.contains(pos);
359 }
360 
361 //-----------------------------------------------------------------------------
initStyleSectionOption(QStyleOptionHeader * option,int section,QRect rect) const362 void ctkCheckableHeaderView::initStyleSectionOption(QStyleOptionHeader *option, int section, QRect rect)const
363 {
364   // from qheaderview.cpp:paintsection
365   QStyle::State state = QStyle::State_None;
366   if (this->isEnabled())
367     {
368     state |= QStyle::State_Enabled;
369     }
370   if (this->window()->isActiveWindow())
371     {
372     state |= QStyle::State_Active;
373     }
374   if (this->isSortIndicatorShown() &&
375       this->sortIndicatorSection() == section)
376     {
377     option->sortIndicator = (this->sortIndicatorOrder() == Qt::AscendingOrder)
378       ? QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
379     }
380 
381   // setup the style option structure
382   QVariant textAlignment =
383     this->model()->headerData(section, this->orientation(),
384                               Qt::TextAlignmentRole);
385   option->rect = rect;
386   option->section = section;
387   option->state |= state;
388   option->textAlignment = Qt::Alignment(textAlignment.isValid()
389                                         ? Qt::Alignment(textAlignment.toInt())
390                                         : this->defaultAlignment());
391 
392   option->iconAlignment = Qt::AlignVCenter;
393   option->text = this->model()->headerData(section, this->orientation(),
394                                   Qt::DisplayRole).toString();
395   if (this->textElideMode() != Qt::ElideNone)
396     {
397     option->text = option->fontMetrics.elidedText(option->text, this->textElideMode() , rect.width() - 4);
398     }
399 
400   QVariant variant = this->model()->headerData(section, this->orientation(),
401                                           Qt::DecorationRole);
402   option->icon = qvariant_cast<QIcon>(variant);
403   if (option->icon.isNull())
404     {
405     option->icon = qvariant_cast<QPixmap>(variant);
406     }
407   QVariant foregroundBrush = this->model()->headerData(section, this->orientation(),
408                                                   Qt::ForegroundRole);
409   if (foregroundBrush.canConvert<QBrush>())
410     {
411     option->palette.setBrush(QPalette::ButtonText, qvariant_cast<QBrush>(foregroundBrush));
412     }
413 
414   //QPointF oldBO = painter->brushOrigin();
415   QVariant backgroundBrush = this->model()->headerData(section, this->orientation(),
416                                                   Qt::BackgroundRole);
417   if (backgroundBrush.canConvert<QBrush>())
418     {
419     option->palette.setBrush(QPalette::Button, qvariant_cast<QBrush>(backgroundBrush));
420     option->palette.setBrush(QPalette::Window, qvariant_cast<QBrush>(backgroundBrush));
421     //painter->setBrushOrigin(option->rect.topLeft());
422     }
423 
424   // the section position
425   int visual = this->visualIndex(section);
426   Q_ASSERT(visual != -1);
427   if (this->count() == 1)
428     {
429     option->position = QStyleOptionHeader::OnlyOneSection;
430     }
431   else if (visual == 0)
432     {
433     option->position = QStyleOptionHeader::Beginning;
434     }
435   else if (visual == this->count() - 1)
436     {
437     option->position = QStyleOptionHeader::End;
438     }
439   else
440     {
441     option->position = QStyleOptionHeader::Middle;
442     }
443   option->orientation = this->orientation();
444   /* the selected position
445   bool previousSelected = d->isSectionSelected(this->logicalIndex(visual - 1));
446   bool nextSelected =  d->isSectionSelected(this->logicalIndex(visual + 1));
447   if (previousSelected && nextSelected)
448     option->selectedPosition = QStyleOptionHeader::NextAndPreviousAreSelected;
449   else if (previousSelected)
450     option->selectedPosition = QStyleOptionHeader::PreviousIsSelected;
451   else if (nextSelected)
452     option->selectedPosition = QStyleOptionHeader::NextIsSelected;
453   else
454     option->selectedPosition = QStyleOptionHeader::NotAdjacent;
455   */
456 }
457 
458