Browse Source

Support hidden children in ctkCollapsibleGroupBox

An externally hidden child should not turn visible when the groupbox is
expanded.
Julien Finet 13 years ago
parent
commit
4c6691e442

+ 29 - 1
Libs/Widgets/Testing/Cpp/ctkCollapsibleGroupBoxTest1.cpp

@@ -20,6 +20,7 @@
 
 // Qt includes
 #include <QApplication>
+#include <QCheckBox>
 #include <QRadioButton>
 #include <QTimer>
 #include <QVBoxLayout>
@@ -41,6 +42,8 @@ int ctkCollapsibleGroupBoxTest1(int argc, char * argv [] )
   QRadioButton *radio1 = new QRadioButton(QObject::tr("&Radio button 1"));
   QRadioButton *radio2 = new QRadioButton(QObject::tr("R&adio button 2"));
   QRadioButton *radio3 = new QRadioButton(QObject::tr("Ra&dio button 3"));
+  ctkCollapsibleGroupBox* hiddenGroupBox = new ctkCollapsibleGroupBox;
+  hiddenGroupBox->setTitle("Advanced...");
 
   radio1->setChecked(true);
 
@@ -48,9 +51,19 @@ int ctkCollapsibleGroupBoxTest1(int argc, char * argv [] )
   vbox->addWidget(radio1);
   vbox->addWidget(radio2);
   vbox->addWidget(radio3);
+  vbox->addWidget(hiddenGroupBox);
   vbox->addStretch(1);
   groupBox->setLayout(vbox);
 
+
+  QCheckBox* checkBox = new QCheckBox("Check box");
+  QVBoxLayout *vbox2 = new QVBoxLayout;
+  vbox2->addWidget(checkBox);
+  hiddenGroupBox->setLayout(vbox2);
+  hiddenGroupBox->setCollapsed(true);
+
+  hiddenGroupBox->setVisible(false);
+
   QVBoxLayout* topLevelVBox = new QVBoxLayout;
   topLevelVBox->addWidget(groupBox);
   topLevel.setLayout(topLevelVBox);
@@ -70,7 +83,7 @@ int ctkCollapsibleGroupBoxTest1(int argc, char * argv [] )
     std::cerr<< "ctkCollapsibleGroupBox::setCollapsed failed." << std::endl;
     return EXIT_FAILURE;
     }
-    
+
   if (radio1->isVisible())
     {
     std::cerr << "ctkCollapsibleGroupBox::setChecked failed. "
@@ -78,6 +91,14 @@ int ctkCollapsibleGroupBoxTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
+  hiddenGroupBox->setVisible(true);
+  if (hiddenGroupBox->isVisible())
+    {
+    std::cerr << "Nested widget in ctkCollapsibleGroupBox failed. "
+              << "Child is visible" << std::endl;
+    return EXIT_FAILURE;
+    }
+
   groupBox->setChecked(true);
 
   if (groupBox->collapsed() != false)
@@ -86,6 +107,13 @@ int ctkCollapsibleGroupBoxTest1(int argc, char * argv [] )
     return EXIT_FAILURE;
     }
 
+  if (!hiddenGroupBox->isVisible())
+    {
+    std::cerr << "Nested widget in ctkCollapsibleGroupBox failed. "
+              << "Child is not visible" << std::endl;
+    return EXIT_FAILURE;
+    }
+
   if (argc < 2 || QString(argv[1]) != "-I" )
     {
     QTimer::singleShot(200, &app, SLOT(quit()));

+ 2 - 1
Libs/Widgets/ctkCollapsibleButton.cpp

@@ -277,7 +277,8 @@ void ctkCollapsibleButton::collapse(bool collapsed)
     this->updateGeometry();
     }
 
-  // update the visibility of all the children
+  // Update the visibility of all the children
+  // We can't use findChildren as it would return the grandchildren
   foreach(QObject* child, this->children())
     {
     QWidget* childWidget = qobject_cast<QWidget*>(child);

+ 1 - 1
Libs/Widgets/ctkCollapsibleButton.h

@@ -118,7 +118,7 @@ public:
   /// change
   virtual bool event(QEvent* event);
 
-  /// Reimplmented for internal reasons
+  /// Reimplemented for internal reasons
   /// Catch when a child widget's visibility is externally changed
   virtual bool eventFilter(QObject* child, QEvent* e);
   

+ 184 - 60
Libs/Widgets/ctkCollapsibleGroupBox.cpp

@@ -55,35 +55,54 @@ class ctkCollapsibleGroupBoxStyle:public QProxyStyle
   
 #endif
 
-//-----------------------------------------------------------------------------
-ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(QWidget* _parent)
-  :QGroupBox(_parent)
-{
-  this->init();
-}
 
 //-----------------------------------------------------------------------------
-ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(const QString& title, QWidget* _parent)
-  :QGroupBox(title, _parent)
+class ctkCollapsibleGroupBoxPrivate
 {
-  this->init();
-}
+  Q_DECLARE_PUBLIC(ctkCollapsibleGroupBox);
+protected:
+  ctkCollapsibleGroupBox* const q_ptr;
+public:
+  ctkCollapsibleGroupBoxPrivate(ctkCollapsibleGroupBox& object);
+  void init();
+  void setChildVisibility(QWidget* childWidget);
+
+  /// Size of the widget for collapsing
+  QSize OldSize;
+  /// Maximum allowed height
+  int   MaxHeight;
+
+  /// We change the visibility of the chidren in setChildrenVisibility
+  /// and we track when the visibility is changed to force it back to possibly
+  /// force the child to be hidden. To prevent infinite loop we need to know
+  /// who is changing children's visibility.
+  bool     ForcingVisibility;
+  /// Sometimes the creation of the widget is not done inside setVisible,
+  /// as we need to do special processing the first time the button is
+  /// setVisible, we track its created state with the variable
+  bool     IsStateCreated;
+};
 
 //-----------------------------------------------------------------------------
-ctkCollapsibleGroupBox::~ctkCollapsibleGroupBox()
+ctkCollapsibleGroupBoxPrivate::ctkCollapsibleGroupBoxPrivate(
+  ctkCollapsibleGroupBox& object)
+  :q_ptr(&object)
 {
-
+  this->ForcingVisibility = false;
+  this->IsStateCreated = false;
+  this->MaxHeight = 0;
 }
 
 //-----------------------------------------------------------------------------
-void ctkCollapsibleGroupBox::init()
+void ctkCollapsibleGroupBoxPrivate::init()
 {
-  this->setCheckable(true);
-  connect(this, SIGNAL(toggled(bool)), this, SLOT(expand(bool)));
+  Q_Q(ctkCollapsibleGroupBox);
+  q->setCheckable(true);
+  QObject::connect(q, SIGNAL(toggled(bool)), q, SLOT(expand(bool)));
 
-  this->MaxHeight = this->maximumHeight();
+  this->MaxHeight = q->maximumHeight();
 #if QT_VERSION >= 0x040600
-  this->setStyle(new ctkCollapsibleGroupBoxStyle);
+  q->setStyle(new ctkCollapsibleGroupBoxStyle);
 #else
   this->setStyleSheet(
     "ctkCollapsibleGroupBox::indicator:checked{"
@@ -92,55 +111,98 @@ void ctkCollapsibleGroupBox::init()
     "image: url(:/Icons/expand-down.png);}");
 #endif
 }
+//-----------------------------------------------------------------------------
+void ctkCollapsibleGroupBoxPrivate::setChildVisibility(QWidget* childWidget)
+{
+  Q_Q(ctkCollapsibleGroupBox);
+  // Don't hide children while the widget is not yet created (before show() is
+  // called). If we hide them (but don't set ExplicitShowHide), they would be
+  // shown anyway when they will be created (because ExplicitShowHide is not set).
+  // If we set ExplicitShowHide, then calling setVisible(false) on them would
+  // be a no (because they are already hidden and ExplicitShowHide is set).
+  // So we don't hide/show the children until the widget is created.
+  if (!q->testAttribute(Qt::WA_WState_Created))
+    {
+    return;
+    }
+  this->ForcingVisibility = true;
+
+  bool visible= !q->collapsed();
+  // if the widget has been explicity hidden, then hide it.
+  if (childWidget->property("visibilityToParent").isValid()
+      && !childWidget->property("visibilityToParent").toBool())
+    {
+    visible = false;
+    }
+
+  childWidget->setVisible(visible);
+
+  // setVisible() has set the ExplicitShowHide flag, restore it as we don't want
+  // to make it like it was an explicit visible set because we want
+  // to allow the children to be explicitly hidden by the user.
+  if ((!childWidget->property("visibilityToParent").isValid() ||
+      childWidget->property("visibilityToParent").toBool()))
+    {
+    childWidget->setAttribute(Qt::WA_WState_ExplicitShowHide, false);
+    }
+  this->ForcingVisibility = false;
+}
+
+//-----------------------------------------------------------------------------
+ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(QWidget* _parent)
+  :QGroupBox(_parent)
+  , d_ptr(new ctkCollapsibleGroupBoxPrivate(*this))
+{
+  Q_D(ctkCollapsibleGroupBox);
+  d->init();
+}
+
+//-----------------------------------------------------------------------------
+ctkCollapsibleGroupBox::ctkCollapsibleGroupBox(const QString& title, QWidget* _parent)
+  :QGroupBox(title, _parent)
+  , d_ptr(new ctkCollapsibleGroupBoxPrivate(*this))
+{
+  Q_D(ctkCollapsibleGroupBox);
+  d->init();
+}
+
+//-----------------------------------------------------------------------------
+ctkCollapsibleGroupBox::~ctkCollapsibleGroupBox()
+{
+
+}
 
 //-----------------------------------------------------------------------------
 void ctkCollapsibleGroupBox::expand(bool _expand)
 {
+  Q_D(ctkCollapsibleGroupBox);
   if (!_expand)
     {
-    this->OldSize = this->size();
+    d->OldSize = this->size();
     }
 
-  QObjectList childList = this->children();
-  for (int i = 0; i < childList.size(); ++i) 
+  // Update the visibility of all the children
+  // We can't use findChildren as it would return the grandchildren
+  foreach(QObject* childObject, this->children())
     {
-    QObject *o = childList.at(i);
-    if (o && o->isWidgetType()) 
+    if (childObject->isWidgetType())
       {
-      QWidget *w = static_cast<QWidget *>(o);
-      if ( w )
-        {
-        w->setVisible(_expand);
-        }
+      d->setChildVisibility(qobject_cast<QWidget*>(childObject));
       }
     }
   
   if (_expand)
     {
-    this->setMaximumHeight(this->MaxHeight);
-    this->resize(this->OldSize);
+    this->setMaximumHeight(d->MaxHeight);
+    this->resize(d->OldSize);
     }
   else
     {
-    this->MaxHeight = this->maximumHeight();
+    d->MaxHeight = this->maximumHeight();
     this->setMaximumHeight(22);
     }
 }
 
-//-----------------------------------------------------------------------------
-void ctkCollapsibleGroupBox::childEvent(QChildEvent* c)
-{
-  if(c && c->type() == QEvent::ChildAdded)
-    {
-    if (c->child() && c->child()->isWidgetType())
-      {
-      QWidget *w = static_cast<QWidget*>(c->child());
-      w->setVisible(this->isChecked());
-      }
-    }
-  QGroupBox::childEvent(c);
-}
-
 #if QT_VERSION < 0x040600
 //-----------------------------------------------------------------------------
 void ctkCollapsibleGroupBox::paintEvent(QPaintEvent* e)
@@ -189,29 +251,91 @@ void ctkCollapsibleGroupBox::mouseReleaseEvent(QMouseEvent *event)
 #endif
 
 //-----------------------------------------------------------------------------
-QSize ctkCollapsibleGroupBox::minimumSizeHint() const
-{
-  //qDebug() << "ctkCollapsibleGroupBox::minimumSizeHint::" << this->QGroupBox::minimumSizeHint() ;
-  return this->QGroupBox::minimumSizeHint();
-}
-
-//-----------------------------------------------------------------------------
-QSize ctkCollapsibleGroupBox::sizeHint() const
+void ctkCollapsibleGroupBox::childEvent(QChildEvent* c)
 {
-  //qDebug() << "ctkCollapsibleGroupBox::sizeHint::" << this->QGroupBox::sizeHint() ;
-  return this->QGroupBox::sizeHint();
+  Q_D(ctkCollapsibleGroupBox);
+  QObject* child = c->child();
+  if (c && c->type() == QEvent::ChildAdded &&
+      child && child->isWidgetType())
+    {
+    QWidget *childWidget = qobject_cast<QWidget*>(c->child());
+    // Handle the case where the child has already it's visibility set before
+    // being added to the widget
+    if (childWidget->testAttribute(Qt::WA_WState_ExplicitShowHide) &&
+        childWidget->testAttribute(Qt::WA_WState_Hidden))
+      {
+      // if the widget has explicitly set to hidden, then mark it as such
+      childWidget->setProperty("visibilityToParent", false);
+      }
+    // We want to catch all the child's Show/Hide events.
+    child->installEventFilter(this);
+    // If the child is added while ctkCollapsibleButton is collapsed, then we
+    // need to hide the child.
+    d->setChildVisibility(childWidget);
+    }
+  this->QGroupBox::childEvent(c);
 }
 
 //-----------------------------------------------------------------------------
-int ctkCollapsibleGroupBox::heightForWidth(int w) const
+void ctkCollapsibleGroupBox::setVisible(bool show)
 {
-  //qDebug() << "ctkCollapsibleGroupBox::heightForWidth::" << this->QGroupBox::heightForWidth(w) ;
-  return this->QGroupBox::heightForWidth(w);
+  Q_D(ctkCollapsibleGroupBox);
+  // calling QWidget::setVisible() on ctkCollapsibleGroupBox 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->QGroupBox::setVisible(show);
+  d->ForcingVisibility = false;
+  // We have been ignoring setChildVisibility() while the collapsible button
+  // is not yet created, now that it is created, ensure that the children
+  // are correctly shown/hidden depending on their explicit visibility and
+  // the collapsed property of the button.
+  if (!d->IsStateCreated && this->testAttribute(Qt::WA_WState_Created))
+    {
+    d->IsStateCreated = true;
+    foreach(QObject* child, this->children())
+      {
+      QWidget* childWidget = qobject_cast<QWidget*>(child);
+      if (childWidget)
+        {
+        d->setChildVisibility(childWidget);
+        }
+      }
+    }
 }
 
 //-----------------------------------------------------------------------------
-void ctkCollapsibleGroupBox::resizeEvent ( QResizeEvent * _event )
+bool ctkCollapsibleGroupBox::eventFilter(QObject* child, QEvent* e)
 {
-  //qDebug() << "ctkCollapsibleGroupBox::resizeEvent::" << _event->oldSize() << _event->size() ;
-  return this->QGroupBox::resizeEvent(_event);
+  Q_D(ctkCollapsibleGroupBox);
+  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->QGroupBox::eventFilter(child, e);
 }

+ 10 - 12
Libs/Widgets/ctkCollapsibleGroupBox.h

@@ -26,6 +26,7 @@
 
 // CTK includes
 #include "ctkWidgetsExport.h"
+class ctkCollapsibleGroupBoxPrivate;
 
 /// A QGroupBox with an arrow indicator that shows/hides the groupbox contents
 /// when clicked. It responds to the slot QGroupBox::setChecked(bool) or
@@ -50,19 +51,19 @@ public:
   /// true if the groupbox is collapsed (closed), false if it is expanded(open)
   inline bool collapsed()const;
 
-  /// Reimplemtented for internal reasons
-  virtual int heightForWidth(int w) const;
-  /// Reimplemtented for internal reasons
-  virtual QSize minimumSizeHint()const;
-  /// Reimplemtented for internal reasons
-  virtual QSize sizeHint()const;
+  /// Reimplemented for internal reasons
+  /// Catch when a child widget's visibility is externally changed
+  virtual bool eventFilter(QObject* child, QEvent* e);
 
+  /// Reimplemented for internal reasons
+  virtual void setVisible(bool show);
 protected slots:
   /// called when the arrow indicator is clicked
   /// users can call it programatically by calling setChecked(bool)
   virtual void expand(bool expand);
 
 protected:
+  QScopedPointer<ctkCollapsibleGroupBoxPrivate> d_ptr;
   /// reimplemented for internal reasons
   virtual void childEvent(QChildEvent*);
 
@@ -71,13 +72,10 @@ protected:
   virtual void mousePressEvent(QMouseEvent*);
   virtual void mouseReleaseEvent(QMouseEvent*);
 #endif
-  virtual void resizeEvent(QResizeEvent*);
+
 private:
-  void init();
-  /// Size of the widget for collapsing
-  QSize OldSize;
-  /// Maximum allowed height
-  int   MaxHeight;
+  Q_DECLARE_PRIVATE(ctkCollapsibleGroupBox);
+  Q_DISABLE_COPY(ctkCollapsibleGroupBox);
 };
 
 //----------------------------------------------------------------------------