qskinny/src/controls/QskPopup.cpp

418 lines
9.4 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-02 08:08:38 +02:00
virtual void geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry ) override final
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-02 08:08:38 +02:00
virtual bool event( QEvent* event ) override final
{
2018-07-02 08:08:38 +02:00
bool ok = Inherited::event( event );
2018-07-02 08:08:38 +02:00
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()
{
2018-07-02 08:08:38 +02:00
if ( 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::TouchBegin:
case QEvent::TouchCancel:
case QEvent::TouchUpdate:
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:
{
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"