qskinny/src/controls/QskPopup.cpp

401 lines
10 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskPopup.h"
#include "QskAspect.h"
#include <QQuickWindow>
2017-11-02 16:25:15 +01:00
#include <QPointer>
2017-07-21 18:21:34 +02:00
#include <QtMath>
QSK_QT_PRIVATE_BEGIN
#include <private/qquickitem_p.h>
#include <private/qquickwindow_p.h>
2017-07-21 18:21:34 +02:00
QSK_QT_PRIVATE_END
QSK_SUBCONTROL( QskPopup, Overlay )
2017-11-02 16:25:15 +01:00
static inline QQuickItem* qskNearestFocusScope( const QQuickItem* item )
{
for ( QQuickItem* scope = item->parentItem();
scope != nullptr; scope = scope->parentItem() )
{
if ( scope->isFocusScope() )
return scope;
}
/*
As the default setting of the root item is to be a focus scope
we usually never get here - beside the flag has been explicitely
disabled in application code.
*/
2017-11-02 16:25:15 +01:00
return nullptr;
}
static void qskSetFocus( QQuickItem* item, bool on )
{
if ( item->window() == nullptr )
return;
/*
For unknown reasons Qt::PopupFocusReason is blocked inside of
QQuickItem::setFocus. So let's bypass it calling
QQuickWindowPrivate::setFocusInScope/clearFocusInScope directly,
*/
const auto scope = qskNearestFocusScope( item );
if ( scope )
{
auto dw = QQuickWindowPrivate::get( item->window() );
if ( on )
dw->setFocusInScope( scope, item, Qt::PopupFocusReason );
else
dw->clearFocusInScope( scope, item, Qt::PopupFocusReason );
}
}
2017-07-21 18:21:34 +02:00
namespace
{
class InputGrabber final : public QQuickItem
{
using Inherited = QQuickItem;
public:
InputGrabber( QskPopup* popup ):
Inherited( popup )
{
setObjectName( QStringLiteral( "QskPopupInputGrabber" ) );
setAcceptedMouseButtons( Qt::AllButtons );
setAcceptHoverEvents( true );
QQuickItemPrivate::get( this )->setTransparentForPositioner( true );
setFlag( QQuickItem::ItemHasContents, false );
/*
The grabber has to be adjusted to the geometry of
the layoutItem, but being a child of he popup its
coordinate system is relative to it and needs to be adjusted
when the position of the popup changes.
*/
auto method = &InputGrabber::updateGeometry;
connect( popup, &QQuickItem::xChanged, this, method );
connect( popup, &QQuickItem::yChanged, this, method );
resetGeometryConnection();
updateGeometry();
}
void resetGeometryConnection()
{
// maybe using QQuickItemChangeListener instead ???
for ( int i = 0; i < 4; i++ )
disconnect( m_connections[i] );
if ( QQuickItem* item = parentItem()->parentItem() )
{
auto method = &InputGrabber::updateGeometry;
m_connections[0] = connect( item, &QQuickItem::xChanged, this, method );
m_connections[1] = connect( item, &QQuickItem::yChanged, this, method );
m_connections[2] = connect( item, &QQuickItem::widthChanged, this, method );
m_connections[3] = connect( item, &QQuickItem::heightChanged, this, method );
}
}
protected:
virtual bool event( QEvent* event ) override final
{
bool doSwallow = false;
switch ( event->type() )
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
doSwallow = true;
break;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
case QEvent::Wheel:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
{
// swallow all events of the parent of the popup
// but not being seen by the popup itself
QPointF pos;
if ( event->type() == QEvent::Wheel )
{
pos = static_cast< QWheelEvent* >( event )->pos();
}
else if ( ( event->type() == QEvent::HoverEnter ) ||
( event->type() == QEvent::HoverLeave ) )
{
pos = static_cast< QHoverEvent* >( event )->posF();
}
else
{
pos = static_cast< QMouseEvent* >( event )->localPos();
}
QQuickItem* popup = parentItem();
doSwallow = !popup->boundingRect().contains(
popup->mapFromItem( this, pos ) );
break;
}
default:
;
}
if ( doSwallow )
{
event->accept();
return true;
}
return Inherited::event( event );
}
private:
void updateGeometry()
{
if ( QskPopup* popup = qobject_cast< QskPopup* >( parentItem() ) )
{
const QRectF r = popup->overlayRect();
if ( r != boundingRect() )
{
setPosition( r.topLeft() );
setSize( r.size() );
// the overlay needs to be repainted
popup->update();
}
}
}
QMetaObject::Connection m_connections[4];
};
}
class QskPopup::PrivateData
{
public:
PrivateData():
inputGrabber( nullptr ),
2017-11-02 16:25:15 +01:00
isModal( false ),
isOpen( false ),
autoGrabFocus( true )
2017-07-21 18:21:34 +02:00
{
}
InputGrabber* inputGrabber;
2017-11-02 16:25:15 +01:00
QPointer< QQuickItem > initialFocusItem;
2017-07-21 18:21:34 +02:00
bool isModal : 1;
2017-11-02 16:25:15 +01:00
bool isOpen : 1;
bool autoGrabFocus : 1;
2017-07-21 18:21:34 +02:00
};
QskPopup::QskPopup( QQuickItem* parent ):
Inherited( parent ),
m_data( new PrivateData() )
{
setAcceptedMouseButtons( Qt::AllButtons );
// we don't want to be resized by layout code
setTransparentForPositioner( true );
setFlag( ItemIsFocusScope );
setTabFence( true );
2017-11-02 16:25:15 +01:00
setFocusPolicy( Qt::ClickFocus );
2017-07-21 18:21:34 +02:00
}
QskPopup::~QskPopup()
{
}
QRectF QskPopup::overlayRect() const
{
const auto item = parentItem();
if ( item && isVisible() && m_data->isModal && hasOverlay() )
{
const QPointF pos = mapFromItem( item, QPointF() );
return QRectF( pos, QSizeF( item->width(), item->height() ) );
}
return QRectF();
}
void QskPopup::updateInputGrabber()
{
if ( parentItem() && isVisible() && m_data->isModal )
{
if ( m_data->inputGrabber == nullptr )
m_data->inputGrabber = new InputGrabber( this );
}
else
{
delete m_data->inputGrabber;
m_data->inputGrabber = nullptr;
}
}
void QskPopup::setModal( bool on )
{
if ( on == m_data->isModal )
return;
m_data->isModal = on;
updateInputGrabber();
Q_EMIT modalChanged();
}
bool QskPopup::isModal() const
{
return m_data->isModal;
}
void QskPopup::setOverlay( bool on )
{
if ( hasOverlay() != on )
{
const auto subControl = effectiveSubcontrol( QskPopup::Overlay );
setFlagHint( subControl | QskAspect::Style, on );
update();
Q_EMIT overlayChanged();
}
}
bool QskPopup::hasOverlay() const
{
return flagHint< bool >( QskPopup::Overlay | QskAspect::Style, true );
}
2017-11-02 16:25:15 +01:00
void QskPopup::grabFocus( bool on )
{
/*
Note, that we are grabbing the local focus, what only
has an effect on the active focus, when all surrounding
focus scopes already have the focus.
*/
if ( on == hasFocus() )
return;
if ( on )
{
if ( auto scope = qskNearestFocusScope( this ) )
{
m_data->initialFocusItem = scope->scopedFocusItem();
qskSetFocus( this, true );
2017-11-02 16:25:15 +01:00
}
}
else
{
QQuickItem* focusItem = m_data->initialFocusItem;
m_data->initialFocusItem = nullptr;
if ( focusItem == nullptr )
focusItem = nextItemInFocusChain( false );
if ( focusItem )
qskSetFocus( focusItem, true );
2017-11-02 16:25:15 +01:00
else
qskSetFocus( this, false );
2017-11-02 16:25:15 +01:00
}
}
2017-07-21 18:21:34 +02:00
bool QskPopup::event( QEvent* event )
{
bool ok = Inherited::event( event );
switch( event->type() )
{
case QEvent::Wheel:
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
// swallow the event
event->accept();
break;
}
default:
{
break;
}
}
return ok;
}
2017-11-02 16:25:15 +01:00
void QskPopup::updateLayout()
{
if ( !m_data->isOpen )
{
if ( m_data->autoGrabFocus )
grabFocus( true );
m_data->isOpen = true;
}
Inherited::updateLayout();
}
2017-07-21 18:21:34 +02:00
void QskPopup::itemChange( QQuickItem::ItemChange change,
const QQuickItem::ItemChangeData& value )
{
Inherited::itemChange( change, value );
switch ( change )
{
case QQuickItem::ItemVisibleHasChanged:
{
updateInputGrabber();
2017-11-02 16:25:15 +01:00
if ( !value.boolValue )
{
m_data->isOpen = false;
grabFocus( false );
}
2017-07-21 18:21:34 +02:00
break;
}
case QQuickItem::ItemParentHasChanged:
{
delete m_data->inputGrabber;
m_data->inputGrabber = nullptr;
updateInputGrabber();
break;
}
2017-11-02 16:25:15 +01:00
case QQuickItem::ItemActiveFocusHasChanged:
{
if ( !hasFocus() )
m_data->initialFocusItem = nullptr;
break;
}
2017-07-21 18:21:34 +02:00
default:
;
}
}
#include "moc_QskPopup.cpp"