qskinny/src/inputpanel/QskInputContext.cpp

535 lines
13 KiB
C++
Raw Normal View History

2018-02-06 14:55:35 +01:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
2017-07-21 18:21:34 +02:00
#include "QskInputContext.h"
2018-04-27 13:48:51 +02:00
#include "QskInputPanel.h"
#include "QskTextPredictor.h"
#include "QskInputPanel.h"
#include "QskInputEngine.h"
2018-05-01 12:41:20 +02:00
#include "QskLinearBox.h"
#include "QskDialog.h"
#include "QskPopup.h"
#include "QskWindow.h"
#include "QskEvent.h"
#include "QskQuick.h"
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
#include <QPointer>
2018-04-20 08:52:26 +02:00
#include <QGuiApplication>
2018-04-27 13:48:51 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
#include <qpa/qplatforminputcontext.h>
2018-04-27 13:48:51 +02:00
static QPointer< QskInputContext > qskInputContext = nullptr;
2018-04-27 13:48:51 +02:00
static void qskSendToPlatformContext( QEvent::Type type )
2018-04-27 13:48:51 +02:00
{
const auto platformInputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
2018-04-27 13:48:51 +02:00
if ( platformInputContext )
{
QEvent event( type );
QCoreApplication::sendEvent( platformInputContext, &event );
}
2018-04-27 13:48:51 +02:00
}
static void qskInputContextHook()
2018-04-27 13:48:51 +02:00
{
qAddPostRoutine( []{ delete qskInputContext; } );
2018-04-27 13:48:51 +02:00
}
Q_COREAPP_STARTUP_FUNCTION( qskInputContextHook )
2018-04-27 13:48:51 +02:00
void QskInputContext::setInstance( QskInputContext* inputContext )
{
if ( inputContext != qskInputContext )
{
const auto oldContext = qskInputContext;
qskInputContext = inputContext;
2018-04-27 13:48:51 +02:00
if ( oldContext && oldContext->parent() == nullptr )
delete oldContext;
2018-04-27 13:48:51 +02:00
qskSendToPlatformContext( QEvent::PlatformPanel );
}
2018-04-27 13:48:51 +02:00
}
QskInputContext* QskInputContext::instance()
2018-04-27 13:48:51 +02:00
{
return qskInputContext;
2018-04-27 13:48:51 +02:00
}
static inline uint qskHashLocale( const QLocale& locale )
2018-04-20 08:52:26 +02:00
{
return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
2018-04-20 08:52:26 +02:00
}
namespace
2018-04-04 20:19:47 +02:00
{
class PredictorTable
2018-04-04 20:19:47 +02:00
{
public:
void replace( const QLocale& locale, QskTextPredictor* predictor )
{
const auto key = qskHashLocale( locale );
2018-04-04 20:19:47 +02:00
if ( predictor )
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
if ( it.value() == predictor )
return;
2018-04-04 20:19:47 +02:00
delete it.value();
*it = predictor;
}
else
{
hashTab.insert( key, predictor );
}
}
else
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
delete it.value();
hashTab.erase( it );
}
}
}
QskTextPredictor* find( const QLocale& locale )
{
const auto key = qskHashLocale( locale );
return hashTab.value( key, nullptr );
}
2018-04-04 20:19:47 +02:00
private:
QHash< uint, QskTextPredictor* > hashTab;
};
2018-04-20 08:52:26 +02:00
}
2018-04-01 12:47:44 +02:00
class QskInputContext::PrivateData
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
public:
2018-04-11 17:33:43 +02:00
// item receiving the input
2018-04-01 12:47:44 +02:00
QPointer< QQuickItem > inputItem;
QPointer< QskInputPanel > inputPanel;
2018-04-11 17:33:43 +02:00
// popup or window embedding the panel
2018-04-12 12:03:51 +02:00
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
2018-04-04 20:19:47 +02:00
PredictorTable predictorTable;
QskInputEngine* engine = nullptr;
2018-04-01 12:47:44 +02:00
};
2018-04-04 15:19:51 +02:00
QskInputContext::QskInputContext():
2018-04-01 12:47:44 +02:00
m_data( new PrivateData() )
{
2018-04-03 20:15:20 +02:00
setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this );
2017-07-21 18:21:34 +02:00
}
QskInputContext::~QskInputContext()
{
}
QQuickItem* QskInputContext::inputItem() const
2017-07-21 18:21:34 +02:00
{
return m_data->inputItem;
2017-07-21 18:21:34 +02:00
}
QskInputPanel* QskInputContext::inputPanel() const
2018-04-04 12:05:01 +02:00
{
if ( m_data->inputPanel == nullptr )
{
auto that = const_cast< QskInputContext* >( this );
2018-04-04 12:05:01 +02:00
auto panel = new QskInputPanel();
panel->setParent( that );
connect( panel, &QQuickItem::visibleChanged,
this, &QskInputContext::activeChanged );
connect( panel, &QskControl::localeChanged,
this, []{ qskSendToPlatformContext( QEvent::LocaleChange ); } );
m_data->inputPanel = panel;
}
return m_data->inputPanel;
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( queries & Qt::ImEnabled )
{
QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event );
2017-07-21 18:21:34 +02:00
if ( !event.value( Qt::ImEnabled ).toBool() )
2017-07-21 18:21:34 +02:00
{
hidePanel();
return;
2017-07-21 18:21:34 +02:00
}
}
if ( auto panel = inputPanel() )
panel->processInputMethodQueries( queries );
2018-04-04 15:19:51 +02:00
}
QRectF QskInputContext::panelRect() const
2017-07-21 18:21:34 +02:00
{
2018-04-27 13:48:51 +02:00
if ( m_data->inputPopup )
return m_data->inputPopup->geometry();
2017-07-21 18:21:34 +02:00
return QRectF();
2017-07-21 18:21:34 +02:00
}
2018-04-30 10:03:51 +02:00
QskPopup* QskInputContext::createEmbeddingPopup( QskInputPanel* panel )
{
auto popup = new QskPopup();
popup->setAutoLayoutChildren( true );
popup->setTransparentForPositioner( false );
popup->setModal( true );
auto box = new QskLinearBox( popup );
box->addItem( panel );
/*
When the panel has an input proxy ( usually a local text input )
we don't need to see the input item and display the overlay
and align in the center of the window.
*/
const bool hasInputProxy = panel->hasInputProxy();
popup->setOverlay( hasInputProxy );
if ( hasInputProxy )
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
else
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::showPanel()
2017-07-21 18:21:34 +02:00
{
2018-04-27 13:48:51 +02:00
auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() );
if ( focusItem == nullptr )
return;
auto panel = inputPanel();
if ( panel == nullptr )
return;
if ( ( focusItem == panel )
|| qskIsAncestorOf( panel, focusItem ) )
2018-04-11 17:33:43 +02:00
{
2018-04-27 13:48:51 +02:00
// ignore: usually the input proxy of the panel
return;
}
2018-04-27 13:48:51 +02:00
m_data->inputItem = focusItem;
2018-04-11 17:33:43 +02:00
2018-04-27 13:48:51 +02:00
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
2018-04-11 17:33:43 +02:00
{
2018-04-27 13:48:51 +02:00
// The input panel is embedded in a top level window
2018-04-30 10:03:51 +02:00
delete m_data->inputPopup;
2018-04-11 17:33:43 +02:00
2018-04-30 10:03:51 +02:00
if ( m_data->inputWindow == nullptr )
2018-04-11 17:33:43 +02:00
{
auto window = createEmbeddingWindow( panel );
2018-04-11 17:33:43 +02:00
2018-04-30 10:03:51 +02:00
if ( window )
{
QSize size = window->effectivePreferredSize();
if ( size.isEmpty() )
{
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
2018-04-30 10:03:51 +02:00
window->resize( size );
window->show();
2018-04-30 10:03:51 +02:00
window->setDeleteOnClose( true );
window->installEventFilter( this );
}
2018-04-11 17:33:43 +02:00
2018-04-30 10:03:51 +02:00
m_data->inputWindow = window;
}
2018-04-11 17:33:43 +02:00
}
else
2017-07-21 18:21:34 +02:00
{
2018-04-27 13:48:51 +02:00
// The input panel is embedded in a popup
2018-04-30 10:03:51 +02:00
delete m_data->inputWindow;
2017-07-21 18:21:34 +02:00
2018-04-30 10:03:51 +02:00
if ( m_data->inputPopup == nullptr )
2017-07-21 18:21:34 +02:00
{
auto popup = createEmbeddingPopup( panel );
2018-04-30 10:03:51 +02:00
if ( popup )
{
popup->setParentItem( m_data->inputItem->window()->contentItem() );
if ( popup->parent() == nullptr )
popup->setParent( this );
2018-04-11 17:33:43 +02:00
2018-04-30 10:03:51 +02:00
popup->setVisible( true );
popup->installEventFilter( this );
}
m_data->inputPopup = popup;
2017-07-21 18:21:34 +02:00
}
}
2018-04-11 17:33:43 +02:00
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
2018-04-27 13:48:51 +02:00
panel->setLocale( locale() );
panel->attachInputItem( m_data->inputItem );
panel->setEngine( m_data->engine );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::hidePanel()
2017-07-21 18:21:34 +02:00
{
2018-04-27 13:48:51 +02:00
if ( m_data->inputPopup )
2018-04-11 17:33:43 +02:00
{
#if 1
2018-04-27 13:48:51 +02:00
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 );
}
#endif
2018-04-30 10:03:51 +02:00
}
if ( auto panel = inputPanel() )
2018-04-30 10:03:51 +02:00
{
panel->setParentItem( nullptr );
panel->attachInputItem( nullptr );
panel->setEngine( nullptr );
2018-04-30 10:03:51 +02:00
}
2018-04-30 10:03:51 +02:00
if ( m_data->inputPopup )
{
2018-04-27 13:48:51 +02:00
m_data->inputPopup->deleteLater();
2018-04-11 17:33:43 +02:00
}
2018-04-27 13:48:51 +02:00
if ( m_data->inputWindow )
2018-04-11 17:33:43 +02:00
{
2018-04-27 13:48:51 +02:00
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
2018-04-11 17:33:43 +02:00
window->removeEventFilter( this );
window->close(); // deleteOnClose is set
}
2018-04-27 13:48:51 +02:00
m_data->inputItem = nullptr;
2017-07-21 18:21:34 +02:00
}
void QskInputContext::setActive( bool on )
2017-07-21 18:21:34 +02:00
{
if ( on )
showPanel();
else
hidePanel();
}
bool QskInputContext::isActive() const
{
if ( auto panel = inputPanel() )
{
return panel && panel->isVisible()
&& panel->window() && panel->window()->isVisible();
}
return false;
2017-07-21 18:21:34 +02:00
}
QLocale QskInputContext::locale() const
{
if ( m_data->inputItem )
{
QInputMethodQueryEvent event( Qt::ImPreferredLanguage );
QCoreApplication::sendEvent( m_data->inputItem, &event );
return event.value( Qt::ImPreferredLanguage ).toLocale();
}
return QLocale();
2017-07-21 18:21:34 +02:00
}
void QskInputContext::setFocusObject( QObject* focusObject )
{
2018-04-27 13:48:51 +02:00
if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject )
{
// we don't care
return;
}
2018-03-14 17:30:39 +01:00
2018-04-30 10:03:51 +02:00
const auto w = m_data->inputItem->window();
if ( w == nullptr )
return;
2018-04-11 17:33:43 +02:00
2018-04-30 10:03:51 +02:00
if ( m_data->inputWindow )
2018-04-27 13:48:51 +02:00
{
2018-04-30 10:03:51 +02:00
if ( focusObject == nullptr )
2018-04-11 17:33:43 +02:00
{
2018-04-30 10:03:51 +02:00
if ( m_data->inputItem->hasFocus() )
2018-04-11 17:33:43 +02:00
{
2018-04-30 10:03:51 +02:00
/*
As long as the focus is nowhere and
2018-04-30 10:03:51 +02:00
the local focus stay on the input item
we don't care
*/
return;
2018-04-11 17:33:43 +02:00
}
2018-04-27 13:48:51 +02:00
}
2018-04-30 10:03:51 +02:00
else
2018-04-27 13:48:51 +02:00
{
2018-04-30 10:03:51 +02:00
const auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if ( focusItem && focusItem->window() == m_data->inputWindow )
return;
2018-04-11 17:33:43 +02:00
}
2018-04-27 13:48:51 +02:00
}
2018-04-30 10:03:51 +02:00
else if ( m_data->inputPopup )
2018-04-27 13:48:51 +02:00
{
2018-04-30 10:03:51 +02:00
if ( w->contentItem()->scopedFocusItem() == m_data->inputPopup )
{
/*
As long as the focus stays inside the inputPopup
we don't care
*/
return;
}
}
2018-04-30 10:03:51 +02:00
hidePanel();
2018-04-30 10:03:51 +02:00
m_data->inputItem = nullptr;
2017-07-21 18:21:34 +02:00
}
void QskInputContext::registerPredictor(
const QLocale& locale, QskTextPredictor* predictor )
2017-07-21 18:21:34 +02:00
{
auto oldPredictor = m_data->predictorTable.find( locale );
if ( predictor == oldPredictor )
return;
2018-04-20 08:52:26 +02:00
if ( predictor )
predictor->setParent( this );
2018-04-20 08:52:26 +02:00
m_data->predictorTable.replace( locale, predictor );
2018-04-20 08:52:26 +02:00
if ( oldPredictor )
delete oldPredictor;
2018-04-20 08:52:26 +02:00
if ( qskHashLocale( locale ) == qskHashLocale( this->locale() ) )
m_data->engine->setPredictor( predictor );
2017-07-21 18:21:34 +02:00
}
QskTextPredictor* QskInputContext::registeredPredictor( const QLocale& locale )
2018-04-20 08:52:26 +02:00
{
return m_data->predictorTable.find( locale );
2018-04-20 08:52:26 +02:00
}
void QskInputContext::processClickAt( int cursorPosition )
2018-04-20 08:52:26 +02:00
{
Q_UNUSED( cursorPosition );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::commitPrediction( bool )
2018-04-04 12:05:01 +02:00
{
2018-04-27 13:48:51 +02:00
/*
called, when the input item loses the focus.
2018-04-27 13:48:51 +02:00
As it it should be possible to navigate inside of the
inputPanel what should we do here ?
2018-04-27 13:48:51 +02:00
*/
2018-04-04 12:05:01 +02:00
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
2018-04-11 17:33:43 +02:00
{
2018-04-12 12:03:51 +02:00
if ( object == m_data->inputWindow )
{
2018-04-12 12:03:51 +02:00
switch( event->type() )
2018-04-11 17:33:43 +02:00
{
2018-04-12 12:03:51 +02:00
case QEvent::Move:
{
Q_EMIT panelRectChanged();
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::Resize:
{
if ( auto panel = inputPanel() )
panel->setSize( m_data->inputWindow->size() );
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::DeferredDelete:
{
m_data->inputWindow = nullptr;
break;
}
default:
break;
2018-04-11 17:33:43 +02:00
}
2018-04-12 12:03:51 +02:00
}
2018-04-20 08:52:26 +02:00
else if ( object == m_data->inputPopup )
2018-04-12 12:03:51 +02:00
{
2018-04-12 13:32:28 +02:00
switch( static_cast< int >( event->type() ) )
{
2018-04-12 12:03:51 +02:00
case QskEvent::GeometryChange:
2018-04-11 17:33:43 +02:00
{
Q_EMIT panelRectChanged();
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::DeferredDelete:
{
2018-04-20 08:52:26 +02:00
m_data->inputPopup = nullptr;
2018-04-12 12:03:51 +02:00
break;
}
}
}
return Inherited::eventFilter( object, event );
}
2017-07-21 18:21:34 +02:00
#include "moc_QskInputContext.cpp"