Преглед изворни кода

Merge branch 'popup-widget'

* popup-widget:
  Tuning of ctkPopupWidget
Julien Finet пре 14 година
родитељ
комит
bd4c27caf0

+ 11 - 3
Libs/Widgets/Testing/Cpp/ctkPopupWidgetTest1.cpp

@@ -20,6 +20,7 @@
 
 // Qt includes
 #include <QApplication>
+#include <QComboBox>
 #include <QVBoxLayout>
 #include <QPushButton>
 #include <QSlider>
@@ -39,24 +40,29 @@ QWidget* createPanel(const QString& title, QList<ctkPopupWidget*>& popups)
   QWidget* topLevel = new QWidget(0);
   topLevel->setWindowTitle(title);
   
-  QPushButton* focusButton = new QPushButton("Focus popup");
+  QComboBox* focusComboBox = new QComboBox;
+  focusComboBox->addItem("Focus popup");
+  focusComboBox->addItem("Focus popup");
+  focusComboBox->addItem("Focus popup");
+  focusComboBox->addItem("Focus popup");
   QPushButton* openButton = new QPushButton("Open popup");
   QPushButton* toggleButton = new QPushButton("Toggle popup");
   toggleButton->setCheckable(true);
 
   QVBoxLayout* vlayout = new QVBoxLayout;
-  vlayout->addWidget(focusButton);
+  vlayout->addWidget(focusComboBox);
   vlayout->addWidget(openButton);
   vlayout->addWidget(toggleButton);
   topLevel->setLayout(vlayout);
 
   ctkPopupWidget* focusPopup = new ctkPopupWidget;
+  focusPopup->setAutoShow(true);
   focusPopup->setAutoHide(true);
   QPushButton* focusPopupContent = new QPushButton("button");
   QVBoxLayout* focusLayout = new QVBoxLayout;
   focusLayout->addWidget(focusPopupContent);
   focusPopup->setLayout(focusLayout);
-  focusPopup->setBaseWidget(focusButton);
+  focusPopup->setBaseWidget(focusComboBox);
 
   QPalette palette = focusPopup->palette();
   QLinearGradient linearGradient(QPointF(0.f, 0.f), QPointF(0.f, 0.666f));
@@ -70,6 +76,7 @@ QWidget* createPanel(const QString& title, QList<ctkPopupWidget*>& popups)
   ctkPopupWidget* openPopup = new ctkPopupWidget;
   openPopup->setFrameStyle(QFrame::Box);
   openPopup->setLineWidth(1);
+  openPopup->setAutoShow(false);
   openPopup->setAutoHide(false);
   openPopup->setWindowOpacity(0.7);
   QPushButton* openPopupContent = new QPushButton("Close popup");
@@ -83,6 +90,7 @@ QWidget* createPanel(const QString& title, QList<ctkPopupWidget*>& popups)
                    openPopup, SLOT(hidePopup()));
                    
   ctkPopupWidget* togglePopup = new ctkPopupWidget;
+  togglePopup->setAutoShow(false);
   togglePopup->setAutoHide(false);
   QPushButton* togglePopupContent = new QPushButton("useless button");
   QVBoxLayout* toggleLayout = new QVBoxLayout;

+ 116 - 35
Libs/Widgets/ctkPopupWidget.cpp

@@ -35,9 +35,9 @@
 // CTK includes
 #include "ctkPopupWidget.h"
 
-#define LEAVE_CLOSING_DELAY 10
-#define ENTER_OPENING_DELAY 10
-#define DEFAULT_FADING_DURATION 333 // fast enough
+#define LEAVE_CLOSING_DELAY 100 // we don't want to be too fast to close
+#define ENTER_OPENING_DELAY 20 // we want to be responsive but allow "errors"
+#define DEFAULT_FADING_DURATION 333 // fast enough without being too slow
 
 // -------------------------------------------------------------------------
 QGradient* duplicateGradient(const QGradient* gradient)
@@ -90,13 +90,26 @@ public:
   bool fitBaseWidgetSize()const;
   Qt::Alignment pixmapAlignment()const;
   void setupPopupPixmapWidget();
-  
+
+  QList<const QWidget*> focusWidgets(bool onlyVisible = false)const;
+
+  // Return true if the mouse cursor is above any of the focus widgets or their
+  // children.
+  // If the cursor is above a child widget, install the event filter to listen
+  // when the cursor leaves the widget.
+  bool mouseOver();
+
+  // Same as QWidget::isAncestorOf() but don't restrain to the same window
+  // and apply it to all the focusWidgets
+  bool isAncestorOf(const QWidget* ancestor, const QWidget* child)const;
+
   QRect closedGeometry()const;
   QRect openGeometry()const;
   
   QPropertyAnimation* currentAnimation()const;
 
   QWidget* BaseWidget;
+  bool AutoShow;
   bool AutoHide;
 
   double WindowAlpha;
@@ -121,6 +134,7 @@ ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
   :q_ptr(&object)
 {
   this->BaseWidget = 0;
+  this->AutoShow = true;
   this->AutoHide = true;
   this->Effect = ctkPopupWidget::ScrollEffect;
   this->WindowAlpha = 1.;
@@ -173,6 +187,63 @@ QPropertyAnimation* ctkPopupWidgetPrivate::currentAnimation()const
 }
 
 // -------------------------------------------------------------------------
+QList<const QWidget*> ctkPopupWidgetPrivate::focusWidgets(bool onlyVisible)const
+{
+  Q_Q(const ctkPopupWidget);
+  QList<const QWidget*> res;
+  if (!onlyVisible || q->isVisible())
+    {
+    res << q;
+    }
+  if (this->BaseWidget && (!onlyVisible || this->BaseWidget->isVisible()))
+    {
+    res << this->BaseWidget;
+    }
+  if (this->PopupPixmapWidget && (!onlyVisible || this->PopupPixmapWidget->isVisible()))
+    {
+    res << this->PopupPixmapWidget;
+    }
+  return res;
+}
+
+// -------------------------------------------------------------------------
+bool ctkPopupWidgetPrivate::mouseOver()
+{
+  Q_Q(ctkPopupWidget);
+  QList<const QWidget*> widgets = this->focusWidgets(true);
+  foreach(const QWidget* widget, widgets)
+    {
+    if (widget->underMouse())
+      {
+      return true;
+      }
+    }
+  // Warning QApplication::widgetAt(QCursor::pos()) can be a bit slow...
+  QWidget* widgetUnderCursor = qApp->widgetAt(QCursor::pos());
+  foreach(const QWidget* focusWidget, widgets)
+    {
+    if (this->isAncestorOf(focusWidget, widgetUnderCursor))
+      {
+      widgetUnderCursor->installEventFilter(q);
+      return true;
+      }
+    }
+  return false;
+}
+
+// -------------------------------------------------------------------------
+bool ctkPopupWidgetPrivate::isAncestorOf(const QWidget* ancestor, const QWidget* child)const
+{
+  while (child)
+    {
+    if (child == ancestor)
+        return true;
+    child = child->parentWidget();
+    }
+  return false;
+}
+
+// -------------------------------------------------------------------------
 void ctkPopupWidgetPrivate::setupPopupPixmapWidget()
 {
   Q_Q(ctkPopupWidget);
@@ -399,6 +470,21 @@ void ctkPopupWidget::setBaseWidget(QWidget* widget)
 }
 
 // -------------------------------------------------------------------------
+bool ctkPopupWidget::autoShow()const
+{
+  Q_D(const ctkPopupWidget);
+  return d->AutoShow;
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidget::setAutoShow(bool mode)
+{
+  Q_D(ctkPopupWidget);
+  d->AutoShow = mode;
+  QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+}
+
+// -------------------------------------------------------------------------
 bool ctkPopupWidget::autoHide()const
 {
   Q_D(const ctkPopupWidget);
@@ -410,7 +496,7 @@ void ctkPopupWidget::setAutoHide(bool mode)
 {
   Q_D(ctkPopupWidget);
   d->AutoHide = mode;
-  QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+  QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
 }
 
 // -------------------------------------------------------------------------
@@ -590,15 +676,28 @@ void ctkPopupWidget::enterEvent(QEvent* event)
 bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
 {
   Q_D(ctkPopupWidget);
-  if (obj == d->BaseWidget &&
-      event->type() == QEvent::Enter)
+  if (event->type() == QEvent::Enter)
     {
-    QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+    if ( d->currentAnimation()->state() == QAbstractAnimation::Stopped )
+      {
+      // Maybe the user moved the mouse on the widget by mistake, don't open
+      // the popup instantly...
+      QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+      }
+    else 
+      {
+      // ... except if the popup is closing, we want to reopen it as sooon as
+      // possible.
+      this->updatePopup();
+      }
     }
-  else if (obj == d->BaseWidget &&
-      event->type() == QEvent::Leave)
+  else if (event->type() == QEvent::Leave)
     {
     QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
+    if (obj != d->BaseWidget)
+      {
+      obj->removeEventFilter(this);
+      }
     }
   return this->QObject::eventFilter(obj, event);
 }
@@ -607,17 +706,15 @@ bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
 void ctkPopupWidget::updatePopup()
 {
   Q_D(ctkPopupWidget);
-  if (!d->AutoHide)
-    {
-    return;
-    }
-  if (this->underMouse() ||
-      (d->BaseWidget && d->BaseWidget->underMouse()) ||
-      (d->PopupPixmapWidget && d->PopupPixmapWidget->underMouse()))
+
+  // Querying mouseOver can be slow, don't do it if not needed.
+  bool mouseOver = (d->AutoShow || d->AutoHide) && d->mouseOver();
+    
+  if (d->AutoShow && mouseOver)
     {
     this->showPopup();
     }
-  else
+  else if (d->AutoHide && !mouseOver)
     {
     this->hidePopup();
     }
@@ -633,23 +730,7 @@ void ctkPopupWidget::showPopup()
     {
     return;
     }
-  if (d->BaseWidget)
-    {
-    /*
-    QPoint bottomLeft = QPoint(d->BaseWidget->geometry().x(), d->BaseWidget->geometry().bottom());
-    QPoint pos = d->BaseWidget->parentWidget() ? d->BaseWidget->parentWidget()->mapToGlobal(bottomLeft) : bottomLeft;
-    this->move(pos);
-    /// TODO: need some refinement
-    if ((this->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag &&
-          this->width() < d->BaseWidget->width()) ||
-        (this->sizePolicy().horizontalPolicy() & QSizePolicy::ShrinkFlag &&
-          this->width() > d->BaseWidget->width()))
-      {
-      // Fit to BaseWidget size
-      this->resize(d->BaseWidget->width(), this->sizeHint().height());
-      }
-    */
-    }
+
   this->setGeometry(d->openGeometry());
   d->currentAnimation()->setDirection(QAbstractAnimation::Forward);
   

+ 16 - 4
Libs/Widgets/ctkPopupWidget.h

@@ -35,26 +35,35 @@ class CTK_WIDGETS_EXPORT ctkPopupWidget : public QFrame
 {
   Q_OBJECT
   /// Control wether the popup automatically opens when the mouse
-  /// is over the baseWidget and automatically closes when it leaves
-  /// the widget.
+  /// enter the widget. True by default
+  Q_PROPERTY( bool autoShow READ autoShow WRITE setAutoShow)
+
+  /// Control wether the popup automatically closes when the mouse
+  /// leaves the widget. True by default
   Q_PROPERTY( bool autoHide READ autoHide WRITE setAutoHide)
-  
+
   /// ScrollEffect by default
   Q_PROPERTY( AnimationEffect animationEffect READ animationEffect WRITE setAnimationEffect)
   
+  /// Opening/Closing curve
   /// QEasingCurve::InOutQuad by default
   Q_PROPERTY( QEasingCurve::Type easingCurve READ easingCurve WRITE setEasingCurve);
   
+  /// Where is the popup in relation to the BaseWidget
   /// To vertically justify, use Qt::AlignTop | Qt::AlignBottom
   /// Qt::AlignJustify | Qt::AlignBottom by default
   Q_PROPERTY( Qt::Alignment alignment READ alignment WRITE setAlignment);
   
+  /// Direction of the scrolling effect, can be Qt::Vertical, Qt::Horizontal or
+  /// both Qt::Vertical|Qt::Horizontal.
   /// Vertical by default
   Q_PROPERTY( Qt::Orientation orientation READ orientation WRITE setOrientation);
   
+  /// Control where the popup opens vertically.
   /// TopToBottom by default
   Q_PROPERTY( ctkPopupWidget::VerticalDirection verticalDirection READ verticalDirection WRITE setVerticalDirection);
 
+  /// Control where the popup opens horizontally.
   /// LeftToRight by default
   Q_PROPERTY( Qt::LayoutDirection horizontalDirection READ horizontalDirection WRITE setHorizontalDirection);
 
@@ -68,7 +77,10 @@ public:
   /// it tries to resize itself to fit the same width of \a baseWidget.
   QWidget* baseWidget()const;
   void setBaseWidget(QWidget* baseWidget);
-  
+
+  bool autoShow()const;
+  void setAutoShow(bool);
+
   bool autoHide()const;
   void setAutoHide(bool);