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