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

View File

@ -303,17 +303,13 @@ int main( int argc, char* argv[] )
qskDialog->setPolicy( QskDialog::EmbeddedBox ); qskDialog->setPolicy( QskDialog::EmbeddedBox );
#endif #endif
#if 0
QskInputContext::setInputEngine( ... );
#endif
Window window1; Window window1;
window1.setObjectName( "Window 1" ); window1.setObjectName( "Window 1" );
window1.setColor( "PapayaWhip" ); window1.setColor( "PapayaWhip" );
window1.resize( 600, 600 ); window1.resize( 600, 600 );
window1.show(); window1.show();
#if 0 #if 1
Window window2; Window window2;
window2.setObjectName( "Window 2" ); window2.setObjectName( "Window 2" );
window2.setColor( "Pink" ); 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 ) QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item )
{ {
if ( 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 QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* );
QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries ); 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* qskItemNode( const QQuickItem* );
QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* );

View File

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

View File

@ -16,6 +16,7 @@
#include <QPointer> #include <QPointer>
#include <QGuiApplication> #include <QGuiApplication>
#include <QMap>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h> #include <private/qguiapplication_p.h>
@ -71,6 +72,85 @@ namespace
private: private:
QskInputPanelBox* m_box; 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; static QPointer< QskInputContext > qskInputContext = nullptr;
@ -116,14 +196,80 @@ QskInputContext* QskInputContext::instance()
class QskInputContext::PrivateData class QskInputContext::PrivateData
{ {
public: public:
// item receiving the input
QPointer< QQuickItem > inputItem;
QPointer< QskInputPanel > panel;
// popup or window embedding the panel inline QskInputPanel* createPanel( QskInputContext* context ) const
QskPopup* inputPopup = nullptr; {
QskWindow* inputWindow = nullptr; 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; QPointer< QskInputContextFactory > factory;
}; };
@ -166,150 +312,72 @@ QskTextPredictor* QskInputContext::textPredictor( const QLocale& locale )
void QskInputContext::update( const QQuickItem* item, Qt::InputMethodQueries queries ) void QskInputContext::update( const QQuickItem* item, Qt::InputMethodQueries queries )
{ {
if ( m_data->inputItem == nullptr )
return;
if ( item == nullptr ) if ( item == nullptr )
{ {
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
#if 1
// those are coming from QQuickWindow based on focus changes // those are coming from QQuickWindow based on focus changes
return; item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
#endif
} }
auto channel = m_data->channels.channel( item );
if ( channel == nullptr )
return;
if ( queries & Qt::ImEnabled ) if ( queries & Qt::ImEnabled )
{ {
QInputMethodQueryEvent event( Qt::ImEnabled ); QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event ); QCoreApplication::sendEvent( channel->item, &event );
if ( !event.value( Qt::ImEnabled ).toBool() ) if ( !event.value( Qt::ImEnabled ).toBool() )
{ {
hidePanel(); hidePanel( item );
return; return;
} }
} }
if ( m_data->panel ) channel->panel->updateInputPanel( queries );
m_data->panel->updateInputPanel( queries );
} }
QRectF QskInputContext::panelRect() const 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(); return QRectF();
} }
QskPopup* QskInputContext::createEmbeddingPopup( QskInputPanel* panel ) void QskInputContext::showPanel( const QQuickItem* item )
{ {
auto popup = new QskPopup(); if ( item == nullptr )
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 )
return; return;
QskInputPanel* panel = nullptr; if ( m_data->channels.ancestorChannel( item ) )
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 ) )
{ {
// ignore: usually the input proxy of the panel // We are inside of an existing panel
return; 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 ) if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{ {
// The input panel is embedded in a top level window // 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 )
{
auto window = createEmbeddingWindow( m_data->panel );
if ( window )
{
QSize size = window->effectivePreferredSize(); QSize size = window->effectivePreferredSize();
if ( size.isEmpty() ) if ( size.isEmpty() )
{ {
@ -321,158 +389,84 @@ void QskInputContext::showPanel()
window->show(); window->show();
window->setDeleteOnClose( true ); window->setDeleteOnClose( true );
window->installEventFilter( this );
}
m_data->inputWindow = window; channel->window = window;
}
} }
else else
{ {
// The input panel is embedded in a popup // The input panel is embedded in a popup
delete m_data->inputWindow; auto popup = m_data->createPopup( panel );
if ( m_data->inputPopup == nullptr ) popup->setParentItem( item->window()->contentItem() );
{
auto popup = createEmbeddingPopup( m_data->panel );
if ( popup )
{
popup->setParentItem( m_data->inputItem->window()->contentItem() );
if ( popup->parent() == nullptr )
popup->setParent( this ); 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 )
{
#if 1 #if 1
if ( auto focusItem = m_data->inputPopup->scopedFocusItem() ) popup->setVisible( true );
{
/*
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 );
}
#endif #endif
channel->popup = popup;
} }
if ( m_data->panel ) panel->attachInputItem( const_cast< QQuickItem* >( item ) );
{
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;
} }
void QskInputContext::setActive( bool on ) void QskInputContext::hidePanel( const QQuickItem* item )
{ {
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() );
}
}
void QskInputContext::setInputPanelVisible( const QQuickItem* item, bool on )
{
// called from inside the controls
if ( item == nullptr )
item = qobject_cast< QQuickItem* >( QGuiApplication::focusObject() );
if ( item )
{
if ( on ) if ( on )
showPanel(); showPanel( item );
else else
hidePanel(); hidePanel( item );
}
} }
bool QskInputContext::isActive() const bool QskInputContext::isInputPanelVisible() const
{ {
const QQuickWindow* window = m_data->inputWindow; return m_data->channels.currentChannel() != nullptr;
if ( window == nullptr && m_data->inputPopup )
window = m_data->inputPopup->window();
return window && window->isVisible();
} }
QLocale QskInputContext::locale() const QLocale QskInputContext::locale() const
{ {
if ( m_data->panel ) if ( auto channel = m_data->channels.currentChannel() )
return m_data->panel->locale(); {
if ( channel->panel )
return channel->panel->locale();
}
return QLocale(); 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 ); 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 ): QskInputContextFactory::QskInputContextFactory( QObject* parent ):
QObject( parent ) QObject( parent )
{ {

View File

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