qskinny/src/inputpanel/QskInputContext.cpp

542 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-04-27 13:48:51 +02:00
#include <QskLinearBox.h>
2017-07-21 18:21:34 +02:00
#include <QskDialog.h>
2018-04-11 17:33:43 +02:00
#include <QskPopup.h>
2017-07-21 18:21:34 +02:00
#include <QskWindow.h>
2018-04-11 17:33:43 +02:00
#include <QskEvent.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>
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 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;
2018-04-11 17:33:43 +02:00
2018-04-27 13:48:51 +02:00
// popup or window embedding qskInputPanel
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()
{
}
bool QskInputContext::isValid() const
{
return true;
}
2018-04-04 12:05:01 +02:00
bool QskInputContext::hasCapability( Capability ) const
{
// what is QPlatformInputContext::HiddenTextCapability ???
return true;
}
QQuickItem* QskInputContext::inputItem()
2017-07-21 18:21:34 +02: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 18:21:34 +02:00
if ( !event.value( Qt::ImEnabled ).toBool() )
2017-07-21 18:21:34 +02:00
{
hideInputPanel();
return;
2017-07-21 18:21:34 +02:00
}
}
2018-04-27 13:48:51 +02:00
if ( qskInputPanel )
qskInputPanel->processInputMethodQueries( queries );
2018-04-04 15:19:51 +02:00
}
2017-07-21 18:21:34 +02:00
QRectF QskInputContext::keyboardRect() const
{
2018-04-27 13:48:51 +02:00
// is this correct and what is this good for ?
if ( m_data->inputPopup )
return m_data->inputPopup->geometry();
2017-07-21 18:21:34 +02:00
return Inherited::keyboardRect();
}
bool QskInputContext::isAnimating() const
{
2018-04-27 13:48:51 +02:00
// can be implemented once we have some sliding/fading effects
2017-07-21 18:21:34 +02:00
return false;
}
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;
}
2017-07-21 18:21:34 +02:00
void QskInputContext::showInputPanel()
{
2018-04-27 13:48:51 +02:00
auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() );
2018-04-11 17:33:43 +02:00
2018-04-27 13:48:51 +02:00
if ( focusItem == nullptr )
return;
if ( ( focusItem == qskInputPanel )
|| qskIsAncestorOf( qskInputPanel, 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 ( qskInputPanel == nullptr )
qskSetInputPanel( new QskInputPanel() );
2018-04-11 17:33:43 +02:00
2018-04-27 13:48:51 +02:00
connect( qskInputPanel, &QQuickItem::visibleChanged,
this, &QPlatformInputContext::emitInputPanelVisibleChanged,
Qt::UniqueConnection );
connect( qskInputPanel, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged,
Qt::UniqueConnection );
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
{
2018-04-30 10:03:51 +02:00
auto window = createEmbeddingWindow( qskInputPanel );
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
{
2018-04-30 10:03:51 +02:00
auto popup = createEmbeddingPopup( qskInputPanel );
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
qskInputPanel->setLocale( locale() );
qskInputPanel->attachInputItem( m_data->inputItem );
qskInputPanel->setEngine( m_data->engine );
2017-07-21 18:21:34 +02:00
}
void QskInputContext::hideInputPanel()
{
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 ( qskInputPanel )
{
qskInputPanel->setParentItem( nullptr );
qskInputPanel->attachInputItem( nullptr );
qskInputPanel->setEngine( nullptr );
}
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
}
bool QskInputContext::isInputPanelVisible() const
{
2018-04-27 13:48:51 +02:00
return qskInputPanel && qskInputPanel->isVisible()
&& qskInputPanel->window() && qskInputPanel->window()->isVisible();
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
}
2018-04-04 12:05:01 +02:00
Qt::LayoutDirection QskInputContext::inputDirection() const
{
return Inherited::inputDirection();
}
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 noewhere and
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
hideInputPanel();
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::invokeAction( QInputMethod::Action, int )
2018-04-20 08:52:26 +02:00
{
2017-07-21 18:21:34 +02:00
}
2018-04-04 12:05:01 +02:00
void QskInputContext::reset()
{
}
void QskInputContext::commit()
{
2018-04-27 13:48:51 +02: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 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:
{
2018-04-27 13:48:51 +02:00
if ( qskInputPanel )
2018-04-12 12:03:51 +02:00
emitKeyboardRectChanged();
2018-04-11 17:33:43 +02:00
2018-04-12 12:03:51 +02:00
break;
}
case QEvent::Resize:
{
2018-04-27 13:48:51 +02:00
if ( qskInputPanel )
qskInputPanel->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
{
emitKeyboardRectChanged();
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 );
}
2018-04-20 08:52:26 +02:00
bool QskInputContext::filterEvent( const QEvent* )
2018-04-04 12:05:01 +02:00
{
2018-04-04 15:19:51 +02:00
// called from QXcbKeyboard, but what about other platforms
2018-04-04 12:05:01 +02:00
return false;
}
2017-07-21 18:21:34 +02:00
#include "moc_QskInputContext.cpp"