support for input panels per window added

This commit is contained in:
Uwe Rathmann 2018-06-12 18:43:14 +02:00
parent 1fc4d3af18
commit 54f4655698
7 changed files with 305 additions and 327 deletions

View File

@ -57,6 +57,8 @@ public:
virtual bool hasCapability( Capability ) const override;
virtual void update( Qt::InputMethodQueries ) override;
Q_INVOKABLE void update( const QQuickItem*, Qt::InputMethodQueries );
virtual void invokeAction( QInputMethod::Action, int ) override;
virtual QRectF keyboardRect() const override;
@ -64,6 +66,8 @@ public:
virtual void showInputPanel() override;
virtual void hideInputPanel() override;
Q_INVOKABLE void setInputPanelVisible( const QQuickItem*, bool );
virtual bool isInputPanelVisible() const override;
virtual void reset() override;
@ -76,8 +80,6 @@ public:
virtual bool filterEvent( const QEvent* ) override;
Q_INVOKABLE void update( const QQuickItem*, Qt::InputMethodQueries );
protected:
virtual bool event( QEvent* ) override;
@ -164,10 +166,7 @@ void QskPlatformInputContext::invokeAction(
QInputMethod::Action action, int cursorPosition )
{
if ( m_context )
{
if ( action == QInputMethod::Click )
m_context->processClickAt( cursorPosition );
}
m_context->invokeAction( action, cursorPosition );
}
QRectF QskPlatformInputContext::keyboardRect() const
@ -188,20 +187,25 @@ bool QskPlatformInputContext::isAnimating() const
void QskPlatformInputContext::showInputPanel()
{
if ( m_context )
m_context->setActive( true );
setInputPanelVisible( nullptr, true );
}
void QskPlatformInputContext::hideInputPanel()
{
setInputPanelVisible( nullptr, false );
}
void QskPlatformInputContext::setInputPanelVisible(
const QQuickItem* item, bool on )
{
if ( m_context )
m_context->setActive( false );
m_context->setInputPanelVisible( item, on );
}
bool QskPlatformInputContext::isInputPanelVisible() const
{
if ( m_context )
return m_context->isActive();
return m_context->isInputPanelVisible();
return false;
}

View File

@ -303,17 +303,13 @@ int main( int argc, char* argv[] )
qskDialog->setPolicy( QskDialog::EmbeddedBox );
#endif
#if 0
QskInputContext::setInputEngine( ... );
#endif
Window window1;
window1.setObjectName( "Window 1" );
window1.setColor( "PapayaWhip" );
window1.resize( 600, 600 );
window1.show();
#if 0
#if 1
Window window2;
window2.setObjectName( "Window 2" );
window2.setColor( "Pink" );

View File

@ -188,6 +188,43 @@ void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries querie
}
}
void qskInputMethodSetVisible( const QQuickItem* item, bool on )
{
static QPlatformInputContext* context = nullptr;
static int methodId = -1;
auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
if ( inputContext == nullptr )
{
context = nullptr;
methodId = -1;
return;
}
if ( inputContext != context )
{
context = inputContext;
methodId = inputContext->metaObject()->indexOfMethod(
"setInputPanelVisible(const QQuickItem*,bool)" );
}
if ( methodId >= 0 )
{
inputContext->metaObject()->method( methodId ).invoke(
inputContext, Qt::DirectConnection,
Q_ARG( const QQuickItem*, item ),
Q_ARG( bool, on ) );
}
else
{
if ( on )
QGuiApplication::inputMethod()->show();
else
QGuiApplication::inputMethod()->hide();
}
}
QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item )
{
if ( item )

View File

@ -31,6 +31,7 @@ QSK_EXPORT void qskForceActiveFocus( QQuickItem*, Qt::FocusReason );
QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* );
QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries );
QSK_EXPORT void qskInputMethodSetVisible( const QQuickItem*, bool );
QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* );
QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* );

View File

@ -302,9 +302,7 @@ void QskTextInput::keyPressEvent( QKeyEvent* event )
#if 1
case Qt::Key_Escape:
{
QGuiApplication::inputMethod()->hide();
setEditing( false );
break;
}
#endif
@ -563,7 +561,7 @@ void QskTextInput::setEditing( bool on )
updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorRectangle);
QGuiApplication::inputMethod()->inputDirection
#endif
inputMethod->show();
qskInputMethodSetVisible( this, true );
}
else
{
@ -579,7 +577,7 @@ void QskTextInput::setEditing( bool on )
#if 0
inputMethod->reset();
#endif
inputMethod->hide();
qskInputMethodSetVisible( this, false );
#if 1
qskForceActiveFocus( this, Qt::PopupFocusReason );
#endif

View File

@ -16,6 +16,7 @@
#include <QPointer>
#include <QGuiApplication>
#include <QMap>
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
@ -71,6 +72,85 @@ namespace
private:
QskInputPanelBox* m_box;
};
class Channel
{
public:
// item receiving the input
QPointer< QQuickItem > item;
// panel for inserting the input
QPointer< QskInputPanel > panel;
// popup or window embedding the panel
QPointer< QskPopup > popup;
QPointer< QskWindow > window;
};
class ChannelTable
{
public:
inline Channel* currentChannel() const
{
const auto object = QGuiApplication::focusObject();
return channel( qobject_cast< const QQuickItem* >( object ) );
}
inline Channel* channel( const QQuickWindow* window ) const
{
if ( window )
{
auto it = m_map.constFind( window );
if ( it != m_map.constEnd() )
return const_cast< Channel* >( &it.value() );
}
return nullptr;
}
inline Channel* channel( const QQuickItem* item ) const
{
if ( item )
{
auto channel = this->channel( item->window() );
if ( channel && channel->item == item )
return channel;
}
return nullptr;
}
inline Channel* ancestorChannel( const QQuickItem* item ) const
{
for ( auto it = m_map.constBegin();
it != m_map.constEnd(); ++it )
{
if ( const auto panel = it.value().panel )
{
if ( ( item == panel )
|| qskIsAncestorOf( panel, item ) )
{
return const_cast< Channel*>( &it.value() );
}
}
}
return nullptr;
}
inline Channel* insert( const QQuickWindow* window )
{
return &m_map[ window ];
}
inline void remove( const QQuickWindow* window )
{
m_map.remove( window );
}
private:
QMap< const QQuickWindow*, Channel > m_map;
};
}
static QPointer< QskInputContext > qskInputContext = nullptr;
@ -116,14 +196,80 @@ QskInputContext* QskInputContext::instance()
class QskInputContext::PrivateData
{
public:
// item receiving the input
QPointer< QQuickItem > inputItem;
QPointer< QskInputPanel > panel;
// popup or window embedding the panel
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
inline QskInputPanel* createPanel( QskInputContext* context ) const
{
QskInputPanel* panel = nullptr;
if ( this->factory )
panel = this->factory->createPanel();
if ( panel == nullptr )
panel = new Panel();
connect( panel, &QskInputPanel::visibleChanged,
context, &QskInputContext::activeChanged );
connect( panel, &QskInputPanel::localeChanged,
context, [] { qskSendToPlatformContext( QEvent::LocaleChange ); } );
return panel;
}
inline QskPopup* createPopup( QskInputPanel* panel ) const
{
auto popup = new QskPopup();
popup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false );
popup->setModal( true );
auto box = new QskLinearBox( popup );
box->addItem( panel );
const auto alignment = panel->alignment() & Qt::AlignVertical_Mask;
popup->setOverlay( alignment == Qt::AlignVCenter );
switch( alignment )
{
case Qt::AlignTop:
{
box->setExtraSpacingAt( Qt::BottomEdge | Qt::LeftEdge | Qt::RightEdge );
break;
}
case Qt::AlignVCenter:
{
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
break;
}
case Qt::AlignBottom:
default:
{
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
}
return popup;
}
inline QskWindow* createWindow( QskInputPanel* panel ) const
{
auto window = new QskWindow();
window->setFlags( window->flags() & Qt::Dialog );
//window->setModality( Qt::ApplicationModal );
window->setAutoLayoutChildren( true );
#if 0
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
#endif
panel->setParentItem( window->contentItem() );
return window;
}
ChannelTable channels;
QPointer< QskInputContextFactory > factory;
};
@ -166,313 +312,161 @@ QskTextPredictor* QskInputContext::textPredictor( const QLocale& locale )
void QskInputContext::update( const QQuickItem* item, Qt::InputMethodQueries queries )
{
if ( m_data->inputItem == nullptr )
return;
if ( item == nullptr )
{
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
#if 1
// those are coming from QQuickWindow based on focus changes
return;
#endif
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
}
auto channel = m_data->channels.channel( item );
if ( channel == nullptr )
return;
if ( queries & Qt::ImEnabled )
{
QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event );
QCoreApplication::sendEvent( channel->item, &event );
if ( !event.value( Qt::ImEnabled ).toBool() )
{
hidePanel();
hidePanel( item );
return;
}
}
if ( m_data->panel )
m_data->panel->updateInputPanel( queries );
channel->panel->updateInputPanel( queries );
}
QRectF QskInputContext::panelRect() const
{
if ( m_data->inputPopup )
return m_data->inputPopup->geometry();
/*
As we can have more than panel at the same time we
better don't return any geometry
*/
return QRectF();
}
QskPopup* QskInputContext::createEmbeddingPopup( QskInputPanel* panel )
void QskInputContext::showPanel( const QQuickItem* item )
{
auto popup = new QskPopup();
popup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false );
popup->setModal( true );
auto box = new QskLinearBox( popup );
box->addItem( panel );
const auto alignment = panel->alignment() & Qt::AlignVertical_Mask;
popup->setOverlay( alignment == Qt::AlignVCenter );
switch( alignment )
{
case Qt::AlignTop:
{
box->setExtraSpacingAt( Qt::BottomEdge | Qt::LeftEdge | Qt::RightEdge );
break;
}
case Qt::AlignVCenter:
{
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
break;
}
case Qt::AlignBottom:
default:
{
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
}
}
return popup;
}
QskWindow* QskInputContext::createEmbeddingWindow( QskInputPanel* panel )
{
auto window = new QskWindow();
window->setFlags( window->flags() & Qt::Dialog );
//window->setModality( Qt::ApplicationModal );
window->setAutoLayoutChildren( true );
#if 0
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
#endif
panel->setParentItem( window->contentItem() );
return window;
}
void QskInputContext::ensurePanel()
{
if ( m_data->panel )
if ( item == nullptr )
return;
QskInputPanel* panel = nullptr;
if ( m_data->factory )
panel = m_data->factory->createPanel();
if ( panel == nullptr )
panel = new Panel();
panel->setParent( const_cast< QskInputContext* >( this ) );
connect( panel, &QskInputPanel::visibleChanged,
this, &QskInputContext::activeChanged,
Qt::UniqueConnection );
connect( panel, &QskInputPanel::localeChanged,
this, [] { qskSendToPlatformContext( QEvent::LocaleChange ); },
Qt::UniqueConnection );
m_data->panel = panel;
}
void QskInputContext::showPanel()
{
auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() );
if ( focusItem == nullptr )
return;
ensurePanel();
if ( ( focusItem == m_data->panel )
|| qskIsAncestorOf( m_data->panel, focusItem ) )
if ( m_data->channels.ancestorChannel( item ) )
{
// ignore: usually the input proxy of the panel
// We are inside of an existing panel
return;
}
m_data->inputItem = focusItem;
if ( auto channel = m_data->channels.channel( item->window() ) )
{
if ( channel->item == item )
return;
hidePanel( channel->item );
}
auto panel = m_data->createPanel( this );
auto channel = m_data->channels.insert( item->window() );
channel->item = const_cast< QQuickItem*>( item );
channel->panel = panel;
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{
// The input panel is embedded in a top level window
delete m_data->inputPopup;
auto window = m_data->createWindow( panel );
if ( m_data->inputWindow == nullptr )
QSize size = window->effectivePreferredSize();
if ( size.isEmpty() )
{
auto window = createEmbeddingWindow( m_data->panel );
if ( window )
{
QSize size = window->effectivePreferredSize();
if ( size.isEmpty() )
{
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
window->resize( size );
window->show();
window->setDeleteOnClose( true );
window->installEventFilter( this );
}
m_data->inputWindow = window;
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
window->resize( size );
window->show();
window->setDeleteOnClose( true );
channel->window = window;
}
else
{
// The input panel is embedded in a popup
delete m_data->inputWindow;
auto popup = m_data->createPopup( panel );
if ( m_data->inputPopup == nullptr )
{
auto popup = createEmbeddingPopup( m_data->panel );
if ( popup )
{
popup->setParentItem( m_data->inputItem->window()->contentItem() );
if ( popup->parent() == nullptr )
popup->setParent( this );
popup->setVisible( true );
popup->installEventFilter( this );
}
m_data->inputPopup = popup;
}
}
m_data->panel->attachInputItem( m_data->inputItem );
}
void QskInputContext::hidePanel()
{
if ( m_data->inputPopup )
{
popup->setParentItem( item->window()->contentItem() );
popup->setParent( this );
#if 1
if ( auto focusItem = m_data->inputPopup->scopedFocusItem() )
{
/*
Qt bug: QQuickItem::hasFocus() is not cleared
when the corresponding focusScope gets deleted.
Usually no problem, but here the focusItem is no
child and will be reused with a different parent
later.
*/
focusItem->setFocus( false );
}
popup->setVisible( true );
#endif
channel->popup = popup;
}
if ( m_data->panel )
{
m_data->panel->setParentItem( nullptr );
m_data->panel->disconnect( this );
m_data->panel->attachInputItem( nullptr );
}
if ( m_data->inputPopup )
m_data->inputPopup->deleteLater();
if ( m_data->inputWindow )
{
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
window->removeEventFilter( this );
window->close(); // deleteOnClose is set
}
m_data->inputItem = nullptr;
panel->attachInputItem( const_cast< QQuickItem* >( item ) );
}
void QskInputContext::setActive( bool on )
void QskInputContext::hidePanel( const QQuickItem* item )
{
if ( on )
showPanel();
else
hidePanel();
if ( item == nullptr )
return;
if ( auto channel = m_data->channels.channel( item ) )
{
if ( channel->popup )
channel->popup->deleteLater();
if ( channel->window )
channel->window->close(); // deleteOnClose is set
m_data->channels.remove( item->window() );
}
}
bool QskInputContext::isActive() const
void QskInputContext::setInputPanelVisible( const QQuickItem* item, bool on )
{
const QQuickWindow* window = m_data->inputWindow;
if ( window == nullptr && m_data->inputPopup )
window = m_data->inputPopup->window();
// called from inside the controls
return window && window->isVisible();
if ( item == nullptr )
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
if ( item )
{
if ( on )
showPanel( item );
else
hidePanel( item );
}
}
bool QskInputContext::isInputPanelVisible() const
{
return m_data->channels.currentChannel() != nullptr;
}
QLocale QskInputContext::locale() const
{
if ( m_data->panel )
return m_data->panel->locale();
if ( auto channel = m_data->channels.currentChannel() )
{
if ( channel->panel )
return channel->panel->locale();
}
return QLocale();
}
void QskInputContext::setFocusObject( QObject* focusObject )
void QskInputContext::setFocusObject( QObject* )
{
if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject )
{
// we don't care
return;
}
const auto w = m_data->inputItem->window();
if ( w == nullptr )
return;
if ( m_data->inputWindow )
{
if ( focusObject == nullptr )
{
if ( m_data->inputItem->hasFocus() )
{
/*
As long as the focus is nowhere and
the local focus stay on the input item
we don't care
*/
return;
}
}
else
{
const auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if ( focusItem && focusItem->window() == m_data->inputWindow )
return;
}
}
else if ( m_data->inputPopup )
{
if ( w->contentItem()->scopedFocusItem() == m_data->inputPopup )
{
/*
As long as the focus stays inside the inputPopup
we don't care
*/
return;
}
}
hidePanel();
m_data->inputItem = nullptr;
}
void QskInputContext::processClickAt( int cursorPosition )
void QskInputContext::invokeAction(
QInputMethod::Action, int cursorPosition )
{
// called from qquicktextinput/qquicktextedit
Q_UNUSED( cursorPosition );
}
@ -485,53 +479,6 @@ void QskInputContext::commitPrediction( bool )
*/
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
{
if ( object == m_data->inputWindow )
{
switch( event->type() )
{
case QEvent::Move:
{
Q_EMIT panelRectChanged();
break;
}
case QEvent::Resize:
{
if ( m_data->panel )
m_data->panel->setSize( m_data->inputWindow->size() );
break;
}
case QEvent::DeferredDelete:
{
m_data->inputWindow = nullptr;
break;
}
default:
break;
}
}
else if ( object == m_data->inputPopup )
{
switch( static_cast< int >( event->type() ) )
{
case QskEvent::GeometryChange:
{
Q_EMIT panelRectChanged();
break;
}
case QEvent::DeferredDelete:
{
m_data->inputPopup = nullptr;
break;
}
}
}
return Inherited::eventFilter( object, event );
}
QskInputContextFactory::QskInputContextFactory( QObject* parent ):
QObject( parent )
{

View File

@ -9,6 +9,7 @@
#include "QskGlobal.h"
#include <QObject>
#include <Qt>
#include <QInputMethod>
#include <memory>
class QskTextPredictor;
@ -45,8 +46,8 @@ public:
QRectF panelRect() const;
void setActive( bool );
bool isActive() const;
void setInputPanelVisible( const QQuickItem*, bool );
bool isInputPanelVisible() const;
QLocale locale() const;
@ -62,24 +63,18 @@ Q_SIGNALS:
void panelRectChanged();
protected:
virtual bool eventFilter( QObject*, QEvent* ) override;
virtual QskPopup* createEmbeddingPopup( QskInputPanel* );
virtual QskWindow* createEmbeddingWindow( QskInputPanel* );
virtual void showPanel();
virtual void hidePanel();
virtual void showPanel( const QQuickItem* );
virtual void hidePanel( const QQuickItem* );
private:
friend class QskPlatformInputContext;
// called from QskPlatformInputContext
void setFocusObject( QObject* );
void update( const QQuickItem*, Qt::InputMethodQueries );
void processClickAt( int cursorPosition );
void commitPrediction( bool );
virtual void setFocusObject( QObject* );
virtual void update( const QQuickItem*, Qt::InputMethodQueries );
virtual void invokeAction( QInputMethod::Action, int cursorPosition );
void ensurePanel();
void commitPrediction( bool );
class PrivateData;
std::unique_ptr< PrivateData > m_data;