qskinny/src/inputpanel/QskInputContext.cpp

513 lines
13 KiB
C++
Raw Normal View History

2018-02-06 13:55:35 +00: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 16:21:34 +00:00
#include "QskInputContext.h"
2018-04-27 11:48:51 +00:00
#include "QskInputPanel.h"
#include "QskTextPredictor.h"
#include "QskInputPanel.h"
#include "QskInputEngine.h"
2018-04-27 11:48:51 +00:00
#include <QskLinearBox.h>
2017-07-21 16:21:34 +00:00
#include <QskDialog.h>
2018-04-11 15:33:43 +00:00
#include <QskPopup.h>
2017-07-21 16:21:34 +00:00
#include <QskWindow.h>
2018-04-11 15:33:43 +00:00
#include <QskEvent.h>
2017-07-21 16:21:34 +00:00
2018-04-01 10:47:44 +00:00
#include <QPointer>
2018-04-20 06:52:26 +00:00
#include <QGuiApplication>
2018-04-27 11:48:51 +00:00
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
static QPointer< QskInputPanel > qskInputPanel = nullptr;
static void qskDeletePanel()
{
delete qskInputPanel;
}
static void qskInputPanelHook()
{
qAddPostRoutine( qskDeletePanel );
}
Q_COREAPP_STARTUP_FUNCTION( qskInputPanelHook )
static void qskSetInputPanel( QskInputPanel* inputPanel )
{
if ( inputPanel == qskInputPanel )
return;
delete qskInputPanel;
qskInputPanel = inputPanel;
}
void QskInputContext::setInputPanel( QskInputPanel* inputPanel )
{
if ( inputPanel == qskInputPanel )
return;
qskSetInputPanel( inputPanel );
const auto inputContext =
QGuiApplicationPrivate::platformIntegration()->inputContext();
if ( auto context = qobject_cast< QskInputContext* >( inputContext ) )
context->hideInputPanel();
}
QskInputPanel* QskInputContext::inputPanel()
{
return qskInputPanel;
}
static inline uint qskHashLocale( const QLocale& locale )
2018-04-20 06:52:26 +00:00
{
return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
2018-04-20 06:52:26 +00:00
}
namespace
2018-04-04 18:19:47 +00:00
{
class PredictorTable
2018-04-04 18:19:47 +00:00
{
public:
void replace( const QLocale& locale, QskTextPredictor* predictor )
{
const auto key = qskHashLocale( locale );
2018-04-04 18:19:47 +00:00
if ( predictor )
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
if ( it.value() == predictor )
return;
2018-04-04 18:19:47 +00: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 18:19:47 +00:00
private:
QHash< uint, QskTextPredictor* > hashTab;
};
2018-04-20 06:52:26 +00:00
}
2018-04-01 10:47:44 +00:00
class QskInputContext::PrivateData
2017-07-21 16:21:34 +00:00
{
2018-04-01 10:47:44 +00:00
public:
2018-04-11 15:33:43 +00:00
// item receiving the input
2018-04-01 10:47:44 +00:00
QPointer< QQuickItem > inputItem;
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
// popup or window embedding qskInputPanel
2018-04-12 10:03:51 +00:00
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
2018-04-04 18:19:47 +00:00
PredictorTable predictorTable;
QskInputEngine* engine = nullptr;
2018-04-01 10:47:44 +00:00
};
2018-04-04 13:19:51 +00:00
QskInputContext::QskInputContext():
2018-04-01 10:47:44 +00:00
m_data( new PrivateData() )
{
2018-04-03 18:15:20 +00:00
setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this );
2017-07-21 16:21:34 +00:00
}
QskInputContext::~QskInputContext()
{
}
bool QskInputContext::isValid() const
{
return true;
}
2018-04-04 10:05:01 +00:00
bool QskInputContext::hasCapability( Capability ) const
{
// what is QPlatformInputContext::HiddenTextCapability ???
return true;
}
QQuickItem* QskInputContext::inputItem()
2017-07-21 16:21:34 +00:00
{
return m_data->inputItem;
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( queries & Qt::ImEnabled )
{
QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event );
2017-07-21 16:21:34 +00:00
if ( !event.value( Qt::ImEnabled ).toBool() )
2017-07-21 16:21:34 +00:00
{
hideInputPanel();
return;
2017-07-21 16:21:34 +00:00
}
}
2018-04-27 11:48:51 +00:00
if ( qskInputPanel )
qskInputPanel->processInputMethodQueries( queries );
2018-04-04 13:19:51 +00:00
}
2017-07-21 16:21:34 +00:00
QRectF QskInputContext::keyboardRect() const
{
2018-04-27 11:48:51 +00:00
// is this correct and what is this good for ?
if ( m_data->inputPopup )
return m_data->inputPopup->geometry();
2017-07-21 16:21:34 +00:00
return Inherited::keyboardRect();
}
bool QskInputContext::isAnimating() const
{
2018-04-27 11:48:51 +00:00
// can be implemented once we have some sliding/fading effects
2017-07-21 16:21:34 +00:00
return false;
}
void QskInputContext::showInputPanel()
{
2018-04-27 11:48:51 +00:00
auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() );
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
if ( focusItem == nullptr )
return;
if ( ( focusItem == qskInputPanel )
|| qskIsAncestorOf( qskInputPanel, focusItem ) )
2018-04-11 15:33:43 +00:00
{
2018-04-27 11:48:51 +00:00
// ignore: usually the input proxy of the panel
return;
}
2018-04-27 11:48:51 +00:00
m_data->inputItem = focusItem;
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
auto& inputPopup = m_data->inputPopup;
auto& inputWindow = m_data->inputWindow;
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
if ( qskInputPanel == nullptr )
qskSetInputPanel( new QskInputPanel() );
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
connect( qskInputPanel, &QQuickItem::visibleChanged,
this, &QPlatformInputContext::emitInputPanelVisibleChanged,
Qt::UniqueConnection );
connect( qskInputPanel, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged,
Qt::UniqueConnection );
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
2018-04-11 15:33:43 +00:00
{
2018-04-27 11:48:51 +00:00
// The input panel is embedded in a top level window
2018-04-11 15:33:43 +00:00
delete inputPopup;
if ( inputWindow == nullptr )
{
inputWindow = new QskWindow();
inputWindow->setDeleteOnClose( true );
2018-04-27 11:48:51 +00:00
#if 0
2018-04-11 15:33:43 +00:00
inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
2018-04-27 11:48:51 +00:00
#endif
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
qskInputPanel->setParentItem( inputWindow->contentItem() );
}
2018-04-27 11:48:51 +00:00
QSize size = qskInputPanel->sizeHint().toSize();
if ( size.isEmpty() )
{
// no idea, may be something based on the screen size
size = QSize( 800, 240 );
}
2018-04-27 11:48:51 +00:00
inputWindow->resize( size );
inputWindow->show();
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
inputWindow->installEventFilter( this );
2018-04-11 15:33:43 +00:00
}
else
2017-07-21 16:21:34 +00:00
{
2018-04-27 11:48:51 +00:00
// The input panel is embedded in a popup
2018-04-11 15:33:43 +00:00
delete inputWindow;
2017-07-21 16:21:34 +00:00
2018-04-11 15:33:43 +00:00
if ( inputPopup == nullptr )
2017-07-21 16:21:34 +00:00
{
2018-04-27 11:48:51 +00:00
inputPopup = new QskPopup( m_data->inputItem->window()->contentItem() );
2018-04-27 11:48:51 +00:00
inputPopup->setAutoLayoutChildren( true );
inputPopup->setTransparentForPositioner( false );
inputPopup->setModal( true );
2018-04-27 11:48:51 +00:00
auto box = new QskLinearBox( inputPopup );
box->addItem( qskInputPanel );
2018-04-27 11:48:51 +00:00
/*
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 = qskInputPanel->hasInputProxy();
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
inputPopup->setOverlay( hasInputProxy );
2018-04-11 15:33:43 +00:00
if ( hasInputProxy )
box->setMargins( QMarginsF( 5, 5, 5, 5 ) );
else
2018-04-27 11:48:51 +00:00
box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge );
2017-07-21 16:21:34 +00:00
}
2018-04-27 11:48:51 +00:00
inputPopup->setParentItem( m_data->inputItem->window()->contentItem() );
2018-04-11 15:33:43 +00:00
inputPopup->setVisible( true );
2018-04-27 11:48:51 +00:00
inputPopup->installEventFilter( this );
}
2018-04-11 15:33:43 +00:00
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
2018-04-27 11:48:51 +00:00
qskInputPanel->setLocale( locale() );
qskInputPanel->attachInputItem( m_data->inputItem );
qskInputPanel->setEngine( m_data->engine );
2017-07-21 16:21:34 +00:00
}
void QskInputContext::hideInputPanel()
{
2018-04-27 11:48:51 +00:00
if ( m_data->inputPopup )
2018-04-11 15:33:43 +00:00
{
#if 1
2018-04-27 11:48:51 +00: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-27 11:48:51 +00:00
m_data->inputPopup->deleteLater();
2018-04-11 15:33:43 +00:00
}
2018-04-27 11:48:51 +00:00
if ( m_data->inputWindow )
2018-04-11 15:33:43 +00:00
{
2018-04-27 11:48:51 +00:00
QskWindow* window = m_data->inputWindow;
m_data->inputWindow = nullptr;
2018-04-11 15:33:43 +00:00
window->removeEventFilter( this );
window->close(); // deleteOnClose is set
}
2018-04-27 11:48:51 +00:00
if ( qskInputPanel )
{
//qskInputPanel->setVisible( false );
qskInputPanel->setParentItem( nullptr );
qskInputPanel->attachInputItem( nullptr );
qskInputPanel->setEngine( nullptr );
}
2018-04-27 11:48:51 +00:00
m_data->inputItem = nullptr;
2017-07-21 16:21:34 +00:00
}
bool QskInputContext::isInputPanelVisible() const
{
2018-04-27 11:48:51 +00:00
return qskInputPanel && qskInputPanel->isVisible()
&& qskInputPanel->window() && qskInputPanel->window()->isVisible();
2017-07-21 16:21:34 +00: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 16:21:34 +00:00
}
2018-04-04 10:05:01 +00:00
Qt::LayoutDirection QskInputContext::inputDirection() const
{
return Inherited::inputDirection();
}
2017-07-21 16:21:34 +00:00
void QskInputContext::setFocusObject( QObject* focusObject )
{
2018-04-27 11:48:51 +00:00
if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject )
{
// we don't care
return;
}
2018-03-14 16:30:39 +00:00
2018-04-27 11:48:51 +00:00
bool doTerminate = true;
if ( focusObject == nullptr && m_data->inputPopup )
2018-03-14 16:30:39 +00:00
{
2018-04-27 11:48:51 +00:00
if ( const auto window = m_data->inputItem->window() )
2018-03-14 16:30:39 +00:00
{
2018-04-27 11:48:51 +00:00
auto focusItem = window->contentItem()->scopedFocusItem();
if ( focusItem == m_data->inputPopup )
doTerminate = false;
2018-03-14 16:30:39 +00:00
}
2017-07-21 16:21:34 +00:00
}
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
if ( doTerminate )
{
if ( m_data->inputWindow )
2018-04-11 15:33:43 +00:00
{
2018-04-27 11:48:51 +00:00
auto focusWindow = QGuiApplication::focusWindow();
if ( focusWindow == nullptr ||
QGuiApplication::focusWindow() == m_data->inputWindow )
2018-04-11 15:33:43 +00:00
{
2018-04-27 11:48:51 +00:00
doTerminate = false;
2018-04-11 15:33:43 +00:00
}
2018-04-27 11:48:51 +00:00
}
else if ( m_data->inputPopup )
{
auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if ( ( focusItem == m_data->inputPopup )
|| qskIsAncestorOf( m_data->inputPopup, focusItem ) )
2018-04-13 14:32:48 +00:00
{
2018-04-27 11:48:51 +00:00
doTerminate = false;
2018-04-13 14:32:48 +00:00
}
2018-04-11 15:33:43 +00:00
}
2018-04-27 11:48:51 +00:00
}
2018-04-11 15:33:43 +00:00
2018-04-27 11:48:51 +00:00
if ( doTerminate )
{
hideInputPanel();
m_data->inputItem = nullptr;
}
2017-07-21 16:21:34 +00:00
}
void QskInputContext::registerPredictor(
const QLocale& locale, QskTextPredictor* predictor )
2017-07-21 16:21:34 +00:00
{
auto oldPredictor = m_data->predictorTable.find( locale );
if ( predictor == oldPredictor )
return;
2018-04-20 06:52:26 +00:00
if ( predictor )
predictor->setParent( this );
2018-04-20 06:52:26 +00:00
m_data->predictorTable.replace( locale, predictor );
2018-04-20 06:52:26 +00:00
if ( oldPredictor )
delete oldPredictor;
2018-04-20 06:52:26 +00:00
if ( qskHashLocale( locale ) == qskHashLocale( this->locale() ) )
m_data->engine->setPredictor( predictor );
2017-07-21 16:21:34 +00:00
}
QskTextPredictor* QskInputContext::registeredPredictor( const QLocale& locale )
2018-04-20 06:52:26 +00:00
{
return m_data->predictorTable.find( locale );
2018-04-20 06:52:26 +00:00
}
void QskInputContext::invokeAction( QInputMethod::Action, int )
2018-04-20 06:52:26 +00:00
{
2017-07-21 16:21:34 +00:00
}
2018-04-04 10:05:01 +00:00
void QskInputContext::reset()
{
}
void QskInputContext::commit()
{
2018-04-27 11:48:51 +00:00
/*
commit is called, when the input item loses the focus.
As it it should be possible to navigate inside of the
inputPanel this is no valid reason to hide the panel.
*/
2018-04-04 10:05:01 +00:00
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
2018-04-11 15:33:43 +00:00
{
2018-04-12 10:03:51 +00:00
if ( object == m_data->inputWindow )
{
2018-04-12 10:03:51 +00:00
switch( event->type() )
2018-04-11 15:33:43 +00:00
{
2018-04-12 10:03:51 +00:00
case QEvent::Move:
{
2018-04-27 11:48:51 +00:00
if ( qskInputPanel )
2018-04-12 10:03:51 +00:00
emitKeyboardRectChanged();
2018-04-11 15:33:43 +00:00
2018-04-12 10:03:51 +00:00
break;
}
case QEvent::Resize:
{
2018-04-27 11:48:51 +00:00
if ( qskInputPanel )
qskInputPanel->setSize( m_data->inputWindow->size() );
2018-04-12 10:03:51 +00:00
break;
}
case QEvent::DeferredDelete:
{
m_data->inputWindow = nullptr;
break;
}
default:
break;
2018-04-11 15:33:43 +00:00
}
2018-04-12 10:03:51 +00:00
}
2018-04-20 06:52:26 +00:00
else if ( object == m_data->inputPopup )
2018-04-12 10:03:51 +00:00
{
2018-04-12 11:32:28 +00:00
switch( static_cast< int >( event->type() ) )
{
2018-04-12 10:03:51 +00:00
case QskEvent::GeometryChange:
2018-04-11 15:33:43 +00:00
{
emitKeyboardRectChanged();
2018-04-12 10:03:51 +00:00
break;
}
case QEvent::DeferredDelete:
{
2018-04-20 06:52:26 +00:00
m_data->inputPopup = nullptr;
2018-04-12 10:03:51 +00:00
break;
}
}
}
return Inherited::eventFilter( object, event );
}
2018-04-20 06:52:26 +00:00
bool QskInputContext::filterEvent( const QEvent* )
2018-04-04 10:05:01 +00:00
{
2018-04-04 13:19:51 +00:00
// called from QXcbKeyboard, but what about other platforms
2018-04-04 10:05:01 +00:00
return false;
}
2017-07-21 16:21:34 +00:00
#include "moc_QskInputContext.cpp"