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 // Qt includes
22 #include <QApplication>
23 #include <QDebug>
24 #include <QChildEvent>
25 #include <QMouseEvent>
26 #include <QStylePainter>
27 #include <QStyleOptionGroupBox>
28 #include <QStyle>
29
30 // CTK includes
31 #include "ctkCollapsibleGroupBox.h"
32
33 #if QT_VERSION >= 0x040600
34 #include "ctkProxyStyle.h"
35
36 //-----------------------------------------------------------------------------
37 class ctkCollapsibleGroupBoxStyle:public ctkProxyStyle
38 {
39 public:
40 typedef ctkProxyStyle Superclass;
ctkCollapsibleGroupBoxStyle(QStyle * style=0,QObject * parent=0)41 ctkCollapsibleGroupBoxStyle(QStyle* style = 0, QObject* parent =0)
42 : Superclass(style, parent)
43 {
44 }
drawPrimitive(PrimitiveElement pe,const QStyleOption * opt,QPainter * p,const QWidget * widget=0) const45 virtual void drawPrimitive(PrimitiveElement pe, const QStyleOption * opt, QPainter * p, const QWidget * widget = 0) const
46 {
47 if (pe == QStyle::PE_IndicatorCheckBox)
48 {
49 const ctkCollapsibleGroupBox* groupBox= qobject_cast<const ctkCollapsibleGroupBox*>(widget);
50 if (groupBox)
51 {
52 this->Superclass::drawPrimitive(groupBox->isChecked() ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight, opt, p, widget);
53 return;
54 }
55 }
56 this->Superclass::drawPrimitive(pe, opt, p, widget);
57 }
pixelMetric(PixelMetric metric,const QStyleOption * option,const QWidget * widget) const58 virtual int pixelMetric(PixelMetric metric, const QStyleOption * option, const QWidget * widget) const
59 {
60 if (metric == QStyle::PM_IndicatorHeight)
61 {
62 const ctkCollapsibleGroupBox* groupBox= qobject_cast<const ctkCollapsibleGroupBox*>(widget);
63 if (groupBox)
64 {
65 return groupBox->fontMetrics().height();
66 }
67 }
68 return this->Superclass::pixelMetric(metric, option, widget);
69 }
70 };
71 #endif
72
73 //-----------------------------------------------------------------------------
74 class ctkCollapsibleGroupBoxPrivate
75 {
76 Q_DECLARE_PUBLIC(ctkCollapsibleGroupBox);
77 protected:
78 ctkCollapsibleGroupBox* const q_ptr;
79 public:
80 ctkCollapsibleGroupBoxPrivate(ctkCollapsibleGroupBox& object);
81 void init();
82 void setChildVisibility(QWidget* childWidget);
83
84 /// Size of the widget for collapsing
85 QSize OldSize;
86 /// Maximum allowed height
87 int MaxHeight;
88 int CollapsedHeight;
89
90 /// We change the visibility of the chidren in setChildrenVisibility
91 /// and we track when the visibility is changed to force it back to possibly
92 /// force the child to be hidden. To prevent infinite loop we need to know
93 /// who is changing children's visibility.
94 bool ForcingVisibility;
95 /// Sometimes the creation of the widget is not done inside setVisible,
96 /// as we need to do special processing the first time the groupBox is
97 /// setVisible, we track its created state with the variable
98 bool IsStateCreated;
99
100 #if QT_VERSION >= 0x040600
101 /// Pointer to keep track of the proxy style
102 ctkCollapsibleGroupBoxStyle* GroupBoxStyle;
103 #endif
104 };
105
106 //-----------------------------------------------------------------------------
ctkCollapsibleGroupBoxPrivate(ctkCollapsibleGroupBox & object)107 ctkCollapsibleGroupBoxPrivate::ctkCollapsibleGroupBoxPrivate(
108 ctkCollapsibleGroupBox& object)
109 :q_ptr(&object)
110 {
111 this->ForcingVisibility = false;
112 this->IsStateCreated = false;
113 this->MaxHeight = 0;
114 this->CollapsedHeight = 14;
115 #if QT_VERSION >= 0x040600
116 this->GroupBoxStyle = 0;
117 #endif
118 }
119
120 //-----------------------------------------------------------------------------
init()121 void ctkCollapsibleGroupBoxPrivate::init()
122 {
123 Q_Q(ctkCollapsibleGroupBox);
124 q->setCheckable(true);
125 QObject::connect(q, SIGNAL(toggled(bool)), q, SLOT(expand(bool)));
126
127 this->MaxHeight = q->maximumHeight();
128 #if QT_VERSION >= 0x040600
129 QWidget* parent = q->parentWidget();
130 QStyle* parentStyle = (parent) ? parent->style() : qApp->style();
131 this->GroupBoxStyle = new ctkCollapsibleGroupBoxStyle(parentStyle, qApp);
132 q->setStyle(this->GroupBoxStyle);
133 this->GroupBoxStyle->ensureBaseStyle();
134 #else
135 this->setStyleSheet(
136 "ctkCollapsibleGroupBox::indicator:checked{"
137 "image: url(:/Icons/expand-up.png);}"
138 "ctkCollapsibleGroupBox::indicator:unchecked{"
139 "image: url(:/Icons/expand-down.png);}");
140 #endif
141 }
142 //-----------------------------------------------------------------------------
setChildVisibility(QWidget * childWidget)143 void ctkCollapsibleGroupBoxPrivate::setChildVisibility(QWidget* childWidget)
144 {
145 Q_Q(ctkCollapsibleGroupBox);
146 // Don't hide children while the widget is not yet created (before show() is
147 // called). If we hide them (but don't set ExplicitShowHide), they would be
148 // shown anyway when they will be created (because ExplicitShowHide is not set).
149 // If we set ExplicitShowHide, then calling setVisible(false) on them would
150 // be a no (because they are already hidden and ExplicitShowHide is set).
151 // So we don't hide/show the children until the widget is created.
152 if (!q->testAttribute(Qt::WA_WState_Created))
153 {
154 return;
155 }
156 this->ForcingVisibility = true;
157
158 bool visible= !q->collapsed();
159 // if the widget has been explicity hidden, then hide it.
160 if (childWidget->property("visibilityToParent").isValid()
161 && !childWidget->property("visibilityToParent").toBool())
162 {
163 visible = false;
164 }
165
166 // Setting Qt::WA_WState_Visible to true during child construction can have
167 // undesirable side effects.
168 if (childWidget->testAttribute(Qt::WA_WState_Created) ||
169 !visible)
170 {
171 childWidget->setVisible(visible);
172 }
173
174 // setVisible() has set the ExplicitShowHide flag, restore it as we don't want
175 // to make it like it was an explicit visible set because we want
176 // to allow any children to be explicitly hidden by the user.
177 if ((!childWidget->property("visibilityToParent").isValid() ||
178 childWidget->property("visibilityToParent").toBool()))
179 {
180 childWidget->setAttribute(Qt::WA_WState_ExplicitShowHide, false);
181 }
182 this->ForcingVisibility = false;
183 }
184
185 //-----------------------------------------------------------------------------
ctkCollapsibleGroupBox(QWidget * _parent)186 ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(QWidget* _parent)
187 :QGroupBox(_parent)
188 , d_ptr(new ctkCollapsibleGroupBoxPrivate(*this))
189 {
190 Q_D(ctkCollapsibleGroupBox);
191 d->init();
192 }
193
194 //-----------------------------------------------------------------------------
ctkCollapsibleGroupBox(const QString & title,QWidget * _parent)195 ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(const QString& title, QWidget* _parent)
196 :QGroupBox(title, _parent)
197 , d_ptr(new ctkCollapsibleGroupBoxPrivate(*this))
198 {
199 Q_D(ctkCollapsibleGroupBox);
200 d->init();
201 }
202
203 //-----------------------------------------------------------------------------
~ctkCollapsibleGroupBox()204 ctkCollapsibleGroupBox::~ctkCollapsibleGroupBox()
205 {
206
207 }
208
209 //-----------------------------------------------------------------------------
setCollapsedHeight(int heightInPixels)210 void ctkCollapsibleGroupBox::setCollapsedHeight(int heightInPixels)
211 {
212 Q_D(ctkCollapsibleGroupBox);
213 d->CollapsedHeight = heightInPixels;
214 }
215
216 //-----------------------------------------------------------------------------
collapsedHeight() const217 int ctkCollapsibleGroupBox::collapsedHeight()const
218 {
219 Q_D(const ctkCollapsibleGroupBox);
220 return d->CollapsedHeight;
221 }
222
223 //-----------------------------------------------------------------------------
expand(bool _expand)224 void ctkCollapsibleGroupBox::expand(bool _expand)
225 {
226 Q_D(ctkCollapsibleGroupBox);
227 if (!_expand)
228 {
229 d->OldSize = this->size();
230 }
231
232 // Update the visibility of all the children
233 // We can't use findChildren as it would return the grandchildren
234 foreach(QObject* childObject, this->children())
235 {
236 if (childObject->isWidgetType())
237 {
238 d->setChildVisibility(qobject_cast<QWidget*>(childObject));
239 }
240 }
241
242 if (_expand)
243 {
244 this->setMaximumHeight(d->MaxHeight);
245 this->resize(d->OldSize);
246 }
247 else
248 {
249 d->MaxHeight = this->maximumHeight();
250 QStyleOptionGroupBox option;
251 this->initStyleOption(&option);
252 QRect labelRect = this->style()->subControlRect(
253 QStyle::CC_GroupBox, &option, QStyle::SC_GroupBoxLabel, this);
254 this->setMaximumHeight(labelRect.height() + d->CollapsedHeight);
255 }
256 }
257
258 #if QT_VERSION < 0x040600
259 //-----------------------------------------------------------------------------
paintEvent(QPaintEvent * e)260 void ctkCollapsibleGroupBox::paintEvent(QPaintEvent* e)
261 {
262 this->QGroupBox::paintEvent(e);
263
264 QStylePainter paint(this);
265 QStyleOptionGroupBox option;
266 initStyleOption(&option);
267 option.activeSubControls &= ~QStyle::SC_GroupBoxCheckBox;
268 paint.drawComplexControl(QStyle::CC_GroupBox, option);
269
270 }
271
272 //-----------------------------------------------------------------------------
mousePressEvent(QMouseEvent * event)273 void ctkCollapsibleGroupBox::mousePressEvent(QMouseEvent *event)
274 {
275 if (event->button() != Qt::LeftButton) {
276 event->ignore();
277 return;
278 }
279 // no animation
280 }
281
282 //-----------------------------------------------------------------------------
mouseReleaseEvent(QMouseEvent * event)283 void ctkCollapsibleGroupBox::mouseReleaseEvent(QMouseEvent *event)
284 {
285 if (event->button() != Qt::LeftButton) {
286 event->ignore();
287 return;
288 }
289
290 QStyleOptionGroupBox box;
291 initStyleOption(&box);
292 box.activeSubControls &= !QStyle::SC_GroupBoxCheckBox;
293 QStyle::SubControl released = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
294 event->pos(), this);
295 bool toggle = this->isCheckable() && (released == QStyle::SC_GroupBoxLabel
296 || released == QStyle::SC_GroupBoxCheckBox);
297 if (toggle)
298 {
299 this->setChecked(!this->isChecked());
300 }
301 }
302
303 #endif
304
305 //-----------------------------------------------------------------------------
childEvent(QChildEvent * c)306 void ctkCollapsibleGroupBox::childEvent(QChildEvent* c)
307 {
308 Q_D(ctkCollapsibleGroupBox);
309 QObject* child = c->child();
310 if (c && c->type() == QEvent::ChildAdded &&
311 child && child->isWidgetType())
312 {
313 QWidget *childWidget = qobject_cast<QWidget*>(c->child());
314 // Handle the case where the child has already it's visibility set before
315 // being added to the widget
316 if (childWidget->testAttribute(Qt::WA_WState_ExplicitShowHide) &&
317 childWidget->testAttribute(Qt::WA_WState_Hidden))
318 {
319 // if the widget has explicitly set to hidden, then mark it as such
320 childWidget->setProperty("visibilityToParent", false);
321 }
322 // We want to catch all the child's Show/Hide events.
323 child->installEventFilter(this);
324 // If the child is added while ctkCollapsibleButton is collapsed, then we
325 // need to hide the child.
326 d->setChildVisibility(childWidget);
327 }
328 this->QGroupBox::childEvent(c);
329 }
330
331 //-----------------------------------------------------------------------------
setVisible(bool show)332 void ctkCollapsibleGroupBox::setVisible(bool show)
333 {
334 Q_D(ctkCollapsibleGroupBox);
335 // calling QWidget::setVisible() on ctkCollapsibleGroupBox will eventually
336 // call QWidget::showChildren() or hideChildren() which will generate
337 // ShowToParent/HideToParent events but we want to ignore that case in
338 // eventFilter().
339 d->ForcingVisibility = true;
340 this->QGroupBox::setVisible(show);
341 d->ForcingVisibility = false;
342 // We have been ignoring setChildVisibility() while the collapsible button
343 // is not yet created, now that it is created, ensure that the children
344 // are correctly shown/hidden depending on their explicit visibility and
345 // the collapsed property of the button.
346 if (!d->IsStateCreated && this->testAttribute(Qt::WA_WState_Created))
347 {
348 d->IsStateCreated = true;
349 foreach(QObject* child, this->children())
350 {
351 QWidget* childWidget = qobject_cast<QWidget*>(child);
352 if (childWidget)
353 {
354 d->setChildVisibility(childWidget);
355 }
356 }
357 }
358 }
359
360 //-----------------------------------------------------------------------------
eventFilter(QObject * child,QEvent * e)361 bool ctkCollapsibleGroupBox::eventFilter(QObject* child, QEvent* e)
362 {
363 Q_D(ctkCollapsibleGroupBox);
364 Q_ASSERT(child && e);
365 // Make sure the Show/QHide events are not generated by one of our
366 // ctkCollapsibleButton function.
367 if (d->ForcingVisibility)
368 {
369 return false;
370 }
371 // When we are here, it's because somewhere (not in ctkCollapsibleButton),
372 // someone explicitly called setVisible() on a child widget.
373 // If the collapsible button is collapsed/closed, then even if someone
374 // request the widget to be visible, we force it back to be hidden because
375 // they meant to be hidden to its parent, the collapsible button. However the
376 // child will later be shown when the button will be expanded/opened.
377 // On the other hand, if the user explicitly hide the child when the button
378 // is collapsed/closed, then we want to keep it hidden next time the
379 // collapsible button is expanded/opened.
380 if (e->type() == QEvent::ShowToParent)
381 {
382 child->setProperty("visibilityToParent", true);
383 Q_ASSERT(qobject_cast<QWidget*>(child));
384 // force the widget to be hidden if the button is collapsed.
385 d->setChildVisibility(qobject_cast<QWidget*>(child));
386 }
387 else if(e->type() == QEvent::HideToParent)
388 {
389 // we don't need to force the widget to be visible here.
390 child->setProperty("visibilityToParent", false);
391 }
392 return this->QGroupBox::eventFilter(child, e);
393 }
394