Forráskód Böngészése

Add ctkPopupWidget to easily control popups/tooltips

Julien Finet 14 éve
szülő
commit
908ec251b7

+ 3 - 0
Libs/Widgets/CMakeLists.txt

@@ -82,6 +82,8 @@ SET(KIT_SRCS
   ctkPathLineEdit.h
   ctkPixmapIconEngine.cpp
   ctkPixmapIconEngine.h
+  ctkPopupWidget.cpp
+  ctkPopupWidget.h
   ctkRangeSlider.cpp
   ctkRangeSlider.h
   ctkRangeWidget.cpp
@@ -195,6 +197,7 @@ SET(KIT_MOC_SRCS
   ctkMenuButton.h
   ctkModalityWidget.h
   ctkPathLineEdit.h
+  ctkPopupWidget.h
   ctkRangeSlider.h
   ctkRangeWidget.h
   ctkScreenshotDialog.h

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

@@ -42,6 +42,7 @@ SET(TEST_SOURCES
   ctkMenuButtonTest1.cpp
   ctkModalityWidgetTest1.cpp
   ctkPathLineEditTest1.cpp
+  ctkPopupWidgetTest1.cpp
   ctkRangeSliderTest1.cpp
   ctkRangeWidgetTest1.cpp
   ctkRangeWidgetTest2.cpp
@@ -160,6 +161,7 @@ SIMPLE_TEST( ctkMatrixWidgetTest2 )
 SIMPLE_TEST( ctkMenuButtonTest1 )
 SIMPLE_TEST( ctkModalityWidgetTest1 )
 SIMPLE_TEST( ctkPathLineEditTest1 )
+SIMPLE_TEST( ctkPopupWidgetTest1 )
 SIMPLE_TEST( ctkRangeSliderTest1 )
 SIMPLE_TEST( ctkRangeWidgetTest1 )
 SIMPLE_TEST( ctkRangeWidgetTest2 )

+ 75 - 0
Libs/Widgets/Testing/Cpp/ctkPopupWidgetTest1.cpp

@@ -0,0 +1,75 @@
+/*=========================================================================
+
+  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 <QVBoxLayout>
+#include <QPushButton>
+#include <QTimer>
+
+// CTK includes
+#include "ctkPopupWidget.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkPopupWidgetTest1(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  QPushButton base("Top level push button");
+  
+  ctkPopupWidget popup;
+  QPushButton popupContent("popup");
+  QVBoxLayout* layout = new QVBoxLayout;
+  layout->addWidget(&popupContent);
+  popup.setLayout(layout);
+
+  popup.setBaseWidget(&base);
+  //QObject::connect(&base, SIGNAL(clicked()), &popup, SLOT(showPopup()));
+  base.show();
+
+  QWidget topLevel;
+  QPushButton button1("button");
+  QVBoxLayout* vlayout = new QVBoxLayout;
+  vlayout->addWidget(&button1);
+  topLevel.setLayout(vlayout);
+  topLevel.show();
+
+  ctkPopupWidget popup2;
+  popup2.setFrameStyle(QFrame::Box);
+  popup2.setLineWidth(2);
+  QPushButton popup2Content("popup2");
+  QVBoxLayout* p2layout = new QVBoxLayout;
+  p2layout->setContentsMargins(0,0,0,0);
+  p2layout->addWidget(&popup2Content);
+  popup2.setLayout(p2layout);
+  popup2.setBaseWidget(&button1);
+  
+  
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+  return app.exec();
+}
+

+ 313 - 0
Libs/Widgets/ctkPopupWidget.cpp

@@ -0,0 +1,313 @@
+/*=========================================================================
+
+  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 <QDebug>
+#include <QDesktopWidget>
+#include <QEvent>
+#include <QLayout>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QStyle>
+#include <QTimer>
+
+// CTK includes
+#include "ctkPopupWidget.h"
+
+#define LEAVE_CLOSING_DELAY 66
+#define ENTER_OPENING_DELAY 66
+#define DEFAULT_FADING_DURATION 333 // fast enough
+#define FADING_FPS 33 // 30 fps
+
+// -------------------------------------------------------------------------
+class ctkPopupWidgetPrivate
+{
+  Q_DECLARE_PUBLIC(ctkPopupWidget);
+protected:
+  ctkPopupWidget* const q_ptr;
+public:
+  ctkPopupWidgetPrivate(ctkPopupWidget& object);
+  void init();
+
+  enum OpenStateType{
+    Closed =0,
+    Closing,
+    Opening,
+    Open
+  };
+  
+  QWidget* BaseWidget;
+  QTimer* Timer;
+  int Alpha;
+  int CurrentAlpha;
+  int Duration;
+  OpenStateType OpenState;
+  bool AutoHide;
+};
+
+// -------------------------------------------------------------------------
+ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
+  :q_ptr(&object)
+{
+  this->BaseWidget = 0;
+  this->Timer = 0;
+  this->Alpha = 255;
+  this->CurrentAlpha = 0;
+  this->Duration = DEFAULT_FADING_DURATION;
+
+  this->OpenState = Closed;
+  this->AutoHide = true;
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidgetPrivate::init()
+{
+  Q_Q(ctkPopupWidget);
+  q->setAttribute(Qt::WA_NoSystemBackground);
+  q->setAttribute(Qt::WA_OpaquePaintEvent, false);
+  q->setAttribute(Qt::WA_TranslucentBackground);
+  //q->setAttribute(Qt::WA_PaintOnScreen);
+  // Already by default
+  //q->setAutoFillBackground(false);
+  // Obsolete
+  //q->setAttribute(Qt::WA_ContentsPropagated);
+  this->Alpha = q->style()->styleHint(QStyle::SH_ToolTipLabel_Opacity);
+  this->Timer = new QTimer(q);
+  QObject::connect(this->Timer, SIGNAL(timeout()), q, SLOT(update()));
+}
+
+// -------------------------------------------------------------------------
+// Qt::FramelessWindowHint is required on Windows for Translucent background
+// Qt::Toolip is preferred to Qt::Popup as it would close itself at the first
+// click outside the widget (typically a click in the BaseWidget)
+ctkPopupWidget::ctkPopupWidget(QWidget* parentWidget)
+  : Superclass(parentWidget, Qt::ToolTip | Qt::FramelessWindowHint)
+  , d_ptr(new ctkPopupWidgetPrivate(*this))
+{
+  Q_D(ctkPopupWidget);
+  d->init();
+}
+
+// -------------------------------------------------------------------------
+ctkPopupWidget::~ctkPopupWidget()
+{
+}
+
+// -------------------------------------------------------------------------
+QWidget* ctkPopupWidget::baseWidget()const
+{
+  Q_D(const ctkPopupWidget);
+  return d->BaseWidget;
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidget::setBaseWidget(QWidget* widget)
+{
+  Q_D(ctkPopupWidget);
+  if (d->BaseWidget)
+    {
+    d->BaseWidget->removeEventFilter(this);
+    }
+  d->BaseWidget = widget;
+  if (d->BaseWidget)
+    {
+    d->BaseWidget->installEventFilter(this);
+    }
+  QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+}
+
+// -------------------------------------------------------------------------
+int ctkPopupWidget::opacity()const
+{
+  Q_D(const ctkPopupWidget);
+  return d->Alpha;
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidget::setOpacity(int alpha)
+{
+  Q_D(ctkPopupWidget);
+  d->Alpha = alpha;
+}
+
+// -------------------------------------------------------------------------
+bool ctkPopupWidget::autoHide()const
+{
+  Q_D(const ctkPopupWidget);
+  return d->AutoHide;
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidget::setAutoHide(bool mode)
+{
+  Q_D(ctkPopupWidget);
+  d->AutoHide = mode;
+  QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+}
+
+// -------------------------------------------------------------------------
+void ctkPopupWidget::paintEvent(QPaintEvent* event)
+{
+  Q_D(ctkPopupWidget);
+  Q_UNUSED(event);
+
+  QPainter painter(this);
+  QColor semiTransparentColor = this->palette().window().color();
+  semiTransparentColor.setAlpha(d->CurrentAlpha);
+  painter.fillRect(this->rect(), semiTransparentColor);
+  this->Superclass::paintEvent(event);
+
+  if (d->OpenState == ctkPopupWidgetPrivate::Opening &&  d->CurrentAlpha < d->Alpha)
+    {
+    d->CurrentAlpha += d->Alpha * d->Timer->interval() / d->Duration;
+    }
+  else if (d->OpenState ==  ctkPopupWidgetPrivate::Closing &&  d->CurrentAlpha > 0)
+    {
+    d->CurrentAlpha -= d->Alpha * d->Timer->interval() / d->Duration;
+    }
+  if (d->CurrentAlpha >= d->Alpha)
+    {
+    d->CurrentAlpha = d->Alpha;
+    d->OpenState = ctkPopupWidgetPrivate::Open;
+    }
+    
+  if (d->CurrentAlpha <= 0)
+    {
+    d->CurrentAlpha = 0;
+    this->hide();
+    d->OpenState = ctkPopupWidgetPrivate::Closed;
+    }
+}
+
+// --------------------------------------------------------------------------
+void ctkPopupWidget::leaveEvent(QEvent* event)
+{
+  QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
+  this->Superclass::leaveEvent(event);
+}
+
+// --------------------------------------------------------------------------
+void ctkPopupWidget::enterEvent(QEvent* event)
+{
+  QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+  this->Superclass::enterEvent(event);
+}
+
+// --------------------------------------------------------------------------
+bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
+{
+  Q_D(ctkPopupWidget);
+  if (obj == d->BaseWidget &&
+      event->type() == QEvent::Enter)
+    {
+    QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
+    }
+  else if (obj == d->BaseWidget &&
+      event->type() == QEvent::Leave)
+    {
+    QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
+    }
+  return this->QObject::eventFilter(obj, event);
+}
+
+// --------------------------------------------------------------------------
+void ctkPopupWidget::updatePopup()
+{
+  Q_D(ctkPopupWidget);
+  if (!d->AutoHide)
+    {
+    return;
+    }
+  if (this->underMouse() || (d->BaseWidget && d->BaseWidget->underMouse()))
+    {
+    this->showPopup();
+    }
+  else
+    {
+    this->hidePopup();
+    }
+}
+/*
+// --------------------------------------------------------------------------
+void ctkPopupWidget::hidePopup()
+{
+  Q_D(ctkPopupWidget);
+  if (this->underMouse() || d->BaseWidget->underMouse())
+    {
+    return;
+    }
+  d->hidePopup();
+}
+
+// --------------------------------------------------------------------------
+void ctkPopupWidget::showPopup()
+{
+  Q_D(ctkPopupWidget);
+  if ((!this->underMouse() && !d->BaseWidget->underMouse()) ||
+      (this->isVisible() && d->CurrentAlpha == d->Alpha))
+    {
+    return;
+    }
+  d->showPopup();
+}
+*/
+// --------------------------------------------------------------------------
+void ctkPopupWidget::showPopup()
+{
+  Q_D(ctkPopupWidget);
+  if (this->isVisible() && d->OpenState == ctkPopupWidgetPrivate::Open)
+    {
+    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());
+      }
+    }
+  d->OpenState = ctkPopupWidgetPrivate::Opening;
+  d->Timer->start(FADING_FPS);
+  this->show();
+}
+
+// --------------------------------------------------------------------------
+void ctkPopupWidget::hidePopup()
+{
+  Q_D(ctkPopupWidget);
+
+  if (!this->isVisible() || d->OpenState == ctkPopupWidgetPrivate::Closed)
+    {
+    return;
+    }
+
+  d->OpenState = ctkPopupWidgetPrivate::Closing;
+  d->Timer->start(FADING_FPS);
+  this->update();
+}

+ 73 - 0
Libs/Widgets/ctkPopupWidget.h

@@ -0,0 +1,73 @@
+/*=========================================================================
+
+  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.
+
+=========================================================================*/
+
+#ifndef __ctkPopupWidget_h
+#define __ctkPopupWidget_h
+
+// Qt includes
+#include <QFrame>
+
+// CTK includes
+#include "ctkWidgetsExport.h"
+
+class ctkPopupWidgetPrivate;
+
+/// Description:
+class CTK_WIDGETS_EXPORT ctkPopupWidget : public QFrame
+{
+  Q_OBJECT
+  /// Final transparency of the widget (after opacity fading)
+  /// QStyle::SH_ToolTipLabel_Opacity by default.
+  Q_PROPERTY( int opacity READ opacity WRITE setOpacity)
+  Q_PROPERTY( bool autoHide READ autoHide WRITE setAutoHide)
+
+public:
+  typedef QFrame Superclass;
+  explicit ctkPopupWidget(QWidget* parent = 0);
+  virtual ~ctkPopupWidget();
+
+  QWidget* baseWidget()const;
+  void setBaseWidget(QWidget* baseWidget);
+  
+  int opacity()const;
+  void setOpacity(int alpha);
+  
+  bool autoHide()const;
+  void setAutoHide(bool);
+
+public slots:
+  void hidePopup();
+  void showPopup();
+  void updatePopup();
+
+protected:
+  QScopedPointer<ctkPopupWidgetPrivate> d_ptr;
+
+  virtual void paintEvent(QPaintEvent*);
+  virtual void leaveEvent(QEvent* event);
+  virtual void enterEvent(QEvent* event);
+  virtual bool eventFilter(QObject* obj, QEvent* event);
+
+private:
+  Q_DECLARE_PRIVATE(ctkPopupWidget);
+  Q_DISABLE_COPY(ctkPopupWidget);
+};
+
+#endif