Bläddra i källkod

Fix collapsible button issue when a child is hidden...

... when the button is collapsed.
If a ctkCollapsibleButton is collapsed and you call setVisible(false) on
a child, then it was not taken into account. Now it is.
Julien Finet 14 år sedan
förälder
incheckning
95a410ab9b

+ 2 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -10,6 +10,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cxx
   ctkCheckableHeaderViewTest2.cpp
   ctkCheckablePushButtonTest1.cpp
   ctkCollapsibleButtonTest1.cpp
+  ctkCollapsibleButtonTest2.cpp
   ctkCollapsibleGroupBoxTest1.cpp
   ctkColorDialogTest1.cpp
   ctkColorDialogTest2.cpp
@@ -97,6 +98,7 @@ SIMPLE_TEST( ctkCheckableHeaderViewTest1 )
 SIMPLE_TEST( ctkCheckableHeaderViewTest2 )
 SIMPLE_TEST( ctkCheckablePushButtonTest1 )
 SIMPLE_TEST( ctkCollapsibleButtonTest1 )
+SIMPLE_TEST( ctkCollapsibleButtonTest2 )
 SIMPLE_TEST( ctkCollapsibleGroupBoxTest1 )
 SIMPLE_TEST( ctkColorDialogTest1 )
 SIMPLE_TEST( ctkColorDialogTest2 )

+ 169 - 0
Libs/Widgets/Testing/Cpp/ctkCollapsibleButtonTest2.cpp

@@ -0,0 +1,169 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.commontk.org/LICENSE
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QDoubleSpinBox>
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QStyle>
+#include <QTimer>
+
+// CTK includes
+#include "ctkCollapsibleButton.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkCollapsibleButtonTest2(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  ctkCollapsibleButton collapsibleButton;
+  collapsibleButton.setText("Button");
+
+  QLabel* label0 = new QLabel("should be invisible", &collapsibleButton);
+  QLabel* label1 = new QLabel("should be invisible", &collapsibleButton);
+  QLabel* label2 = new QLabel("should be invisible", &collapsibleButton);
+  QLabel* label3 = new QLabel("should be visible", &collapsibleButton);
+  QLabel* label4 = new QLabel("should be visible", &collapsibleButton);
+  QLabel* label5 = new QLabel("should be visible", &collapsibleButton);
+  
+  QVBoxLayout *vbox = new QVBoxLayout;
+  vbox->addWidget(label0);
+  vbox->addWidget(label1);
+  vbox->addWidget(label2);
+  vbox->addWidget(label3);
+  vbox->addWidget(label4);
+  vbox->addWidget(label5);
+  collapsibleButton.setLayout(vbox);
+
+  label0->setVisible(false);
+
+  collapsibleButton.show();
+
+  label1->setVisible(false);
+  
+  if (label0->isVisible() ||
+      label1->isVisible() ||
+      !label2->isVisible() ||
+      !label3->isVisible() ||
+      !label4->isVisible() ||
+      !label5->isVisible())
+    {
+    std::cout << "Wrong child visibility: "
+      << label0->isVisible()  << " "
+      << label1->isVisible()  << " "
+      << label2->isVisible() << " "
+      << label3->isVisible() << " "
+      << label4->isVisible() << " "
+      << label5->isVisible() << std::endl;
+    return EXIT_FAILURE;
+    }
+  
+  collapsibleButton.setCollapsed(true);
+  
+  if (label0->isVisible() ||
+      label1->isVisible() ||
+      label2->isVisible() ||
+      label3->isVisible() ||
+      label4->isVisible() ||
+      label5->isVisible())
+    {
+    std::cout << "Wrong child visibility: "
+      << label0->isVisible()  << " "
+      << label1->isVisible()  << " "
+      << label2->isVisible() << " "
+      << label3->isVisible() << " "
+      << label4->isVisible() << " "
+      << label5->isVisible() << std::endl;
+    return EXIT_FAILURE;
+    }
+  
+  label2->setVisible(false);
+  label3->setVisible(true);
+  label4->setVisible(false);
+  label5->setVisible(false);
+  label5->setVisible(true);
+  
+  if (label0->isVisible() ||
+      label1->isVisible() ||
+      label2->isVisible() ||
+      label3->isVisible() ||
+      label4->isVisible() ||
+      label5->isVisible())
+    {
+    std::cout << "Wrong child visibility: "
+      << label0->isVisible()  << " "
+      << label1->isVisible()  << " "
+      << label2->isVisible() << " "
+      << label3->isVisible() << " "
+      << label4->isVisible() << " "
+      << label5->isVisible() << std::endl;
+    return EXIT_FAILURE;
+    }
+  
+  collapsibleButton.setCollapsed(false);
+
+  if (label0->isVisible() ||
+      label1->isVisible() ||
+      label2->isVisible() ||
+      !label3->isVisible() ||
+      label4->isVisible() ||
+      !label5->isVisible())
+    {
+    std::cout << "Wrong child visibility: "
+      << label0->isVisible()  << " "
+      << label1->isVisible()  << " "
+      << label2->isVisible() << " "
+      << label3->isVisible() << " "
+      << label4->isVisible() << " "
+      << label5->isVisible() << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  label4->setVisible(true);
+
+  if (label0->isVisible() ||
+      label1->isVisible() ||
+      label2->isVisible() ||
+      !label3->isVisible() ||
+      !label4->isVisible() ||
+      !label5->isVisible())
+    {
+    std::cout << "Wrong child visibility: "
+      << label0->isVisible()  << " "
+      << label1->isVisible()  << " "
+      << label2->isVisible() << " "
+      << label3->isVisible() << " "
+      << label4->isVisible() << " "
+      << label5->isVisible() << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}

+ 90 - 22
Libs/Widgets/ctkCollapsibleButton.cpp

@@ -42,7 +42,8 @@ protected:
 public:
   ctkCollapsibleButtonPrivate(ctkCollapsibleButton& object);
   void init();
-
+  void setChildVisibility(QWidget* childWidget);
+  
   bool     Collapsed;
 
   // Contents frame
@@ -54,6 +55,7 @@ public:
   int      CollapsedHeight;
   bool     ExclusiveMouseOver;
   bool     LookOffWhenChecked;
+  bool     ForcingVisibility;
 
   int      MaximumHeight;  // use carefully
 
@@ -105,6 +107,27 @@ void ctkCollapsibleButtonPrivate::init()
 }
 
 //-----------------------------------------------------------------------------
+void ctkCollapsibleButtonPrivate::setChildVisibility(QWidget* childWidget)
+{
+  Q_Q(ctkCollapsibleButton);
+  this->ForcingVisibility = true;
+ 
+  bool visible= !this->Collapsed &&
+    (childWidget->property("visibilityToParent").isValid() ?
+       childWidget->property("visibilityToParent").toBool() : true);
+
+  childWidget->setVisible(visible);
+
+  // restore the flag as we don't want to make it like it is an explicit visible set.
+  if (!childWidget->property("visibilityToParent").isValid() ||
+      childWidget->property("visibilityToParent").toBool())
+    {
+    childWidget->setAttribute(Qt::WA_WState_ExplicitShowHide, false);
+    }
+  this->ForcingVisibility = false;
+}
+
+//-----------------------------------------------------------------------------
 void ctkCollapsibleButton::initStyleOption(QStyleOptionButton* option)const
 {
   Q_D(const ctkCollapsibleButton);
@@ -203,18 +226,18 @@ void ctkCollapsibleButton::onToggled(bool checked)
 }
 
 //-----------------------------------------------------------------------------
-void ctkCollapsibleButton::collapse(bool c)
+void ctkCollapsibleButton::collapse(bool collapsed)
 {
   Q_D(ctkCollapsibleButton);
-  if (c == d->Collapsed)
+  if (collapsed == d->Collapsed)
     {
     return;
     }
 
-  d->Collapsed = c;
+  d->Collapsed = collapsed;
 
   // we do that here as setVisible calls will correctly refresh the widget
-  if (c)
+  if (collapsed)
     {
     d->MaximumHeight = this->maximumHeight();
     this->setMaximumHeight(this->sizeHint().height());
@@ -227,17 +250,12 @@ void ctkCollapsibleButton::collapse(bool c)
     this->updateGeometry();
     }
 
-  QObjectList childList = this->children();
-  for (int i = 0; i < childList.size(); ++i)
+  // update the visibility of all the children
+  foreach(QWidget* child, this->findChildren<QWidget*>())
     {
-    QObject *o = childList.at(i);
-    if (!o->isWidgetType())
-      {
-      continue;
-      }
-    QWidget *w = static_cast<QWidget *>(o);
-    w->setVisible(!c);
+    d->setChildVisibility(child);
     }
+
   // this might be too many updates...
   QWidget* _parent = this->parentWidget();
   if (!d->Collapsed && (!_parent || !_parent->layout()))
@@ -250,7 +268,7 @@ void ctkCollapsibleButton::collapse(bool c)
     }
   //this->update(QRect(QPoint(0,0), this->sizeHint()));
   //this->repaint(QRect(QPoint(0,0), this->sizeHint()));
-  emit contentsCollapsed(c);
+  emit contentsCollapsed(collapsed);
 }
 
 //-----------------------------------------------------------------------------
@@ -610,15 +628,65 @@ bool ctkCollapsibleButton::hitButton(const QPoint & _pos)const
 void ctkCollapsibleButton::childEvent(QChildEvent* c)
 {
   Q_D(ctkCollapsibleButton);
-  if(c && c->type() == QEvent::ChildAdded)
+  if (c && c->type() == QEvent::ChildAdded &&
+      c->child() && c->child()->isWidgetType())
     {
-    if (c->child() && c->child()->isWidgetType())
-      {
-      QWidget *w = static_cast<QWidget*>(c->child());
-      w->setVisible(!d->Collapsed);
-      }
+    // We want to catch all the child's Show/Hide events.
+    c->child()->installEventFilter(this);
+    // If the child is added while ctkCollapsibleButton is collapsed, then we
+    // need to hide the child.
+    QWidget *w = static_cast<QWidget*>(c->child());
+    d->setChildVisibility(w);
     }
-  QWidget::childEvent(c);
+  this->QWidget::childEvent(c);
+}
+
+//-----------------------------------------------------------------------------
+void ctkCollapsibleButton::setVisible(bool show)
+{
+  Q_D(ctkCollapsibleButton);
+  // calling QWidget::setVisible() on ctkCollapsibleButton will eventually
+  // call QWidget::showChildren() or hideChildren() which will generate
+  // ShowToParent/HideToParent events but we want to ignore that case in
+  // eventFilter().
+  d->ForcingVisibility = true;
+  this->QWidget::setVisible(show);
+  d->ForcingVisibility = false;
+}
+
+//-----------------------------------------------------------------------------
+bool ctkCollapsibleButton::eventFilter(QObject* child, QEvent* e)
+{
+  Q_D(ctkCollapsibleButton);
+  Q_ASSERT(child && e);
+  // Make sure the Show/QHide events are not generated by one of our
+  // ctkCollapsibleButton function.
+  if (d->ForcingVisibility)
+    {
+    return false;
+    }
+  // When we are here, it's because somewhere (not in ctkCollapsibleButton),
+  // someone explicitly called setVisible() on a child widget.
+  // If the collapsible button is collapsed/closed, then even if someone
+  // request the widget to be visible, we force it back to be hidden because
+  // they meant to be hidden to its parent, the collapsible button. However the
+  // child will later be shown when the button will be expanded/opened.
+  // On the other hand, if the user explicitly hide the child when the button
+  // is collapsed/closed, then we want to keep it hidden next time the
+  // collapsible button is expanded/opened.
+  if (e->type() == QEvent::ShowToParent)
+    {
+    child->setProperty("visibilityToParent", true);
+    Q_ASSERT(qobject_cast<QWidget*>(child));
+    // force the widget to be hidden if the button is collapsed.
+    d->setChildVisibility(qobject_cast<QWidget*>(child));
+    }
+  else if(e->type() == QEvent::HideToParent)
+    {
+    // we don't need to force the widget to be visible here.
+    child->setProperty("visibilityToParent", false);
+    }
+  return this->QWidget::eventFilter(child, e);
 }
 
 //-----------------------------------------------------------------------------

+ 11 - 3
Libs/Widgets/ctkCollapsibleButton.h

@@ -34,12 +34,12 @@ class QStyleOptionButton;
 
 /// A collapsible button that shows/hides its children depending on its
 /// checked/collapsed property.
-/// Warning: As ctkCollapsibleButton forces the Visiblity of its children to
+/// Warning: <old behavior> As ctkCollapsibleButton forces the Visiblity of its children to
 /// true when it get expanded, any child Visibility property is lost. All the widgets
 /// will then be visible. To avoid this behavior, use an intermediate widget that
 /// contains all the children (they would become grandchildren and their Visibility property
-/// will remain relative to their parent, ctkCollapsibleButton's unique child widget.
-/// The user QAbstractButton::icon is not visible (it's placeholder is used to display the
+/// will remain relative to their parent, ctkCollapsibleButton's unique child widget.</old behavior>
+/// Note: The user QAbstractButton::icon is not visible (it's placeholder is used to display the
 /// collapsible state
 class CTK_WIDGETS_EXPORT ctkCollapsibleButton : public QAbstractButton
 {
@@ -118,6 +118,14 @@ public:
   /// change
   virtual bool event(QEvent* event);
 
+  /// Reimplmented for internal reasons
+  /// Catch when a child widget's visibility is externally changed
+  virtual bool eventFilter(QObject* child, QEvent* e);
+  
+  /// Reimplemented for internal reasons
+  /// Don't process Show/Hide events of children when it is
+  /// ctkCollapsibleButton that generate them.
+  virtual void setVisible(bool);
 signals:
   /// 
   /// Signal emitted when the widget is collapsed or expanded.