qskinny/src/controls/QskPopup.cpp

423 lines
9.7 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"
2018-07-02 08:08:38 +02:00
#include "QskInputGrabber.h"
2017-07-21 18:21:34 +02:00
#include "QskAspect.h"
#include "QskWindow.h"
2018-07-02 08:08:38 +02:00
#include "QskQuick.h"
2017-07-21 18:21:34 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qquickwindow_p.h>
2017-07-21 18:21:34 +02:00
QSK_QT_PRIVATE_END
QSK_SUBCONTROL( QskPopup, Overlay )
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,
*/
if ( const auto scope = qskNearestFocusScope( item ) )
{
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
{
2018-07-02 08:08:38 +02:00
class InputGrabber final : public QskInputGrabber
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
using Inherited = QskInputGrabber;
2017-07-21 18:21:34 +02:00
public:
2018-07-02 08:08:38 +02:00
InputGrabber( QskPopup* parent ):
Inherited( parent )
2017-07-21 18:21:34 +02:00
{
}
2018-07-31 17:32:25 +02:00
void geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry ) override
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
Inherited::geometryChanged( newGeometry, oldGeometry );
2017-07-21 18:21:34 +02:00
2018-07-02 08:08:38 +02:00
if ( auto popup = static_cast< QskPopup* >( parentItem() ) )
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
if ( popup->hasOverlay() )
popup->update();
2017-07-21 18:21:34 +02:00
}
}
2018-07-31 17:32:25 +02:00
bool event( QEvent* event ) override
{
2018-07-02 08:08:38 +02:00
bool ok = Inherited::event( event );
if ( event->type() == QEvent::MouseButtonPress )
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
if ( auto popup = static_cast< QskPopup* >( parentItem() ) )
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
if ( event->isAccepted() &&
( popup->popupFlags() & QskPopup::CloseOnPressOutside ) )
{
popup->close();
}
2017-07-21 18:21:34 +02:00
}
}
2018-07-02 08:08:38 +02:00
return ok;
2017-07-21 18:21:34 +02:00
}
};
}
class QskPopup::PrivateData
{
public:
PrivateData():
inputGrabber( nullptr ),
2018-07-02 08:08:38 +02:00
flags( 0 ),
2017-11-02 16:25:15 +01:00
isModal( false ),
isOpen( false ),
autoGrabFocus( true ),
handoverFocus( true )
2017-07-21 18:21:34 +02:00
{
}
InputGrabber* inputGrabber;
2017-11-02 16:25:15 +01:00
2018-07-02 08:08:38 +02:00
int flags : 4;
2017-07-21 18:21:34 +02:00
bool isModal : 1;
bool isOpen : 1;
const bool autoGrabFocus : 1;
const bool handoverFocus : 1;
2017-07-21 18:21:34 +02:00
};
QskPopup::QskPopup( QQuickItem* parent ):
Inherited( parent ),
m_data( new PrivateData() )
{
// we need to stop event propagation
2017-07-21 18:21:34 +02:00
setAcceptedMouseButtons( Qt::AllButtons );
setWheelEnabled( true );
2017-07-21 18:21:34 +02:00
// we don't want to be resized by layout code
setTransparentForPositioner( true );
2018-01-16 12:13:38 +01:00
setFlag( ItemIsFocusScope, true );
2017-07-21 18:21:34 +02:00
setTabFence( true );
setFocusPolicy( Qt::StrongFocus );
2017-07-21 18:21:34 +02:00
}
QskPopup::~QskPopup()
{
}
2018-07-02 08:08:38 +02:00
void QskPopup::open()
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
setFading( true );
}
2018-07-02 08:08:38 +02:00
void QskPopup::close()
{
2018-07-02 08:08:38 +02:00
const bool wasOpen = m_data->isOpen;
m_data->isOpen = false;
setFading( false );
if ( wasOpen )
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
Q_EMIT closed();
if ( testPopupFlag( DeleteOnClose ) )
deleteLater();
2017-07-21 18:21:34 +02:00
}
2018-07-02 08:08:38 +02:00
}
void QskPopup::setFading( bool on )
{
setVisible( on );
}
bool QskPopup::isOpen() const
{
return m_data->isOpen;
}
QRectF QskPopup::overlayRect() const
{
if ( hasOverlay() && m_data->inputGrabber )
return m_data->inputGrabber->grabberRect();
2017-07-21 18:21:34 +02:00
return QRectF();
}
void QskPopup::updateInputGrabber()
{
if ( parentItem() && isVisible()
&& ( isModal() || testPopupFlag( CloseOnPressOutside ) ) )
2017-07-21 18:21:34 +02:00
{
if ( m_data->inputGrabber == nullptr )
{
const auto children = childItems();
2017-07-21 18:21:34 +02:00
m_data->inputGrabber = new InputGrabber( this );
if ( !children.isEmpty() )
{
/*
Even if the input grabber has no content it has an effect
on QQuickItem::childAt. Also tools like Squish struggle with
sorting out items without content.
2018-02-07 15:39:46 +01:00
So let's better move the grabber to the beginning.
*/
m_data->inputGrabber->stackBefore( children.first() );
}
}
2017-07-21 18:21:34 +02:00
}
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( on );
2017-07-21 18:21:34 +02:00
}
bool QskPopup::isModal() const
{
return m_data->isModal;
}
2018-07-02 08:08:38 +02:00
void QskPopup::setPopupFlags( PopupFlags flags )
{
m_data->flags = flags;
}
QskPopup::PopupFlags QskPopup::popupFlags() const
{
return static_cast< PopupFlags >( m_data->flags );
}
void QskPopup::setPopupFlag( PopupFlag flag, bool on )
{
auto flags = m_data->flags;
if ( on )
flags |= flag;
else
flags &= ~flag;
if ( flags != m_data->flags )
{
m_data->flags = flags;
updateInputGrabber();
}
}
bool QskPopup::testPopupFlag( PopupFlag flag ) const
{
return m_data->flags & flag;
}
2017-07-21 18:21:34 +02:00
void QskPopup::setOverlay( bool on )
{
if ( hasOverlay() != on )
{
const auto subControl = effectiveSubcontrol( QskPopup::Overlay );
setFlagHint( subControl | QskAspect::Style, on );
update();
Q_EMIT overlayChanged( on );
2017-07-21 18:21:34 +02:00
}
}
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 )
{
if ( on == hasFocus() )
return;
if ( on )
{
qskSetFocus( this, true );
2017-11-02 16:25:15 +01:00
}
else
{
QQuickItem* successor = nullptr;
2017-11-02 16:25:15 +01:00
if ( m_data->handoverFocus )
{
/*
Qt/Quick does not handover the focus to another item,
when the active focus gets lost. For the situation of
a popup being closed we try to do it.
*/
successor = focusSuccessor();
}
2017-11-02 16:25:15 +01:00
if ( successor )
qskSetFocus( successor, true );
if ( hasFocus() )
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::KeyPress:
case QEvent::KeyRelease:
2017-07-21 18:21:34 +02:00
case QEvent::Wheel:
2017-07-21 18:21:34 +02:00
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
case QEvent::MouseButtonRelease:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
2017-07-21 18:21:34 +02:00
{
// swallow the event
event->accept();
if ( auto w = qobject_cast< QskWindow* >( window() ) )
w->setEventAcceptance( QskWindow::EventPropagationStopped );
2017-07-21 18:21:34 +02:00
break;
}
default:
{
/*
Don't accept touch events otherwise we don't receive the
synthesized mouse events and need to handle both type of
events from now on.
But by accepting the mouse event propagation of the touch
events also stops.
*/
2017-07-21 18:21:34 +02:00
break;
}
}
return ok;
}
void QskPopup::focusInEvent( QFocusEvent* event )
{
Inherited::focusInEvent( event );
if ( isFocusScope() && isTabFence() && ( scopedFocusItem() == nullptr ) )
{
if ( event->reason() == Qt::PopupFocusReason )
{
/*
When receiving the focus we need to have a focused
item, so that the tab focus chain has a starting point.
But we only do it when the reason is Qt::PopupFocusReason
as we also receive focus events during the process of reparenting
children and setting the focus there can leave the item tree
in an invalid state.
*/
if ( auto focusItem = nextItemInFocusChain( true ) )
{
if ( qskIsItemComplete( focusItem )
&& qskIsAncestorOf( this, focusItem ) )
{
focusItem->setFocus( true );
}
}
}
}
}
void QskPopup::focusOutEvent( QFocusEvent* event )
{
Inherited::focusOutEvent( event );
}
QQuickItem* QskPopup::focusSuccessor() const
{
if ( const auto scope = qskNearestFocusScope( this ) )
{
const auto children = qskPaintOrderChildItems( scope );
for ( auto it = children.crbegin(); it != children.crend(); ++it)
{
auto child = *it;
2018-01-24 10:14:50 +01:00
if ( ( child != this ) && child->isFocusScope()
&& child->activeFocusOnTab() && child->isVisible() )
{
return child;
2018-01-24 10:14:50 +01:00
}
}
}
return nullptr;
}
void QskPopup::aboutToShow()
2017-11-02 16:25:15 +01:00
{
2018-07-02 08:08:38 +02:00
m_data->isOpen = true;
2017-11-02 16:25:15 +01:00
2018-07-02 08:08:38 +02:00
if ( m_data->autoGrabFocus )
{
// What to do, when we are hidden below another popup ??
grabFocus( true );
2017-11-02 16:25:15 +01:00
}
Inherited::aboutToShow();
2017-11-02 16:25:15 +01:00
}
2017-07-21 18:21:34 +02:00
void QskPopup::itemChange( QQuickItem::ItemChange change,
const QQuickItem::ItemChangeData& value )
{
Inherited::itemChange( change, value );
2018-07-02 08:08:38 +02:00
if ( change == QQuickItem::ItemVisibleHasChanged )
2017-07-21 18:21:34 +02:00
{
2018-07-02 08:08:38 +02:00
if ( !value.boolValue )
2017-07-21 18:21:34 +02:00
{
updateInputGrabber();
2018-07-02 08:08:38 +02:00
grabFocus( false );
if ( m_data->isOpen )
2017-11-02 16:25:15 +01:00
{
2018-07-02 08:08:38 +02:00
if ( testPopupFlag( CloseOnHide ) )
close();
2017-11-02 16:25:15 +01:00
}
2017-07-21 18:21:34 +02:00
}
}
}
#include "moc_QskPopup.cpp"