Added some parts of ctk lib.

As it looks now that lib contains way more then I want to pull in so i'm just copying the files i'm interested in.
This commit is contained in:
eelke 2017-12-29 08:35:57 +01:00
parent a06c752029
commit 3a425ab7c1
11 changed files with 2162 additions and 2 deletions

48
ctk/ctk.pro Normal file
View file

@ -0,0 +1,48 @@
#-------------------------------------------------
#
# Project created by QtCreator 2017-02-26T10:51:14
#
#-------------------------------------------------
QT += widgets
TARGET = ctk
TEMPLATE = lib
CONFIG += staticlib c++14
QMAKE_CXXFLAGS += /std:c++17
INCLUDEPATH += C:\prog\include \
C:\Prog\include\pgsql \
C:\VSproj\boost32\include\boost-1_65_1
DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += ctkBasePopupWidget.cpp \
ctkPopupWidget.cpp \
ctkWidgetsUtils.cpp
HEADERS += ctkBasePopupWidget.h \
ctkBasePopupWidget_p.h \
ctkPopupWidget.h \
ctkPopupWidget_p.h \
ctkWidgetsUtils.h \
ctkWidgetsExport.h
unix {
target.path = /usr/lib
INSTALLS += target
}

843
ctk/ctkBasePopupWidget.cpp Normal file
View file

@ -0,0 +1,843 @@
/*=========================================================================
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 <QDir>
#include <QEvent>
#include <QLabel>
#include <QLayout>
#include <QMouseEvent>
#include <QMoveEvent>
#include <QPainter>
#include <QPointer>
#include <QPropertyAnimation>
#include <QStyle>
#include <QTimer>
// CTK includes
#include "ctkBasePopupWidget_p.h"
#include "ctkWidgetsUtils.h"
// -------------------------------------------------------------------------
QGradient* duplicateGradient(const QGradient* gradient)
{
QGradient* newGradient = 0;
switch (gradient->type())
{
case QGradient::LinearGradient:
{
const QLinearGradient* linearGradient = static_cast<const QLinearGradient*>(gradient);
newGradient = new QLinearGradient(linearGradient->start(), linearGradient->finalStop());
break;
}
case QGradient::RadialGradient:
{
const QRadialGradient* radialGradient = static_cast<const QRadialGradient*>(gradient);
newGradient = new QRadialGradient(radialGradient->center(), radialGradient->radius());
break;
}
case QGradient::ConicalGradient:
{
const QConicalGradient* conicalGradient = static_cast<const QConicalGradient*>(gradient);
newGradient = new QConicalGradient(conicalGradient->center(), conicalGradient->angle());
break;
}
default:
break;
}
if (!newGradient)
{
Q_ASSERT(gradient->type() != QGradient::NoGradient);
return newGradient;
}
newGradient->setCoordinateMode(gradient->coordinateMode());
newGradient->setSpread(gradient->spread());
newGradient->setStops(gradient->stops());
return newGradient;
}
// -------------------------------------------------------------------------
ctkBasePopupWidgetPrivate::ctkBasePopupWidgetPrivate(ctkBasePopupWidget& object)
:q_ptr(&object)
{
this->Effect = ctkBasePopupWidget::ScrollEffect;
this->EffectDuration = 333; // in ms
this->EffectAlpha = 1.;
this->AlphaAnimation = 0;
this->ForcedTranslucent = false;
this->ScrollAnimation = 0;
this->PopupPixmapWidget = 0;
// Geometry attributes
this->Alignment = Qt::AlignJustify | Qt::AlignBottom;
this->Orientations = Qt::Vertical;
this->VerticalDirection = ctkBasePopupWidget::TopToBottom;
this->HorizontalDirection = Qt::LeftToRight;
}
// -------------------------------------------------------------------------
ctkBasePopupWidgetPrivate::~ctkBasePopupWidgetPrivate()
{
}
// -------------------------------------------------------------------------
void ctkBasePopupWidgetPrivate::init()
{
Q_Q(ctkBasePopupWidget);
// By default, Tooltips are shown only on active windows. In a popup widget
// case, we sometimes aren't the active window but we still would like to
// show the children tooltips.
q->setAttribute(Qt::WA_AlwaysShowToolTips, true);
//q->setAttribute(Qt::WA_MacAlwaysShowToolWindow, true);
this->AlphaAnimation = new QPropertyAnimation(q, "effectAlpha", q);
this->AlphaAnimation->setDuration(this->EffectDuration);
this->AlphaAnimation->setStartValue(0.);
this->AlphaAnimation->setEndValue(1.);
QObject::connect(this->AlphaAnimation, SIGNAL(finished()),
q, SLOT(onEffectFinished()));
this->PopupPixmapWidget = new QLabel(q, Qt::ToolTip | Qt::FramelessWindowHint);
this->ScrollAnimation = new QPropertyAnimation(q, "effectGeometry", q);
this->ScrollAnimation->setDuration(this->EffectDuration);
QObject::connect(this->ScrollAnimation, SIGNAL(finished()),
q, SLOT(onEffectFinished()));
QObject::connect(this->ScrollAnimation, SIGNAL(finished()),
this->PopupPixmapWidget, SLOT(hide()));
q->setAnimationEffect(this->Effect);
q->setEasingCurve(QEasingCurve::OutCubic);
q->setBaseWidget(q->parentWidget());
}
// -------------------------------------------------------------------------
QPropertyAnimation* ctkBasePopupWidgetPrivate::currentAnimation()const
{
return this->Effect == ctkBasePopupWidget::ScrollEffect ?
this->ScrollAnimation : this->AlphaAnimation;
}
// -------------------------------------------------------------------------
bool ctkBasePopupWidgetPrivate::isOpening()const
{
return this->currentAnimation()->state() == QAbstractAnimation::Running &&
this->currentAnimation()->direction() == QAbstractAnimation::Forward;
}
// -------------------------------------------------------------------------
bool ctkBasePopupWidgetPrivate::isClosing()const
{
return this->currentAnimation()->state() == QAbstractAnimation::Running &&
this->currentAnimation()->direction() == QAbstractAnimation::Backward;
}
// -------------------------------------------------------------------------
bool ctkBasePopupWidgetPrivate::wasClosing()const
{
Q_Q(const ctkBasePopupWidget);
return qobject_cast<QAbstractAnimation*>(q->sender())->direction()
== QAbstractAnimation::Backward;
}
// -------------------------------------------------------------------------
QWidgetList ctkBasePopupWidgetPrivate::focusWidgets(bool onlyVisible)const
{
Q_Q(const ctkBasePopupWidget);
QWidgetList res;
if (!onlyVisible || q->isVisible())
{
res << const_cast<ctkBasePopupWidget*>(q);
}
if (!this->BaseWidget.isNull() && (!onlyVisible || this->BaseWidget->isVisible()))
{
res << this->BaseWidget;
}
if (this->PopupPixmapWidget && (!onlyVisible || this->PopupPixmapWidget->isVisible()))
{
res << this->PopupPixmapWidget;
}
return res;
}
// -------------------------------------------------------------------------
QWidget* ctkBasePopupWidgetPrivate::mouseOver()
{
QList<QWidget*> widgets = this->focusWidgets(true);
foreach(QWidget* widget, widgets)
{
if (widget->underMouse())
{
return widget;
}
}
// Warning QApplication::widgetAt(QCursor::pos()) can be a bit slow...
const QPoint pos = QCursor::pos();
QWidget* widgetUnderCursor = qApp->widgetAt(pos);
foreach(const QWidget* focusWidget, widgets)
{
if (this->isAncestorOf(focusWidget, widgetUnderCursor) &&
// Ignore when cursor is above a title bar of a focusWidget, underMouse
// wouldn't have return false, but QApplication::widgetAt would return
// the widget
(focusWidget != widgetUnderCursor ||
QRect(QPoint(0,0), focusWidget->size()).contains(
focusWidget->mapFromGlobal(pos))))
{
return widgetUnderCursor;
}
}
return 0;
}
// -------------------------------------------------------------------------
bool ctkBasePopupWidgetPrivate::isAncestorOf(const QWidget* ancestor, const QWidget* child)const
{
while (child)
{
if (child == ancestor)
{
return true;
}
child = child->parentWidget();
}
return false;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidgetPrivate::setupPopupPixmapWidget()
{
Q_Q(ctkBasePopupWidget);
this->PopupPixmapWidget->setAlignment(this->pixmapAlignment());
QPixmap pixmap;
if (q->testAttribute(Qt::WA_TranslucentBackground))
{
// only QImage handle transparency correctly
QImage image(q->geometry().size(), QImage::Format_ARGB32);
image.fill(0);
q->render(&image);
pixmap = QPixmap::fromImage(image);
}
else
{
pixmap = QPixmap::fromImage(
ctk::grabWidget(q, QRect(QPoint(0,0), q->geometry().size())));
}
this->PopupPixmapWidget->setPixmap(pixmap);
this->PopupPixmapWidget->setAttribute(
Qt::WA_TranslucentBackground, q->testAttribute(Qt::WA_TranslucentBackground));
this->PopupPixmapWidget->setWindowOpacity(q->windowOpacity());
}
// -------------------------------------------------------------------------
Qt::Alignment ctkBasePopupWidgetPrivate::pixmapAlignment()const
{
Qt::Alignment alignment;
if (this->VerticalDirection == ctkBasePopupWidget::TopToBottom)
{
alignment |= Qt::AlignBottom;
}
else// if (this->VerticalDirection == ctkBasePopupWidget::BottomToTop)
{
alignment |= Qt::AlignTop;
}
if (this->HorizontalDirection == Qt::LeftToRight)
{
alignment |= Qt::AlignRight;
}
else// if (this->VerticalDirection == ctkBasePopupWidget::BottomToTop)
{
alignment |= Qt::AlignLeft;
}
return alignment;
}
// -------------------------------------------------------------------------
QRect ctkBasePopupWidgetPrivate::closedGeometry()const
{
Q_Q(const ctkBasePopupWidget);
return this->closedGeometry(q->geometry());
}
// -------------------------------------------------------------------------
QRect ctkBasePopupWidgetPrivate::closedGeometry(QRect openGeom)const
{
if (this->Orientations & Qt::Vertical)
{
if (this->VerticalDirection == ctkBasePopupWidget::BottomToTop)
{
openGeom.moveTop(openGeom.bottom());
}
openGeom.setHeight(0);
}
if (this->Orientations & Qt::Horizontal)
{
if (this->HorizontalDirection == Qt::RightToLeft)
{
openGeom.moveLeft(openGeom.right());
}
openGeom.setWidth(0);
}
return openGeom;
}
// -------------------------------------------------------------------------
QRect ctkBasePopupWidgetPrivate::baseGeometry()const
{
if (this->BaseWidget.isNull())
{
return QRect();
}
return QRect(this->mapToGlobal(this->BaseWidget->geometry().topLeft()),
this->BaseWidget->size());
}
// -------------------------------------------------------------------------
QPoint ctkBasePopupWidgetPrivate::mapToGlobal(const QPoint& baseWidgetPoint)const
{
QPoint mappedPoint = baseWidgetPoint;
if (!this->BaseWidget.isNull() && this->BaseWidget->parentWidget())
{
mappedPoint = this->BaseWidget->parentWidget()->mapToGlobal(mappedPoint);
}
return mappedPoint;
}
// -------------------------------------------------------------------------
QRect ctkBasePopupWidgetPrivate::desiredOpenGeometry()const
{
return this->desiredOpenGeometry(this->baseGeometry());
}
// -------------------------------------------------------------------------
QRect ctkBasePopupWidgetPrivate::desiredOpenGeometry(QRect baseGeometry)const
{
Q_Q(const ctkBasePopupWidget);
QSize size = q->size();
if (!q->testAttribute(Qt::WA_WState_Created))
{
size = q->sizeHint();
}
if (baseGeometry.isNull())
{
return QRect(q->pos(), size);
}
QRect geometry;
if (this->Alignment & Qt::AlignJustify)
{
if (this->Orientations & Qt::Vertical)
{
size.setWidth(baseGeometry.width());
}
}
if (this->Alignment & Qt::AlignTop &&
this->Alignment & Qt::AlignBottom)
{
size.setHeight(baseGeometry.height());
}
geometry.setSize(size);
QPoint topLeft = baseGeometry.topLeft();
QPoint bottomRight = baseGeometry.bottomRight();
if (this->Alignment & Qt::AlignLeft)
{
if (this->HorizontalDirection == Qt::LeftToRight)
{
geometry.moveLeft(topLeft.x());
}
else
{
geometry.moveRight(topLeft.x() - 1);
}
}
else if (this->Alignment & Qt::AlignRight)
{
if (this->HorizontalDirection == Qt::LeftToRight)
{
geometry.moveLeft(bottomRight.x() + 1);
}
else
{
geometry.moveRight(bottomRight.x());
}
}
else if (this->Alignment & Qt::AlignHCenter)
{
geometry.moveLeft((topLeft.x() + bottomRight.x()) / 2 - size.width() / 2);
}
else if (this->Alignment & Qt::AlignJustify)
{
geometry.moveLeft(topLeft.x());
}
if (this->Alignment & Qt::AlignTop)
{
if (this->VerticalDirection == ctkBasePopupWidget::TopToBottom)
{
geometry.moveTop(topLeft.y());
}
else
{
geometry.moveBottom(topLeft.y() - 1);
}
}
else if (this->Alignment & Qt::AlignBottom)
{
if (this->VerticalDirection == ctkBasePopupWidget::TopToBottom)
{
geometry.moveTop(bottomRight.y() + 1);
}
else
{
geometry.moveBottom(bottomRight.y());
}
}
else if (this->Alignment & Qt::AlignVCenter)
{
geometry.moveTop((topLeft.y() + bottomRight.y()) / 2 - size.height() / 2);
}
return geometry;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidgetPrivate::hideAll()
{
Q_Q(ctkBasePopupWidget);
// It is possible to have the popup widget not being a popup but inside
// a layout: maybe the popup has been pin-down in a way that it gets parented
// In that case, there is no reason to hide the popup.
if (!(q->windowFlags() & PopupWindowType))
{
return;
}
// Before hiding, transfer the active window flag to its parent, this will
// prevent the application to send a ApplicationDeactivate signal that
// doesn't need to be done.
#ifndef Q_OS_MAC // See Slicer issue #3850
if (q->isActiveWindow() && !this->BaseWidget.isNull())
{
qApp->setActiveWindow(this->BaseWidget->window());
}
#endif
q->hide();
this->PopupPixmapWidget->hide();
// If there is a popup open in the ctkBasePopupWidget children, then hide it
// as well so we don't have a popup open while the ctkBasePopupWidget is hidden.
QPointer<QWidget> activePopupWidget = qApp->activePopupWidget();
if (activePopupWidget && this->isAncestorOf(q, activePopupWidget))
{
activePopupWidget->close();
}
}
// -------------------------------------------------------------------------
// 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)
ctkBasePopupWidget::ctkBasePopupWidget(QWidget* parentWidget)
//: Superclass(QApplication::desktop()->screen(QApplication::desktop()->screenNumber(parentWidget)),
: Superclass(parentWidget,
PopupWindowType | Qt::FramelessWindowHint)
, d_ptr(new ctkBasePopupWidgetPrivate(*this))
{
Q_D(ctkBasePopupWidget);
d->init();
}
// -------------------------------------------------------------------------
ctkBasePopupWidget::ctkBasePopupWidget(ctkBasePopupWidgetPrivate* pimpl, QWidget* parentWidget)
//: //Superclass(QApplication::desktop()->screen(QApplication::desktop()->screenNumber(parentWidget)),
: Superclass(parentWidget,
PopupWindowType | Qt::FramelessWindowHint)
, d_ptr(pimpl)
{
}
// -------------------------------------------------------------------------
ctkBasePopupWidget::~ctkBasePopupWidget()
{
}
// -------------------------------------------------------------------------
QWidget* ctkBasePopupWidget::baseWidget()const
{
Q_D(const ctkBasePopupWidget);
return d->BaseWidget;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setBaseWidget(QWidget* widget)
{
Q_D(ctkBasePopupWidget);
if (!d->BaseWidget.isNull())
{
//disconnect(d->BaseWidget, SIGNAL(destroyed(QObject*)),
// this, SLOT(onBaseWidgetDestroyed()));
}
d->BaseWidget = widget;
if (!d->BaseWidget.isNull())
{
//connect(d->BaseWidget, SIGNAL(destroyed(QObject*)),
// this, SLOT(onBaseWidgetDestroyed()));
}
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::onBaseWidgetDestroyed()
{
Q_D(ctkBasePopupWidget);
d->hideAll();
this->setBaseWidget(0);
// could be a property.
this->deleteLater();
}
// -------------------------------------------------------------------------
ctkBasePopupWidget::AnimationEffect ctkBasePopupWidget::animationEffect()const
{
Q_D(const ctkBasePopupWidget);
return d->Effect;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setAnimationEffect(ctkBasePopupWidget::AnimationEffect effect)
{
Q_D(ctkBasePopupWidget);
/// TODO: handle the case where there is an animation running
d->Effect = effect;
}
// -------------------------------------------------------------------------
int ctkBasePopupWidget::effectDuration()const
{
Q_D(const ctkBasePopupWidget);
return d->EffectDuration;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setEffectDuration(int duration)
{
Q_D(ctkBasePopupWidget);
d->EffectDuration = duration;
d->AlphaAnimation->setDuration(d->EffectDuration);
d->ScrollAnimation->setDuration(d->EffectDuration);
}
// -------------------------------------------------------------------------
QEasingCurve::Type ctkBasePopupWidget::easingCurve()const
{
Q_D(const ctkBasePopupWidget);
return d->AlphaAnimation->easingCurve().type();
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setEasingCurve(QEasingCurve::Type easingCurve)
{
Q_D(ctkBasePopupWidget);
d->AlphaAnimation->setEasingCurve(easingCurve);
d->ScrollAnimation->setEasingCurve(easingCurve);
}
// -------------------------------------------------------------------------
Qt::Alignment ctkBasePopupWidget::alignment()const
{
Q_D(const ctkBasePopupWidget);
return d->Alignment;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setAlignment(Qt::Alignment alignment)
{
Q_D(ctkBasePopupWidget);
d->Alignment = alignment;
}
// -------------------------------------------------------------------------
Qt::Orientations ctkBasePopupWidget::orientation()const
{
Q_D(const ctkBasePopupWidget);
return d->Orientations;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setOrientation(Qt::Orientations orientations)
{
Q_D(ctkBasePopupWidget);
d->Orientations = orientations;
}
// -------------------------------------------------------------------------
ctkBasePopupWidget::VerticalDirection ctkBasePopupWidget::verticalDirection()const
{
Q_D(const ctkBasePopupWidget);
return d->VerticalDirection;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setVerticalDirection(ctkBasePopupWidget::VerticalDirection verticalDirection)
{
Q_D(ctkBasePopupWidget);
d->VerticalDirection = verticalDirection;
}
// -------------------------------------------------------------------------
Qt::LayoutDirection ctkBasePopupWidget::horizontalDirection()const
{
Q_D(const ctkBasePopupWidget);
return d->HorizontalDirection;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::setHorizontalDirection(Qt::LayoutDirection horizontalDirection)
{
Q_D(ctkBasePopupWidget);
d->HorizontalDirection = horizontalDirection;
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::onEffectFinished()
{
Q_D(ctkBasePopupWidget);
if (d->ForcedTranslucent)
{
d->ForcedTranslucent = false;
this->setAttribute(Qt::WA_TranslucentBackground, false);
}
if (d->wasClosing())
{
d->hideAll();
emit this->popupOpened(false);
}
else
{
this->show();
emit this->popupOpened(true);
}
}
// -------------------------------------------------------------------------
bool ctkBasePopupWidget::event(QEvent* event)
{
switch(event->type())
{
case QEvent::ParentChange:
// For now the base widget is the parent widget
this->setBaseWidget(this->parentWidget());
break;
default:
break;
}
return this->Superclass::event(event);
}
// -------------------------------------------------------------------------
void ctkBasePopupWidget::paintEvent(QPaintEvent* event)
{
Q_D(ctkBasePopupWidget);
Q_UNUSED(event);
QPainter painter(this);
QBrush brush = this->palette().window();
if (brush.style() == Qt::LinearGradientPattern ||
brush.style() == Qt::ConicalGradientPattern ||
brush.style() == Qt::RadialGradientPattern)
{
QGradient* newGradient = duplicateGradient(brush.gradient());
QGradientStops stops;
foreach(QGradientStop stop, newGradient->stops())
{
stop.second.setAlpha(stop.second.alpha() * d->EffectAlpha);
stops.push_back(stop);
}
newGradient->setStops(stops);
brush = QBrush(*newGradient);
delete newGradient;
}
else
{
QColor color = brush.color();
color.setAlpha(color.alpha() * d->EffectAlpha);
brush.setColor(color);
}
//QColor semiTransparentColor = this->palette().window().color();
//semiTransparentColor.setAlpha(d->CurrentAlpha);
painter.fillRect(this->rect(), brush);
painter.end();
// Let the QFrame draw itself if needed
this->Superclass::paintEvent(event);
}
// --------------------------------------------------------------------------
void ctkBasePopupWidget::showPopup()
{
Q_D(ctkBasePopupWidget);
if ((this->isVisible() &&
d->currentAnimation()->state() == QAbstractAnimation::Stopped) ||
(!d->BaseWidget.isNull() && !d->BaseWidget->isVisible()))
{
return;
}
// If the layout has never been activated, the widget doesn't know its
// minSize/maxSize and we then wouldn't know what's its true geometry.
if (this->layout() && !this->testAttribute(Qt::WA_WState_Created))
{
this->layout()->activate();
}
this->setGeometry(d->desiredOpenGeometry());
/// Maybe the popup doesn't allow the desiredOpenGeometry if the widget
/// minimum size is larger than the desired size.
QRect openGeometry = this->geometry();
QRect closedGeometry = d->closedGeometry();
d->currentAnimation()->setDirection(QAbstractAnimation::Forward);
switch(d->Effect)
{
case WindowOpacityFadeEffect:
if (!this->testAttribute(Qt::WA_TranslucentBackground))
{
d->ForcedTranslucent = true;
this->setAttribute(Qt::WA_TranslucentBackground, true);
}
this->show();
break;
case ScrollEffect:
{
d->PopupPixmapWidget->setGeometry(closedGeometry);
d->ScrollAnimation->setStartValue(closedGeometry);
d->ScrollAnimation->setEndValue(openGeometry);
d->setupPopupPixmapWidget();
d->PopupPixmapWidget->show();
break;
}
default:
break;
}
switch(d->currentAnimation()->state())
{
case QAbstractAnimation::Stopped:
d->currentAnimation()->start();
break;
case QAbstractAnimation::Paused:
d->currentAnimation()->resume();
break;
default:
case QAbstractAnimation::Running:
break;
}
}
// --------------------------------------------------------------------------
void ctkBasePopupWidget::hidePopup()
{
Q_D(ctkBasePopupWidget);
if (!this->isVisible() &&
d->currentAnimation()->state() == QAbstractAnimation::Stopped)
{
return;
}
d->currentAnimation()->setDirection(QAbstractAnimation::Backward);
QRect openGeometry = this->geometry();
QRect closedGeometry = d->closedGeometry();
switch(d->Effect)
{
case WindowOpacityFadeEffect:
if (!this->testAttribute(Qt::WA_TranslucentBackground))
{
d->ForcedTranslucent = true;
this->setAttribute(Qt::WA_TranslucentBackground, true);
}
break;
case ScrollEffect:
{
d->ScrollAnimation->setStartValue(closedGeometry);
d->ScrollAnimation->setEndValue(openGeometry);
d->setupPopupPixmapWidget();
d->PopupPixmapWidget->setGeometry(this->geometry());
d->PopupPixmapWidget->show();
if (this->isActiveWindow())
{
qApp->setActiveWindow(!d->BaseWidget.isNull() ? d->BaseWidget->window() : 0);
}
this->hide();
break;
}
default:
break;
}
switch(d->currentAnimation()->state())
{
case QAbstractAnimation::Stopped:
d->currentAnimation()->start();
break;
case QAbstractAnimation::Paused:
d->currentAnimation()->resume();
break;
default:
case QAbstractAnimation::Running:
break;
}
}
// --------------------------------------------------------------------------
double ctkBasePopupWidget::effectAlpha()const
{
Q_D(const ctkBasePopupWidget);
return d->EffectAlpha;
}
// --------------------------------------------------------------------------
void ctkBasePopupWidget::setEffectAlpha(double alpha)
{
Q_D(ctkBasePopupWidget);
d->EffectAlpha = alpha;
this->repaint();
}
// --------------------------------------------------------------------------
QRect ctkBasePopupWidget::effectGeometry()const
{
Q_D(const ctkBasePopupWidget);
return d->PopupPixmapWidget->geometry();
}
// --------------------------------------------------------------------------
void ctkBasePopupWidget::setEffectGeometry(QRect newGeometry)
{
Q_D(ctkBasePopupWidget);
d->PopupPixmapWidget->setGeometry(newGeometry);
d->PopupPixmapWidget->repaint();
}

225
ctk/ctkBasePopupWidget.h Normal file
View file

@ -0,0 +1,225 @@
/*=========================================================================
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 __ctkBasePopupWidget_h
#define __ctkBasePopupWidget_h
// Qt includes
#include <QEasingCurve>
#include <QFrame>
#include <QMetaType>
// CTK includes
#include "ctkWidgetsExport.h"
class ctkBasePopupWidgetPrivate;
/// \ingroup Widgets
/// ctkBasePopupWidget is a popup that opens under, above or on the side of
/// another widget (baseWidget() or its parent widget by default).
/// The children (widgets and layout) of the popup define of the content
/// of the popup. Different effects can be applied during the opening or
/// closing of the popup.
/// See ctkPopupWidget for an automatic control of its opening and closing.
/// \sa baseWidget(), animationEffect, ctkPopupWidget
class CTK_WIDGETS_EXPORT ctkBasePopupWidget : public QFrame
{
Q_OBJECT
Q_ENUMS(AnimationEffect)
Q_ENUMS(VerticalDirection)
/// This property controls the effect to apply when the popup is being
/// opened or closed. The total duration and the easing curve of the effect
/// are controlled by \a effectDuration and \easingCurve respectively.
/// ScrollEffect by default.
/// \sa AnimationEffect, animationEffect(), setAnimationEffect(),
/// effectDuration, easingCurve
Q_PROPERTY( AnimationEffect animationEffect READ animationEffect WRITE setAnimationEffect)
/// The property controls the \a animationEffect duration in ms.
/// If the popup state (open or close) is being changed during the animation,
/// the active animation is stopped and a new animation is being created from
/// the current state (geometry, transparency...) to the new final state.
/// Default to 333ms
/// \sa effectDuration(), setEffectDuration(), animationEffect, easingCurve
Q_PROPERTY( int effectDuration READ effectDuration WRITE setEffectDuration);
/// The property controls the behavior of the opening or closing curve of the
/// animation effect.
/// QEasingCurve::InOutQuad by default
/// \sa easingCurve(), setEasingCurve(), animationEffect, effectDuration
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::Orientations orientation READ orientation WRITE setOrientation);
/// Control where the popup opens vertically.
/// TopToBottom by default
Q_PROPERTY( ctkBasePopupWidget::VerticalDirection verticalDirection READ verticalDirection WRITE setVerticalDirection);
/// Control where the popup opens horizontally.
/// LeftToRight by default
Q_PROPERTY( Qt::LayoutDirection horizontalDirection READ horizontalDirection WRITE setHorizontalDirection);
public:
typedef QFrame Superclass;
/// Although a popup widget is a top-level widget, if a parent is
/// passed the popup widget will be deleted when that parent is
/// destroyed (as with any other QObject).
/// ctkBasePopupWidget is a top-level widget (Qt::ToolTip), so
/// even if a parent is passed, the popup will display outside the possible
/// parent layout.
/// \sa baseWidget().
explicit ctkBasePopupWidget(QWidget* parent = 0);
virtual ~ctkBasePopupWidget();
/// Widget the popup is attached to. It opens right under \a baseWidget
/// and if the ctkBasePopupWidget sizepolicy contains the growFlag/shrinkFlag,
/// it tries to resize itself to fit the same width of \a baseWidget.
/// By default, baseWidget is the parent widget.
/// \sa setBaseWidget()
QWidget* baseWidget()const;
enum AnimationEffect
{
WindowOpacityFadeEffect = 0,
ScrollEffect,
FadeEffect
};
/// Return the animationEffect property value.
/// \sa animationEffect
AnimationEffect animationEffect()const;
/// Set the animationEffect property value.
/// \sa animationEffect
void setAnimationEffect(AnimationEffect effect);
/// Return the effectDuration property value.
/// \sa effectDuration
int effectDuration()const;
/// Set the effectDuration property value.
/// \sa effectDuration
void setEffectDuration(int duration);
/// Return the easingCurve property value.
/// \sa easingCurve
QEasingCurve::Type easingCurve()const;
/// Set the easingCurve property value.
/// \sa easingCurve
void setEasingCurve(QEasingCurve::Type easingCurve);
/// Return the alignment property value.
/// \sa alignment
Qt::Alignment alignment()const;
/// Set the alignment property value.
/// \sa alignment
void setAlignment(Qt::Alignment alignment);
/// Return the orientation property value.
/// \sa orientation
Qt::Orientations orientation()const;
/// Set the orientation property value.
/// \sa orientation
void setOrientation(Qt::Orientations orientation);
enum VerticalDirection{
TopToBottom = 1,
BottomToTop = 2
};
/// Return the verticalDirection property value.
/// \sa verticalDirection
VerticalDirection verticalDirection()const;
/// Set the verticalDirection property value.
/// \sa verticalDirection
void setVerticalDirection(VerticalDirection direction);
/// Return the horizontalDirection property value.
/// \sa horizontalDirection
Qt::LayoutDirection horizontalDirection()const;
/// Set the horizontalDirection property value.
/// \sa horizontalDirection
void setHorizontalDirection(Qt::LayoutDirection direction);
public Q_SLOTS:
/// Hide the popup if open or opening. It takes around 300ms
/// for the fading effect to hide the popup.
virtual void hidePopup();
/// Open the popup if closed or closing. It takes around 300ms
/// for the fading effect to open the popup.
virtual void showPopup();
/// Show/hide the popup. It can be conveniently linked to a QPushButton
/// signal.
inline void showPopup(bool show);
Q_SIGNALS:
/// Fired when the popup finished its animation: opening (true) or closing (false).
/// \sa showPopup(), hidePopup()
void popupOpened(bool open);
protected:
explicit ctkBasePopupWidget(ctkBasePopupWidgetPrivate* pimpl, QWidget* parent = 0);
QScopedPointer<ctkBasePopupWidgetPrivate> d_ptr;
Q_PROPERTY(double effectAlpha READ effectAlpha WRITE setEffectAlpha DESIGNABLE false)
Q_PROPERTY(QRect effectGeometry READ effectGeometry WRITE setEffectGeometry DESIGNABLE false)
double effectAlpha()const;
QRect effectGeometry()const;
virtual void setBaseWidget(QWidget* baseWidget);
virtual bool event(QEvent* event);
virtual void paintEvent(QPaintEvent*);
protected Q_SLOTS:
virtual void onEffectFinished();
void setEffectAlpha(double alpha);
void setEffectGeometry(QRect geometry);
void onBaseWidgetDestroyed();
private:
Q_DECLARE_PRIVATE(ctkBasePopupWidget);
Q_DISABLE_COPY(ctkBasePopupWidget);
};
Q_DECLARE_METATYPE(ctkBasePopupWidget::AnimationEffect)
Q_DECLARE_METATYPE(ctkBasePopupWidget::VerticalDirection)
// -------------------------------------------------------------------------
void ctkBasePopupWidget::showPopup(bool show)
{
if (show)
{
this->showPopup();
}
else
{
this->hidePopup();
}
}
#endif

108
ctk/ctkBasePopupWidget_p.h Normal file
View file

@ -0,0 +1,108 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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 __ctkBasePopupWidget_p_h
#define __ctkBasePopupWidget_p_h
// Qt includes
#include <QPointer>
class QLabel;
class QPropertyAnimation;
// CTK includes
#include "ctkBasePopupWidget.h"
#define PopupWindowType Qt::Tool
// -------------------------------------------------------------------------
/// \ingroup Widgets
class CTK_WIDGETS_EXPORT ctkBasePopupWidgetPrivate
: public QObject
{
Q_OBJECT
Q_DECLARE_PUBLIC(ctkBasePopupWidget);
protected:
ctkBasePopupWidget* const q_ptr;
public:
ctkBasePopupWidgetPrivate(ctkBasePopupWidget& object);
~ctkBasePopupWidgetPrivate();
virtual void init();
bool isOpening()const;
bool isClosing()const;
/// Return true if the animation was closing (direction == backward).
/// It doesn't indicate if the action is still running or finished.
/// Can only be called in a slot as it uses sender().
bool wasClosing()const;
bool fitBaseWidgetSize()const;
Qt::Alignment pixmapAlignment()const;
void setupPopupPixmapWidget();
QWidgetList focusWidgets(bool onlyVisible = false)const;
// Return the widget if the mouse cursor is above any of the focus widgets or their
// children.
virtual QWidget* 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;
/// Return the closed geometry for the popup based on the current geometry
QRect closedGeometry()const;
/// Return the closed geometry for a given open geometry
QRect closedGeometry(QRect openGeom)const;
/// Return the desired geometry, maybe it won't happen if the size is too
/// small for the popup.
QRect desiredOpenGeometry()const;
QRect desiredOpenGeometry(QRect baseGeometry)const;
QRect baseGeometry()const;
QPoint mapToGlobal(const QPoint& baseWidgetPoint)const;
QPropertyAnimation* currentAnimation()const;
//void temporarilyHiddenOn();
//void temporarilyHiddenOff();
void hideAll();
protected:
QPointer<QWidget> BaseWidget;
double EffectAlpha;
ctkBasePopupWidget::AnimationEffect Effect;
int EffectDuration;
QPropertyAnimation* AlphaAnimation;
bool ForcedTranslucent;
QPropertyAnimation* ScrollAnimation;
QLabel* PopupPixmapWidget;
// Geometry attributes
Qt::Alignment Alignment;
Qt::Orientations Orientations;
ctkBasePopupWidget::VerticalDirection VerticalDirection;
Qt::LayoutDirection HorizontalDirection;
};
#endif

560
ctk/ctkPopupWidget.cpp Normal file
View file

@ -0,0 +1,560 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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 <QDesktopWidget>
#include <QDialog>
#include <QDir>
#include <QEvent>
#include <QLabel>
#include <QLayout>
#include <QMouseEvent>
#include <QMoveEvent>
#include <QPainter>
#include <QPointer>
#include <QPropertyAnimation>
#include <QStyle>
#include <QTimer>
// CTK includes
#include "ctkPopupWidget_p.h"
// -------------------------------------------------------------------------
ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
:Superclass(object)
{
this->Active = false;
this->AutoShow = true;
this->ShowDelay = 20;
this->AutoHide = true;
this->HideDelay = 200;
}
// -------------------------------------------------------------------------
ctkPopupWidgetPrivate::~ctkPopupWidgetPrivate()
{
}
// -------------------------------------------------------------------------
void ctkPopupWidgetPrivate::init()
{
Q_Q(ctkPopupWidget);
this->setParent(q);
q->setActive(true);
this->Superclass::init();
}
// -------------------------------------------------------------------------
QWidget* ctkPopupWidgetPrivate::mouseOver()
{
Q_Q(ctkPopupWidget);
QWidget* widgetUnderCursor = this->Superclass::mouseOver();
if (widgetUnderCursor &&
!this->focusWidgets(true).contains(widgetUnderCursor))
{
widgetUnderCursor->installEventFilter(q);
}
return widgetUnderCursor;
}
// -------------------------------------------------------------------------
bool ctkPopupWidgetPrivate::eventFilter(QObject* obj, QEvent* event)
{
Q_Q(ctkPopupWidget);
QWidget* widget = qobject_cast<QWidget*>(obj);
if (!widget)
{
return this->Superclass::eventFilter(obj, event);
}
// Here are the application events, it's a lot of events, so we need to be
// careful to be fast.
if (event->type() == QEvent::ApplicationDeactivate)
{
// We wait to see if there is no other window being active
QTimer::singleShot(0, this, SLOT(onApplicationDeactivate()));
}
else if (event->type() == QEvent::ApplicationActivate)
{
QTimer::singleShot(0, this, SLOT(updateVisibility()));
}
if (this->BaseWidget.isNull())
{
return false;
}
if (event->type() == QEvent::Move && widget != this->BaseWidget)
{
if (widget->isAncestorOf(this->BaseWidget))
{
q->setGeometry(this->desiredOpenGeometry());
}
else if (this->isHidingCandidate(widget))
{
QTimer::singleShot(0, this, SLOT(updateVisibility()));
}
}
else if (event->type() == QEvent::Resize)
{
if (widget->isAncestorOf(this->BaseWidget))
{
q->setGeometry(this->desiredOpenGeometry());
}
else if (this->isHidingCandidate(widget))
{
QTimer::singleShot(0, this, SLOT(updateVisibility()));
}
}
else if (event->type() == QEvent::WindowStateChange &&
this->isHidingCandidate(widget))
{
QTimer::singleShot(0, this, SLOT(updateVisibility()));
}
else if ((event->type() == QEvent::WindowActivate ||
event->type() == QEvent::WindowDeactivate) &&
widget == this->BaseWidget->window())
{
QTimer::singleShot(0, this, SLOT(updateVisibility()));
}
else if (event->type() == QEvent::RequestSoftwareInputPanel)
{
qApp->setActiveWindow(widget->window());
}
return false;
}
// -------------------------------------------------------------------------
void ctkPopupWidgetPrivate::onApplicationDeactivate()
{
// Still no active window, that means the user now is controlling another
// application, we have no control over when the other app moves over the
// popup, so we hide the popup as it would show on top of the other app.
if (!qApp->activeWindow())
{
this->temporarilyHiddenOn();
}
}
// -------------------------------------------------------------------------
bool ctkPopupWidgetPrivate::isHidingCandidate(QWidget* widget)const
{
// The mac window manager is keeping the Qt:Tool widgets always on top,
// so if a non modal dialog is moved near the popup widget, the popup will
// always appear on top of the dialog. For this reason we manually have to
// hide the popup when a dialog is intersecting with the popup.
bool canWindowsHidePopup = false;
#if defined Q_OS_MAC
canWindowsHidePopup = true;
#endif
bool isWindow = widget->isWindow();
QDialog* dialog = qobject_cast<QDialog*>(widget);
bool isModal = dialog ? dialog->isModal() : false;
bool isBasePopupWidget = qobject_cast<ctkBasePopupWidget*>(widget);
bool isToolTip = widget->windowType() == Qt::ToolTip;
bool isPopup = widget->windowType() == Qt::Popup;
bool isSelf = (widget == (this->BaseWidget ? this->BaseWidget->window() : 0));
return canWindowsHidePopup && isWindow && !isModal && !isBasePopupWidget &&
!isToolTip && !isPopup && !isSelf;
}
// -------------------------------------------------------------------------
void ctkPopupWidgetPrivate::updateVisibility()
{
Q_Q(ctkPopupWidget);
// If the BaseWidget window is active, then there is no reason to cover the
// popup.
if (this->BaseWidget.isNull() ||
// the popupwidget active window is not active
(!this->BaseWidget->window()->isActiveWindow() &&
// and no other active window
(!qApp->activeWindow() ||
// or the active window is a popup
(!qobject_cast<ctkBasePopupWidget*>(qApp->activeWindow()) && //->windowType() != PopupWindowType &&
qApp->activeWindow()->windowType() != Qt::Popup))))
{
foreach(QWidget* topLevelWidget, qApp->topLevelWidgets())
{
// If there is at least 1 window (active or not) that covers the popup,
// then we ensure the popup is hidden.
// We have no way of knowing which toplevel is over (z-order) which one,
// it is an OS specific information.
// Of course, tooltips and popups don't count as covering windows.
if (topLevelWidget->isVisible() &&
!(topLevelWidget->windowState() & Qt::WindowMinimized) &&
this->isHidingCandidate(topLevelWidget) &&
topLevelWidget->frameGeometry().intersects(q->geometry()))
{
//qDebug() << "hide" << q << "because of: " << topLevelWidget
// << " with windowType: " << topLevelWidget->windowType()
// << topLevelWidget->isVisible()
// << (this->BaseWidget ? this->BaseWidget->window() : 0)
// << topLevelWidget->frameGeometry();
this->temporarilyHiddenOn();
return;
}
}
}
// If the base widget is hidden or minimized, we don't want to restore the
// popup.
if (!this->BaseWidget.isNull() &&
(!this->BaseWidget->isVisible() ||
this->BaseWidget->window()->windowState() & Qt::WindowMinimized))
{
return;
}
// Restore the visibility of the popup if it was hidden
this->temporarilyHiddenOff();
}
// -------------------------------------------------------------------------
void ctkPopupWidgetPrivate::temporarilyHiddenOn()
{
Q_Q(ctkPopupWidget);
if (!this->AutoHide &&
(q->isVisible() || this->isOpening()) &&
!(q->isHidden() || this->isClosing()))
{
this->setProperty("forcedClosed", this->isOpening() ? 2 : 1);
}
this->currentAnimation()->stop();
this->hideAll();
}
// -------------------------------------------------------------------------
void ctkPopupWidgetPrivate::temporarilyHiddenOff()
{
Q_Q(ctkPopupWidget);
int forcedClosed = this->property("forcedClosed").toInt();
if (forcedClosed > 0)
{
q->show();
if (forcedClosed == 2)
{
emit q->popupOpened(true);
}
this->setProperty("forcedClosed", 0);
}
else
{
q->updatePopup();
}
}
// -------------------------------------------------------------------------
// 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(new ctkPopupWidgetPrivate(*this), parentWidget)
{
Q_D(ctkPopupWidget);
d->init();
}
// -------------------------------------------------------------------------
ctkPopupWidget::~ctkPopupWidget()
{
}
// -------------------------------------------------------------------------
bool ctkPopupWidget::isActive()const
{
Q_D(const ctkPopupWidget);
return d->Active;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setActive(bool active)
{
Q_D(ctkPopupWidget);
if (active == d->Active)
{
return;
}
d->Active = active;
if (d->Active)
{
if (!d->BaseWidget.isNull())
{
d->BaseWidget->installEventFilter(this);
}
if (d->PopupPixmapWidget)
{
d->PopupPixmapWidget->installEventFilter(this);
}
qApp->installEventFilter(d);
}
else // not active
{
if (!d->BaseWidget.isNull())
{
d->BaseWidget->removeEventFilter(this);
}
if (d->PopupPixmapWidget)
{
d->PopupPixmapWidget->removeEventFilter(this);
}
qApp->removeEventFilter(d);
}
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setBaseWidget(QWidget* widget)
{
Q_D(ctkPopupWidget);
if (!d->BaseWidget.isNull())
{
d->BaseWidget->removeEventFilter(this);
}
this->Superclass::setBaseWidget(widget);
if (!d->BaseWidget.isNull() && d->Active)
{
d->BaseWidget->installEventFilter(this);
}
QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
}
// -------------------------------------------------------------------------
bool ctkPopupWidget::autoShow()const
{
Q_D(const ctkPopupWidget);
return d->AutoShow;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setAutoShow(bool mode)
{
Q_D(ctkPopupWidget);
d->AutoShow = mode;
QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
}
// -------------------------------------------------------------------------
int ctkPopupWidget::showDelay()const
{
Q_D(const ctkPopupWidget);
return d->ShowDelay;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setShowDelay(int delay)
{
Q_D(ctkPopupWidget);
d->ShowDelay = delay;
}
// -------------------------------------------------------------------------
bool ctkPopupWidget::autoHide()const
{
Q_D(const ctkPopupWidget);
return d->AutoHide;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setAutoHide(bool mode)
{
Q_D(ctkPopupWidget);
d->AutoHide = mode;
QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
}
// -------------------------------------------------------------------------
int ctkPopupWidget::hideDelay()const
{
Q_D(const ctkPopupWidget);
return d->HideDelay;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::setHideDelay(int delay)
{
Q_D(ctkPopupWidget);
d->HideDelay = delay;
}
// -------------------------------------------------------------------------
void ctkPopupWidget::onEffectFinished()
{
Q_D(ctkPopupWidget);
bool wasClosing = d->wasClosing();
this->Superclass::onEffectFinished();
if (wasClosing)
{
/// restore the AutoShow if needed.
if (!this->property("AutoShowOnClose").isNull())
{
d->AutoShow = this->property("AutoShowOnClose").toBool();
this->setProperty("AutoShowOnClose", QVariant());
}
}
}
// --------------------------------------------------------------------------
void ctkPopupWidget::leaveEvent(QEvent* event)
{
Q_D(ctkPopupWidget);
QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
this->Superclass::leaveEvent(event);
}
// --------------------------------------------------------------------------
void ctkPopupWidget::enterEvent(QEvent* event)
{
Q_D(ctkPopupWidget);
QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
this->Superclass::enterEvent(event);
}
// --------------------------------------------------------------------------
bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
{
Q_D(ctkPopupWidget);
// Here we listen to PopupPixmapWidget, BaseWidget and ctkPopupWidget
// children popups that were under the mouse
switch(event->type())
{
case QEvent::Move:
{
if (obj != d->BaseWidget)
{
break;
}
QMoveEvent* moveEvent = dynamic_cast<QMoveEvent*>(event);
QRect newBaseGeometry = d->baseGeometry();
newBaseGeometry.moveTopLeft(d->mapToGlobal(moveEvent->pos()));
QRect desiredGeometry = d->desiredOpenGeometry(newBaseGeometry);
this->move(desiredGeometry.topLeft());
//this->move(this->pos() + moveEvent->pos() - moveEvent->oldPos());
this->update();
break;
}
case QEvent::Hide:
case QEvent::Close:
// if the mouse was in a base widget child popup, then when we leave
// the popup we want to check if it needs to be closed.
if (obj != d->BaseWidget)
{
if (obj != d->PopupPixmapWidget &&
qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
{
obj->removeEventFilter(this);
QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
}
break;
}
d->temporarilyHiddenOn();
break;
case QEvent::Show:
if (obj != d->BaseWidget)
{
break;
}
this->setGeometry(d->desiredOpenGeometry());
d->temporarilyHiddenOff();
break;
case QEvent::Enter:
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(d->ShowDelay, this, SLOT(updatePopup()));
}
else
{
// ... except if the popup is closing, we want to reopen it as sooon as
// possible.
this->updatePopup();
}
break;
case QEvent::Leave:
// Don't listen to base widget children that are popups as what
// matters here is their close event instead
if (obj != d->BaseWidget &&
obj != d->PopupPixmapWidget &&
qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
{
break;
}
// The mouse might have left the area that keeps the popup open
QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
if (obj != d->BaseWidget &&
obj != d->PopupPixmapWidget)
{
obj->removeEventFilter(this);
}
break;
default:
break;
}
return this->QObject::eventFilter(obj, event);
}
// --------------------------------------------------------------------------
void ctkPopupWidget::updatePopup()
{
Q_D(ctkPopupWidget);
// Querying mouseOver can be slow, don't do it if not needed.
QWidget* mouseOver = (d->AutoShow || d->AutoHide) ? d->mouseOver() : 0;
if ((d->AutoShow ||
// Even if there is no AutoShow, we might still want to reopen the popup
// when closing it inadvertently, except if we are un-pin-ing the popup
(d->AutoHide && d->isClosing() && this->property("AutoShowOnClose").toBool())) &&
// to be automatically open, the mouse has to be over a child widget
mouseOver &&
// disable opening the popup when the popup is disabled
(d->BaseWidget.isNull() || d->BaseWidget->isEnabled()))
{
this->showPopup();
}
else if (d->AutoHide && !mouseOver)
{
this->hidePopup();
}
}
// --------------------------------------------------------------------------
void ctkPopupWidget::hidePopup()
{
// just in case it was set.
this->setProperty("forcedClosed", 0);
this->Superclass::hidePopup();
}
// --------------------------------------------------------------------------
void ctkPopupWidget::pinPopup(bool pin)
{
Q_D(ctkPopupWidget);
this->setAutoHide(!pin);
if (pin)
{
this->showPopup();
}
else
{
// When closing, we don't want to inadvertently re-open the menu.
this->setProperty("AutoShowOnClose", this->autoShow());
d->AutoShow = false;
this->hidePopup();
}
}

133
ctk/ctkPopupWidget.h Normal file
View file

@ -0,0 +1,133 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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
// CTK includes
#include "ctkBasePopupWidget.h"
class ctkPopupWidgetPrivate;
/// \ingroup Widgets
/// ctkPopupWidget is a specialization of ctkBasePopupWidget that handles
/// the opening and closing of the popup.
/// Below is an example of a popup slider that opens and closes next to a
/// button
/// \code
/// ctkPopupWidget* popup = new ctkPopupWidget(pushButton);
/// popup->setAlignment(Qt::AlignRight | Qt::AlignTop | Qt::AlignBottom);
/// popup->setOrientation(Qt::Horizontal);
/// QHBoxLayout* popupLayout = new QHBoxLayout(popup);
/// QSlider* popupSlider = new QSlider(popup);
/// popupLayout->addWidget(popupSlider);
/// \endcode
/// \sa ctkBasePopupWidget
class CTK_WIDGETS_EXPORT ctkPopupWidget : public ctkBasePopupWidget
{
Q_OBJECT
/// Control whether the popup listens to the application and baseWidget
/// events and decides if it needs to be permanently or temporarily hidden.
/// You might want to setActive(false) when embedding the popup
/// into a static layout intead of having it top-level (no parent).
/// Consider also removing its windowFlags (Qt::ToolTip |
/// Qt::FramelessWindowHint) and removing the baseWidget.
/// True by default
/// \sa isActive(), setActive()
Q_PROPERTY( bool active READ isActive WRITE setActive)
/// Control wether the popup automatically opens when the mouse
/// enter the widget. True by default
/// \sa autoShow(), setAutoShow()
Q_PROPERTY( bool autoShow READ autoShow WRITE setAutoShow)
/// Time in ms to wait before opening the popup if autoShow is set.
/// 20ms by default
/// \sa showDelay(), setShowDelay()
Q_PROPERTY( int showDelay READ showDelay WRITE setShowDelay)
/// Control wether the popup automatically closes when the mouse
/// leaves the widget. True by default.
/// \sa autoHide(), setAutoHide()
Q_PROPERTY( bool autoHide READ autoHide WRITE setAutoHide)
/// Time in ms to wait before closing the popup if autoHide is set.
/// 200ms by default
/// \sa hideDelay(), setHideDelay()
Q_PROPERTY( int hideDelay READ hideDelay WRITE setHideDelay)
public:
typedef ctkBasePopupWidget Superclass;
/// By default, the parent is the \a baseWidget.
/// \sa baseWidget()
explicit ctkPopupWidget(QWidget* parent = 0);
virtual ~ctkPopupWidget();
bool isActive()const;
void setActive(bool);
bool autoShow()const;
/// Calling setAutoShow automatically updates opens the popup if the cursor
/// is above the popup or the base widget.
void setAutoShow(bool);
int showDelay()const;
void setShowDelay(int delay);
bool autoHide()const;
/// Don't automatically close the popup when leaving the widget.
/// Calling setAutoHide automatically updates the state close the popup
/// if the mouse is not over the popup nor the base widget.
void setAutoHide(bool autoHide);
int hideDelay()const;
void setHideDelay(int delay);
public Q_SLOTS:
/// Convenient function that calls setAutoHide(!pin) and opens the popup
/// if pin is true regardless of the value of \a AutoShow.
/// It is typically connected with a checkable button to anchor the popup.
void pinPopup(bool pin);
public:
/// Reimplemented for internal reasons
virtual void hidePopup();
protected:
virtual void leaveEvent(QEvent* event);
virtual void enterEvent(QEvent* event);
virtual bool eventFilter(QObject* obj, QEvent* event);
/// Widget the popup is attached to. It opens right under \a baseWidget
/// and if the ctkPopupWidget sizepolicy contains the growFlag/shrinkFlag,
/// it tries to resize itself to fit the same width of \a baseWidget.
virtual void setBaseWidget(QWidget* baseWidget);
protected Q_SLOTS:
void updatePopup();
virtual void onEffectFinished();
private:
Q_DECLARE_PRIVATE(ctkPopupWidget);
Q_DISABLE_COPY(ctkPopupWidget);
};
#endif

67
ctk/ctkPopupWidget_p.h Normal file
View file

@ -0,0 +1,67 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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_p_h
#define __ctkPopupWidget_p_h
// CTK includes
#include "ctkBasePopupWidget_p.h"
#include "ctkPopupWidget.h"
// -------------------------------------------------------------------------
/// \ingroup Widgets
class CTK_WIDGETS_EXPORT ctkPopupWidgetPrivate
: public ctkBasePopupWidgetPrivate
{
Q_OBJECT
Q_DECLARE_PUBLIC(ctkPopupWidget);
public:
typedef ctkBasePopupWidgetPrivate Superclass;
ctkPopupWidgetPrivate(ctkPopupWidget& object);
~ctkPopupWidgetPrivate();
virtual void init();
// Return the widget 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.
virtual QWidget* mouseOver();
virtual bool eventFilter(QObject* obj, QEvent* event);
void temporarilyHiddenOn();
void temporarilyHiddenOff();
bool isHidingCandidate(QWidget* widget)const;
public Q_SLOTS:
void updateVisibility();
void onApplicationDeactivate();
protected:
bool Active;
bool AutoShow;
int ShowDelay;
bool AutoHide;
int HideDelay;
};
#endif

6
ctk/ctkWidgetsExport.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef CTKWIDGETSEXPORT_H
#define CTKWIDGETSEXPORT_H
#define CTK_WIDGETS_EXPORT
#endif // CTKWIDGETSEXPORT_H

115
ctk/ctkWidgetsUtils.cpp Normal file
View file

@ -0,0 +1,115 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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 <QBuffer>
#include <QImage>
//#include <QGLWidget>
#include <QPainter>
#include <QWidget>
// ctkWidgets includes
#include "ctkWidgetsUtils.h"
//----------------------------------------------------------------------------
QString ctk::base64HTMLImageTagSrc(const QImage& image)
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
return QString("data:image/png;base64,%1")
.arg(QString(buffer.data().toBase64()));
}
//----------------------------------------------------------------------------
//QImage ctk::grabWidget(QWidget* widget, QRect rectangle)
//{
// if (!widget)
// {
// return QImage();
// }
// if (!rectangle.isValid())
// {
// // Let Qt trigger layout mechanism and compute widget size.
// rectangle = QRect(0,0,-1,-1);
// }
//#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
// QPixmap widgetPixmap = QPixmap::grabWidget(widget, rectangle);
//#else
// QPixmap widgetPixmap = widget->grab(rectangle);
//#endif
// QImage widgetImage = widgetPixmap.toImage();
// QPainter painter;
// painter.begin(&widgetImage);
// foreach(QGLWidget* glWidget, widget->findChildren<QGLWidget*>())
// {
// if (!glWidget->isVisible())
// {
// continue;
// }
// QRect subWidgetRect = QRect(glWidget->mapTo(widget, QPoint(0,0)), glWidget->size());
// if (!rectangle.intersects(subWidgetRect))
// {
// continue;
// }
// QImage subImage = glWidget->grabFrameBuffer();
// painter.drawImage(subWidgetRect, subImage);
// }
// painter.end();
// return widgetImage;
//}
//----------------------------------------------------------------------------
QImage ctk::kwIconToQImage(const unsigned char *data, int width, int height, int pixelSize, unsigned int bufferLength, int options)
{
Q_UNUSED(options); // not yet supported
QByteArray imageData = QByteArray::fromRawData(
reinterpret_cast<const char *>(data), bufferLength);
unsigned int expectedLength = width * height * pixelSize;
// Start with a zlib header ? If not, then it must be base64 encoded
if (bufferLength != expectedLength &&
static_cast<unsigned char>(imageData[0]) != 0x78 &&
static_cast<unsigned char>(imageData[1]) != 0xDA)
{
imageData = QByteArray::fromBase64(imageData);
bufferLength = imageData.size();
}
if (bufferLength != expectedLength &&
static_cast<unsigned char>(imageData[0]) == 0x78 &&
static_cast<unsigned char>(imageData[1]) == 0xDA)
{
imageData.prepend(static_cast<char>((expectedLength >> 0) & 0xFF));
imageData.prepend(static_cast<char>((expectedLength >> 8) & 0xFF));
imageData.prepend(static_cast<char>((expectedLength >> 16) & 0xFF));
imageData.prepend(static_cast<char>((expectedLength >> 24) & 0xFF));
imageData = qUncompress(imageData);
}
QImage image(reinterpret_cast<unsigned char*>(imageData.data()),
width, height, width * pixelSize,
pixelSize == 4 ? QImage::Format_ARGB32 : QImage::Format_RGB888);
if (pixelSize == 4)
{
return image.rgbSwapped();
}
return image.copy();
}

53
ctk/ctkWidgetsUtils.h Normal file
View file

@ -0,0 +1,53 @@
/*=========================================================================
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.apache.org/licenses/LICENSE-2.0.txt
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 __ctkWidgetsUtils_h
#define __ctkWidgetsUtils_h
// Qt includes
#include <QString>
#include <QRect>
class QImage;
#include "ctkWidgetsExport.h"
namespace ctk {
///
/// \ingroup Widgets
/// Create a base 64 image tag. Can be used that way:
/// QString("<img src=\"%1\">").arg(base64HTMLImageTagSrc(myImage);
QString CTK_WIDGETS_EXPORT base64HTMLImageTagSrc(const QImage& image);
///
/// \ingroup Widgets
/// Grab the contents of a QWidget and all its children.
/// Handle correctly the case of QGLWidgets.
/// \sa QWidget::grab
QImage CTK_WIDGETS_EXPORT grabWidget(QWidget* widget, QRect rectangle = QRect());
///
/// \ingroup Widgets
/// Convert an KWidget encoded image into a QImage
/// The data can be base64 encoded and/or zlib compressed.
QImage CTK_WIDGETS_EXPORT kwIconToQImage(const unsigned char *data, int width, int height, int pixelSize, unsigned int bufferLength, int options = 0);
}
#endif

View file

@ -4,9 +4,11 @@ DEFINES += BOOST_ENABLE_ASSERT_HANDLER
SUBDIRS += core \
ctk \
pgsql \
pglab \
pglablib
pglablib \
pglab
CONFIG(debug, debug|release) {
SUBDIRS += tests